refactor: Update IPC server to handle log level and elevate mode changes
This commit is contained in:
@ -9,6 +9,7 @@
|
||||
#include "base/Log.h"
|
||||
#include "common/constants.h"
|
||||
#include "deskflow/DaemonApp.h"
|
||||
#include "deskflow/ipc/DaemonIpcServer.h"
|
||||
|
||||
#include "arch/Arch.h"
|
||||
#include "base/EventQueue.h"
|
||||
@ -44,28 +45,58 @@ int main(int argc, char **argv)
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
const auto pDaemon = &DaemonApp::instance();
|
||||
QObject::connect(pDaemon, &DaemonApp::mainLoopFinished, &app, &QCoreApplication::quit);
|
||||
QObject::connect(pDaemon, &DaemonApp::fatalErrorOccurred, &app, &QCoreApplication::quit);
|
||||
QObject::connect(pDaemon, &DaemonApp::serviceInstalled, &app, &QCoreApplication::quit);
|
||||
QObject::connect(pDaemon, &DaemonApp::serviceUninstalled, &app, &QCoreApplication::quit);
|
||||
pDaemon->init(&events, argc, argv);
|
||||
const auto initResult = pDaemon->init(&events, argc, argv);
|
||||
|
||||
// Thread must be on the heap, as the thread needs to be deleted only when the loop finishes,
|
||||
// and not when we go out of scope.
|
||||
// Deliberately do not set ownership of the thread to the daemon, as we want to delete the thread
|
||||
// when the loop finishes, and not when the daemon is deleted.
|
||||
QThread *thread = new QThread(); // NOSONAR
|
||||
pDaemon->moveToThread(thread);
|
||||
QObject::connect(thread, &QThread::started, pDaemon, &DaemonApp::run);
|
||||
QObject::connect(thread, &QThread::finished, pDaemon, &QObject::deleteLater);
|
||||
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
thread->start();
|
||||
switch (initResult) {
|
||||
using enum DaemonApp::InitResult;
|
||||
|
||||
return QCoreApplication::exec();
|
||||
case StartDaemon: {
|
||||
using namespace deskflow::core;
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
auto *pDaemonThread = new QThread(); // NOSONAR
|
||||
pDaemon->moveToThread(pDaemonThread);
|
||||
|
||||
QObject::connect(pDaemonThread, &QThread::started, pDaemon, &DaemonApp::run);
|
||||
QObject::connect(pDaemonThread, &QThread::finished, pDaemon, &QObject::deleteLater);
|
||||
QObject::connect(pDaemonThread, &QThread::finished, pDaemonThread, &QThread::deleteLater);
|
||||
QObject::connect(pDaemonThread, &QThread::finished, QCoreApplication::instance(), &QCoreApplication::quit);
|
||||
|
||||
// The daemon app is on it's own thread which doesn't have a Qt event loop, so we need to use direct connection.
|
||||
auto *ipcServer = new ipc::DaemonIpcServer(&app); // NOSONAR
|
||||
QObject::connect(
|
||||
ipcServer, &ipc::DaemonIpcServer::logLevelChanged, pDaemon, &DaemonApp::saveLogLevel, //
|
||||
Qt::DirectConnection
|
||||
);
|
||||
QObject::connect(
|
||||
ipcServer, &ipc::DaemonIpcServer::elevateModeChanged, pDaemon, &DaemonApp::setElevate, //
|
||||
Qt::DirectConnection
|
||||
);
|
||||
QObject::connect(
|
||||
ipcServer, &ipc::DaemonIpcServer::commandChanged, pDaemon, &DaemonApp::setCommand, //
|
||||
Qt::DirectConnection
|
||||
);
|
||||
QObject::connect(
|
||||
ipcServer, &ipc::DaemonIpcServer::restartRequested, pDaemon, &DaemonApp::restartCoreProcess, //
|
||||
Qt::DirectConnection
|
||||
);
|
||||
pDaemonThread->start();
|
||||
return QCoreApplication::exec();
|
||||
}
|
||||
|
||||
case FatalError:
|
||||
return kExitFailed;
|
||||
|
||||
default:
|
||||
return kExitSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
#if SYSAPI_WIN32
|
||||
|
||||
// Win32 subsystem entry point (simply forwards to main).
|
||||
// We need this because using regular main under the Win32 subsystem results in empty args.
|
||||
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
||||
{
|
||||
return main(__argc, __argv);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Deskflow -- mouse and keyboard sharing utility
|
||||
* SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd.
|
||||
* SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman
|
||||
* SPDX-FileCopyrightText: (C) 2010 - 2018, 2024 - 2025 Symless Ltd.
|
||||
* SPDX-FileCopyrightText: (C) 2002 - 2007 Chris Schoeneman
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||
*/
|
||||
|
||||
@ -22,8 +22,8 @@
|
||||
// make assert available since we use it a lot
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h> // IWYU pragma: keep
|
||||
#include <string.h> // IWYU pragma: keep
|
||||
|
||||
// defined in Carbon
|
||||
#if !defined(__MACTYPES__)
|
||||
@ -35,10 +35,9 @@
|
||||
|
||||
enum
|
||||
{
|
||||
kExitSuccess = 0, // successful completion
|
||||
kExitFailed = 1, // general failure
|
||||
kExitTerminated = 2, // killed by signal
|
||||
kExitArgs = 3, // bad arguments
|
||||
kExitConfig = 4, // cannot read configuration
|
||||
kExitSubscription = 5 // subscription error
|
||||
kExitSuccess = 0, // successful completion
|
||||
kExitFailed = 1, // general failure
|
||||
kExitTerminated = 2, // killed by signal
|
||||
kExitArgs = 3, // bad arguments
|
||||
kExitConfig = 4, // cannot read configuration
|
||||
};
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
/*
|
||||
* Deskflow -- mouse and keyboard sharing utility
|
||||
* SPDX-FileCopyrightText: (C) 2024 Chris Rizzitello <sithlord48@gmail.com>
|
||||
* SPDX-FileCopyrightText: (C) 2012 - 2025 Symless Ltd.
|
||||
* SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman
|
||||
* SPDX-FileCopyrightText: (C) 2025 Symless Ltd.
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||
*/
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Deskflow -- mouse and keyboard sharing utility
|
||||
* SPDX-FileCopyrightText: (C) 2012 Symless Ltd.
|
||||
* SPDX-FileCopyrightText: (C) 2012 - 2025 Symless Ltd.
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||
*/
|
||||
|
||||
@ -19,7 +19,6 @@
|
||||
#include "deskflow/ArgParser.h"
|
||||
#include "deskflow/ClientArgs.h"
|
||||
#include "deskflow/ServerArgs.h"
|
||||
#include "ipc/DaemonIpcServer.h"
|
||||
#include "ipc/IpcClientProxy.h"
|
||||
#include "ipc/IpcLogOutputter.h"
|
||||
#include "ipc/IpcMessage.h"
|
||||
@ -37,16 +36,10 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
#elif SYSAPI_UNIX
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#endif
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@ -99,13 +92,13 @@ int winMainLoopStatic(int, const char **)
|
||||
}
|
||||
#endif
|
||||
|
||||
DaemonApp::DaemonApp() : m_ipcServer{new ipc::DaemonIpcServer(this)}
|
||||
void showHelp(int argc, char **argv) // NOSONAR - CLI args
|
||||
{
|
||||
connect(m_ipcServer, &ipc::DaemonIpcServer::elevateModeChanged, this, &DaemonApp::handleElevateModeChanged);
|
||||
connect(m_ipcServer, &ipc::DaemonIpcServer::commandChanged, this, &DaemonApp::handleCommandChanged);
|
||||
connect(m_ipcServer, &ipc::DaemonIpcServer::restartRequested, this, &DaemonApp::handleRestartRequested);
|
||||
const auto binName = argc > 0 ? std::filesystem::path(argv[0]).filename().string() : "deskflow-core";
|
||||
std::cout << "Usage: " << binName << " daemon [-f|--foreground] [--install] [--uninstall]" << std::endl;
|
||||
}
|
||||
|
||||
DaemonApp::DaemonApp() = default;
|
||||
DaemonApp::~DaemonApp() = default;
|
||||
|
||||
void DaemonApp::run()
|
||||
@ -125,34 +118,62 @@ void DaemonApp::run()
|
||||
ARCH->daemonize(kAppName, unixMainLoopStatic);
|
||||
#endif
|
||||
}
|
||||
|
||||
Q_EMIT mainLoopFinished();
|
||||
}
|
||||
|
||||
void DaemonApp::handleElevateModeChanged(int mode)
|
||||
void DaemonApp::saveLogLevel(const QString &logLevel) const
|
||||
{
|
||||
LOG_DEBUG("elevate mode changed: %d", mode);
|
||||
m_elevateMode = mode;
|
||||
LOG_DEBUG("log level changed: %s", logLevel.toUtf8().constData());
|
||||
CLOG->setFilter(logLevel.toUtf8().constData());
|
||||
|
||||
try {
|
||||
// saves setting for next time the daemon starts.
|
||||
ARCH->setting("LogLevel", logLevel.toStdString());
|
||||
} catch (XArch &e) {
|
||||
LOG((CLOG_ERR "failed to save log level setting: %s", e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void DaemonApp::handleCommandChanged(const QString &command)
|
||||
void DaemonApp::setElevate(bool elevate)
|
||||
{
|
||||
LOG((CLOG_DEBUG "elevate value changed: %s", elevate ? "yes" : "no"));
|
||||
m_elevate = elevate;
|
||||
|
||||
try {
|
||||
// saves setting for next time the daemon starts.
|
||||
ARCH->setting("Elevate", std::string(elevate ? "1" : "0"));
|
||||
} catch (XArch &e) {
|
||||
LOG((CLOG_ERR "failed to save elevate setting: %s", e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void DaemonApp::setCommand(const QString &command)
|
||||
{
|
||||
LOG_DEBUG("service command updated");
|
||||
m_command = command.toStdString();
|
||||
|
||||
try {
|
||||
// saves setting for next time the daemon starts.
|
||||
ARCH->setting("Command", command.toStdString());
|
||||
} catch (XArch &e) {
|
||||
LOG((CLOG_ERR "failed to save command setting: %s", e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void DaemonApp::handleRestartRequested()
|
||||
void DaemonApp::restartCoreProcess()
|
||||
{
|
||||
LOG_DEBUG("service restart requested");
|
||||
|
||||
#if SYSAPI_WIN32
|
||||
m_watchdog->setCommand(m_command, m_elevateMode);
|
||||
m_watchdog->setCommand(m_command, m_elevate);
|
||||
#else
|
||||
LOG_ERR("restart not implemented on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
void DaemonApp::init(IEventQueue *events, int argc, char **argv) // NOSONAR
|
||||
DaemonApp::InitResult DaemonApp::init(IEventQueue *events, int argc, char **argv) // NOSONAR - CLI args
|
||||
{
|
||||
using enum InitResult;
|
||||
|
||||
if (events == nullptr) {
|
||||
throw XDeskflow("event queue not set");
|
||||
}
|
||||
@ -168,26 +189,30 @@ void DaemonApp::init(IEventQueue *events, int argc, char **argv) // NOSONAR
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
string arg(argv[i]);
|
||||
|
||||
if (arg == "-f") {
|
||||
if (arg == "-h" || arg == "--help") {
|
||||
showHelp(argc, argv);
|
||||
return ShowHelp;
|
||||
} else if (arg == "-f" || arg == "--foreground") {
|
||||
m_foreground = true;
|
||||
}
|
||||
#if SYSAPI_WIN32
|
||||
else if (arg == "--install" || arg == "/install") {
|
||||
LOG((CLOG_NOTE "installing windows daemon"));
|
||||
ARCH->installDaemon();
|
||||
Q_EMIT serviceInstalled();
|
||||
return Installed;
|
||||
} else if (arg == "--uninstall" || arg == "/uninstall") {
|
||||
LOG((CLOG_NOTE "uninstalling windows daemon"));
|
||||
isUninstalling = true;
|
||||
ARCH->uninstallDaemon();
|
||||
Q_EMIT serviceUninstalled();
|
||||
return Uninstalled;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
stringstream ss;
|
||||
ss << "Unrecognized argument: " << arg;
|
||||
foregroundError(ss.str().c_str());
|
||||
Q_EMIT fatalErrorOccurred();
|
||||
handleError(ss.str().c_str());
|
||||
showHelp(argc, argv);
|
||||
return ArgsError;
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,6 +223,9 @@ void DaemonApp::init(IEventQueue *events, int argc, char **argv) // NOSONAR
|
||||
CLOG->insert(new MSWindowsDebugOutputter()); // NOSONAR - Adopted by `Log`
|
||||
#endif
|
||||
}
|
||||
|
||||
return StartDaemon;
|
||||
|
||||
} catch (XArch &e) {
|
||||
std::string message = e.what();
|
||||
if (isUninstalling && (message.find("The service has not been started") != std::string::npos)) {
|
||||
@ -207,16 +235,15 @@ void DaemonApp::init(IEventQueue *events, int argc, char **argv) // NOSONAR
|
||||
// horribly wrong, but it's just the service manager reporting a false
|
||||
// positive (the service has actually shut down in most cases).
|
||||
} else {
|
||||
foregroundError(message.c_str());
|
||||
handleError(message.c_str());
|
||||
}
|
||||
Q_EMIT fatalErrorOccurred();
|
||||
} catch (std::exception &e) {
|
||||
foregroundError(e.what());
|
||||
Q_EMIT fatalErrorOccurred();
|
||||
handleError(e.what());
|
||||
} catch (...) {
|
||||
foregroundError("Unrecognized error.");
|
||||
Q_EMIT fatalErrorOccurred();
|
||||
handleError("Unrecognized error.");
|
||||
}
|
||||
|
||||
return FatalError;
|
||||
}
|
||||
|
||||
void DaemonApp::mainLoop(bool logToFile, bool foreground)
|
||||
@ -268,12 +295,14 @@ void DaemonApp::mainLoop(bool logToFile, bool foreground)
|
||||
}
|
||||
}
|
||||
|
||||
void DaemonApp::foregroundError(const char *message)
|
||||
void DaemonApp::handleError(const char *message)
|
||||
{
|
||||
// Always print error to stdout in case run as CLI program.
|
||||
LOG_ERR("%s", message);
|
||||
|
||||
#if SYSAPI_WIN32
|
||||
MessageBoxA(NULL, message, "Deskflow Service", MB_OK | MB_ICONERROR);
|
||||
#elif SYSAPI_UNIX
|
||||
cerr << message << endl;
|
||||
// Show a message box for when run from MSI in Win32 subsystem.
|
||||
MessageBoxA(nullptr, message, "Deskflow daemon error", MB_OK | MB_ICONERROR);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Deskflow -- mouse and keyboard sharing utility
|
||||
* SPDX-FileCopyrightText: (C) 2012 Symless Ltd.
|
||||
* SPDX-FileCopyrightText: (C) 2012 - 2025 Symless Ltd.
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||
*/
|
||||
|
||||
@ -37,9 +37,23 @@ class DaemonApp : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
void init(IEventQueue *events, int argc, char **argv);
|
||||
enum class InitResult
|
||||
{
|
||||
Installed,
|
||||
Uninstalled,
|
||||
StartDaemon,
|
||||
ShowHelp,
|
||||
ArgsError,
|
||||
FatalError,
|
||||
};
|
||||
|
||||
InitResult init(IEventQueue *events, int argc, char **argv);
|
||||
void run();
|
||||
void mainLoop(bool logToFile, bool foreground = false);
|
||||
void restartCoreProcess();
|
||||
void saveLogLevel(const QString &logLevel) const;
|
||||
void setElevate(bool elevate);
|
||||
void setCommand(const QString &command);
|
||||
|
||||
static DaemonApp &instance()
|
||||
{
|
||||
@ -47,25 +61,14 @@ public:
|
||||
return instance;
|
||||
}
|
||||
|
||||
signals:
|
||||
void mainLoopFinished();
|
||||
void fatalErrorOccurred();
|
||||
void serviceInstalled();
|
||||
void serviceUninstalled();
|
||||
|
||||
private:
|
||||
explicit DaemonApp();
|
||||
~DaemonApp() override;
|
||||
|
||||
void daemonize();
|
||||
void foregroundError(const char *message);
|
||||
void handleError(const char *message);
|
||||
std::string logFilename();
|
||||
void handleIpcMessage(const Event &, void *);
|
||||
|
||||
private slots:
|
||||
void handleElevateModeChanged(int mode);
|
||||
void handleCommandChanged(const QString &command);
|
||||
void handleRestartRequested();
|
||||
void handleIpcMessage(const Event &e, void *);
|
||||
|
||||
#if SYSAPI_WIN32
|
||||
std::unique_ptr<MSWindowsWatchdog> m_watchdog;
|
||||
@ -77,6 +80,6 @@ private:
|
||||
FileLogOutputter *m_fileLogOutputter = nullptr;
|
||||
deskflow::core::ipc::DaemonIpcServer *m_ipcServer = nullptr;
|
||||
std::string m_command = "";
|
||||
int m_elevateMode = 0;
|
||||
bool m_elevate = false;
|
||||
bool m_foreground = false;
|
||||
};
|
||||
|
||||
@ -100,15 +100,27 @@ void DaemonIpcServer::processMessage(QLocalSocket *clientSocket, const QString &
|
||||
LOG_DEBUG("ipc server got message: %s", message.toUtf8().constData());
|
||||
if (message == "hello") {
|
||||
clientSocket->write("hello\n");
|
||||
} else if (message == "noop") {
|
||||
clientSocket->write(kAckMessage);
|
||||
} else if (message.startsWith("logLevel=")) {
|
||||
const auto logLevel = message.split('=')[1];
|
||||
if (logLevel.isEmpty()) {
|
||||
LOG_ERR("ipc server got empty log level");
|
||||
clientSocket->write(kErrorMessage);
|
||||
} else {
|
||||
LOG_DEBUG("ipc server got new log level: %s", logLevel.toUtf8().constData());
|
||||
Q_EMIT logLevelChanged(logLevel);
|
||||
clientSocket->write(kAckMessage);
|
||||
}
|
||||
} else if (message.startsWith("elevate=")) {
|
||||
const auto elevateMode = message.split('=')[1].toInt();
|
||||
if (elevateMode < 0 || elevateMode > 2) {
|
||||
LOG_ERR("ipc server got invalid elevate mode: %d", elevateMode);
|
||||
const auto elevateMode = message.split('=')[1];
|
||||
if (elevateMode != "yes" && elevateMode != "no") {
|
||||
LOG_ERR("ipc server got invalid elevate value: %s", elevateMode.toUtf8().constData());
|
||||
clientSocket->write(kErrorMessage);
|
||||
return;
|
||||
} else {
|
||||
LOG_DEBUG("ipc server got new elevate mode: %d", elevateMode);
|
||||
Q_EMIT elevateModeChanged(elevateMode);
|
||||
LOG_DEBUG("ipc server got new elevate value: %s", elevateMode.toUtf8().constData());
|
||||
Q_EMIT elevateModeChanged(elevateMode == "yes");
|
||||
clientSocket->write(kAckMessage);
|
||||
}
|
||||
} else if (message.startsWith("command=")) {
|
||||
|
||||
@ -23,7 +23,8 @@ public:
|
||||
~DaemonIpcServer();
|
||||
|
||||
signals:
|
||||
void elevateModeChanged(int mode);
|
||||
void logLevelChanged(const QString &logLevel);
|
||||
void elevateModeChanged(bool elevate);
|
||||
void commandChanged(const QString &command);
|
||||
void restartRequested();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user