refactor: Update IPC server to handle log level and elevate mode changes

This commit is contained in:
Nick Bolton
2025-02-07 17:03:26 +00:00
parent 53038760de
commit 7c672b06d8
7 changed files with 162 additions and 88 deletions

View File

@ -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);

View File

@ -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
};

View File

@ -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
*/

View File

@ -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
}

View File

@ -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;
};

View File

@ -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=")) {

View File

@ -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();