Files
deskflow/src/apps/deskflow-daemon/deskflow-daemon.cpp

148 lines
4.2 KiB
C++

/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2012 - 2016, 2025 Symless Ltd.
* SPDX-FileCopyrightText: (C) 2012 Nick Bolton
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "arch/Arch.h"
#include "base/EventQueue.h"
#include "base/Log.h"
#include "common/constants.h"
#include "deskflow/DaemonApp.h"
#include "deskflow/ipc/DaemonIpcServer.h"
#if SYSAPI_WIN32
#include "arch/win32/ArchMiscWindows.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif
#include <QCoreApplication>
#include <QThread>
using namespace deskflow::core;
void handleError(const char *message);
int main(int argc, char **argv)
{
#if SYSAPI_WIN32
ArchMiscWindows::guardRuntimeVersion();
// Save window instance for later use, e.g. `GetModuleFileName` which is used when installing the daemon.
ArchMiscWindows::setInstanceWin32(GetModuleHandle(nullptr));
#endif
Arch arch;
arch.init();
Log log;
EventQueue events;
auto &daemon = DaemonApp::instance();
DaemonApp::InitResult initResult;
try {
initResult = daemon.init(&events, argc, argv);
} catch (std::exception &e) {
handleError(e.what());
return kExitFailed;
} catch (...) {
handleError("Unrecognized error.");
return kExitFailed;
}
// Important: Log the app name and version number to the log file after the daemon app init
// because the file log outputter is created there. Logging before would only log to stdout
// which is not useful for troubleshooting Windows services.
// It's important to write the version number to the log file so we can be certain the old daemon
// was uninstalled, since sometimes Windows services can get stuck and fail to be removed.
LOG_PRINT("%s Daemon v%s", kAppName, kDisplayVersion);
switch (initResult) {
using enum DaemonApp::InitResult;
case StartDaemon: {
LOG_INFO("starting daemon");
QCoreApplication app(argc, argv);
QThread daemonThread;
daemon.moveToThread(&daemonThread);
QObject::connect(&daemonThread, &QThread::started, [&daemon, &daemonThread]() {
LOG_DEBUG("daemon thread started");
daemon.run();
daemonThread.quit();
LOG_DEBUG("daemon thread finished");
});
QObject::connect(&daemonThread, &QThread::finished, &app, &QCoreApplication::quit);
ipc::DaemonIpcServer ipcServer(&app, QString::fromStdString(daemon.logFilename()));
// Use direct connection as the daemon app is on it's own thread, and so is on a different event loop.
QObject::connect(
&ipcServer, &ipc::DaemonIpcServer::logLevelChanged, &daemon, &DaemonApp::saveLogLevel, //
Qt::DirectConnection
);
QObject::connect(
&ipcServer, &ipc::DaemonIpcServer::elevateModeChanged, &daemon, &DaemonApp::setElevate, //
Qt::DirectConnection
);
QObject::connect(
&ipcServer, &ipc::DaemonIpcServer::commandChanged, &daemon, &DaemonApp::setCommand, //
Qt::DirectConnection
);
QObject::connect(
&ipcServer, &ipc::DaemonIpcServer::startProcessRequested, &daemon, &DaemonApp::applyWatchdogCommand, //
Qt::DirectConnection
);
QObject::connect(
&ipcServer, &ipc::DaemonIpcServer::stopProcessRequested, &daemon, &DaemonApp::clearWatchdogCommand, //
Qt::DirectConnection
);
QObject::connect(
&ipcServer, &ipc::DaemonIpcServer::clearSettingsRequested, &daemon, &DaemonApp::clearSettings, //
Qt::DirectConnection
);
daemonThread.start();
const auto exitCode = QCoreApplication::exec();
daemonThread.wait();
LOG_DEBUG("daemon exited, code: %d", exitCode);
return exitCode;
}
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);
}
#endif
void handleError(const char *message)
{
// Always print error to stdout in case run as CLI program.
LOG_ERR("%s", message);
#if SYSAPI_WIN32
// Show a message box for when run from MSI in Win32 subsystem.
MessageBoxA(nullptr, message, "Deskflow daemon error", MB_OK | MB_ICONERROR);
#endif
}