feat: Implement basic IPC connection between Daemon and GUI

This commit is contained in:
Nick Bolton
2025-02-06 12:03:51 +00:00
parent c0094554b1
commit 0d2d9f385d
17 changed files with 219 additions and 218 deletions

View File

@ -12,23 +12,15 @@
#include "base/Log.h"
#if SYSAPI_WIN32
#include "arch/win32/ArchMiscWindows.h"
#endif
#ifdef SYSAPI_UNIX
int main(int argc, char **argv)
{
DaemonApp app;
return app.run(argc, argv);
}
#elif SYSAPI_WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
#endif
int main(int argc, char **argv)
{
#if SYSAPI_WIN32
// win32 instance needed for threading, etc.
@ -45,4 +37,11 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
return DaemonApp::exec();
}
#if SYSAPI_WIN32
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
return main(__argc, __argv);
}
#endif

View File

@ -21,6 +21,7 @@
#include "gui/constants.h"
#include "gui/core/CoreProcess.h"
#include "gui/diagnostic.h"
#include "gui/ipc/DaemonIpcClient.h"
#include "gui/messages.h"
#include "gui/string_utils.h"
#include "gui/style_utils.h"
@ -70,6 +71,7 @@ MainWindow::MainWindow(ConfigScopes &configScopes, AppConfig &appConfig)
m_tlsUtility(appConfig),
m_trayIcon{new QSystemTrayIcon(this)},
m_guiDupeChecker{new QLocalServer(this)},
m_daemonIpcClient{new ipc::DaemonIpcClient(this)},
m_lblSecurityStatus{new QLabel(this)},
m_lblStatus{new QLabel(this)},
m_btnFingerprint{new QToolButton(this)},
@ -167,6 +169,8 @@ MainWindow::MainWindow(ConfigScopes &configScopes, AppConfig &appConfig)
regenerateLocalFingerprints();
}
}
m_daemonIpcClient->connect();
}
MainWindow::~MainWindow()

View File

@ -54,6 +54,10 @@ namespace Ui {
class MainWindow;
}
namespace deskflow::gui::ipc {
class DaemonIpcClient;
}
class MainWindow : public QMainWindow
{
using CoreMode = deskflow::gui::CoreProcess::Mode;
@ -208,6 +212,7 @@ private:
QStringList m_checkedServers;
QSystemTrayIcon *m_trayIcon = nullptr;
QLocalServer *m_guiDupeChecker = nullptr;
deskflow::gui::ipc::DaemonIpcClient *m_daemonIpcClient = nullptr;
QLabel *m_lblSecurityStatus = nullptr;
QLabel *m_lblStatus = nullptr;

View File

@ -1,7 +1,7 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2024 Chris Rizzitello <sithlord48@gmail.com>
* SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd.
* SPDX-FileCopyrightText: (C) 2012 - 2025 Symless Ltd.
* SPDX-FileCopyrightText: (C) 2002 Chris Schoeneman
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
@ -13,10 +13,11 @@ const auto kAppId = "@CMAKE_PROJECT_NAME@";
const auto kAppDescription = "@CMAKE_PROJECT_DESCRIPTION@";
const auto kVersion = "@CMAKE_PROJECT_VERSION@";
const auto kVersionGitSha = "@GIT_SHA_SHORT@";
const auto kDaemonIpcName = "@CMAKE_PROJECT_NAME@-daemon";
const auto kCopyright = //
"Copyright @CMAKE_PROJECT_COPYRIGHT@\n"
"Copyright (C) 2012-2024 Symless Ltd.\n"
"Copyright (C) 2012-2025 Symless Ltd.\n"
"Copyright (C) 2009-2012 Nick Bolton\n"
"Copyright (C) 2002-2009 Chris Schoeneman";
@ -26,7 +27,6 @@ const auto kDebugBuild = true;
const auto kDebugBuild = false;
#endif
const auto kSslDir = "tls";
const auto kTlsDbSize = 2;
const auto kCertificateFilename = "@CMAKE_PROJECT_NAME@.pem";

View File

@ -145,8 +145,8 @@ add_library(${lib_name} STATIC ${PLATFORM_CODE}
XScreen.h
languages/LanguageManager.cpp
languages/LanguageManager.h
ipc/IpcServer2.cpp
ipc/IpcServer2.h
ipc/DaemonIpcServer.cpp
ipc/DaemonIpcServer.h
)
find_package(Qt6 COMPONENTS Core Network)

View File

@ -13,16 +13,15 @@
#include "base/Log.h"
#include "base/TMethodEventJob.h"
#include "base/log_outputters.h"
#include "common/constants.h"
#include "common/ipc.h"
#include "deskflow/App.h"
#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"
#include "ipc/IpcServer2.h"
#include "ipc/IpcSettingMessage.h"
#include "net/SocketMultiplexer.h"
@ -48,6 +47,7 @@
#include <string>
using namespace std;
using namespace deskflow::core;
const char *const kLogFilename = "deskflow-daemon.log";
@ -99,13 +99,8 @@ int winMainLoopStatic(int, const char **)
DaemonApp::DaemonApp(IEventQueue *events, int argc, char **argv)
: QCoreApplication(argc, argv),
m_events(events),
m_ipcServer2{new deskflow::ipc::IpcServer2(this)}
m_ipcServer{new ipc::DaemonIpcServer(this)}
{
// HACK: init used to be run, which was the main loop.
// now it's used for arg parsing, install/uninstall, etc.
if (init(argc, argv) != kExitSuccess) {
exit(kExitFailed);
}
s_instance = this;
}
@ -197,29 +192,11 @@ void DaemonApp::mainLoop(bool logToFile, bool foreground)
CLOG->insert(m_fileLogOutputter);
}
// create socket multiplexer. this must happen after daemonization
// on unix because threads evaporate across a fork().
SocketMultiplexer multiplexer;
// uses event queue, must be created here.
m_ipcServer = std::make_unique<IpcServer>(m_events, &multiplexer);
// send logging to gui via ipc, log system adopts outputter.
m_ipcLogOutputter = std::make_unique<IpcLogOutputter>(*m_ipcServer, IpcClientType::GUI, true);
CLOG->insert(m_ipcLogOutputter.get());
#if SYSAPI_WIN32
m_watchdog = std::make_unique<MSWindowsWatchdog>(false, *m_ipcServer, *m_ipcLogOutputter, foreground);
m_watchdog = std::make_unique<MSWindowsWatchdog>(false, foreground);
m_watchdog->setFileLogOutputter(m_fileLogOutputter);
#endif
m_events->adoptHandler(
m_events->forIpcServer().messageReceived(), m_ipcServer.get(),
new TMethodEventJob<DaemonApp>(this, &DaemonApp::handleIpcMessage)
);
m_ipcServer->listen();
#if SYSAPI_WIN32
// install the platform event queue to handle service stop events.
@ -273,113 +250,3 @@ std::string DaemonApp::logFilename()
return logFilename;
}
void DaemonApp::handleIpcMessage(const Event &e, void *)
{
IpcMessage *m = static_cast<IpcMessage *>(e.getDataObject());
switch (m->type()) {
case IpcMessageType::Command: {
IpcCommandMessage *cm = static_cast<IpcCommandMessage *>(m);
std::string command = cm->command();
// if empty quotes, clear.
if (command == "\"\"") {
command.clear();
}
if (!command.empty()) {
LOG((CLOG_DEBUG "daemon got new core command"));
LOG((CLOG_DEBUG2 "new command, elevate=%d command=%s", cm->elevate(), command.c_str()));
std::vector<std::string> argsArray;
ArgParser::splitCommandString(command, argsArray);
ArgParser argParser(NULL);
const char **argv = argParser.getArgv(argsArray);
int argc = static_cast<int>(argsArray.size());
if (isServerCommandLine(argsArray)) {
auto serverArgs = new deskflow::ServerArgs();
argParser.parseServerArgs(*serverArgs, argc, argv);
} else {
auto clientArgs = new deskflow::ClientArgs();
argParser.parseClientArgs(*clientArgs, argc, argv);
}
delete[] argv;
std::string logLevel(ArgParser::argsBase().m_logFilter);
if (!logLevel.empty()) {
try {
// change log level based on that in the command string
// and change to that log level now.
ARCH->setting("LogLevel", logLevel);
CLOG->setFilter(logLevel.c_str());
} catch (XArch &e) {
LOG((CLOG_ERR "failed to save LogLevel setting, %s", e.what()));
}
}
} else {
LOG((CLOG_DEBUG "empty command, elevate=%d", cm->elevate()));
}
try {
// store command in system settings. this is used when the daemon
// next starts.
ARCH->setting("Command", command);
// TODO: it would be nice to store bools/ints...
ARCH->setting("Elevate", std::string(cm->elevate() ? "1" : "0"));
} catch (XArch &e) {
LOG((CLOG_ERR "failed to save settings, %s", e.what()));
}
#if SYSAPI_WIN32
// tell the relauncher about the new command. this causes the
// relauncher to stop the existing command and start the new
// command.
m_watchdog->setCommand(command, cm->elevate());
#endif
break;
}
case IpcMessageType::Hello: {
IpcHelloMessage *hm = static_cast<IpcHelloMessage *>(m);
std::string type;
switch (hm->clientType()) {
case IpcClientType::GUI:
type = "gui";
break;
case IpcClientType::Node:
type = "node";
break;
default:
type = "unknown";
break;
}
LOG((CLOG_DEBUG "ipc hello, type=%s", type.c_str()));
// TODO: implement hello back handling in s1 gui and node (server/client).
if (hm->clientType() == IpcClientType::GUI) {
LOG((CLOG_DEBUG "sending ipc hello back"));
IpcHelloBackMessage hbm;
m_ipcServer->send(hbm, hm->clientType());
}
#if SYSAPI_WIN32
std::string watchdogStatus = m_watchdog->isProcessRunning() ? "active" : "idle";
LOG((CLOG_INFO "service status: %s", watchdogStatus.c_str()));
#endif
m_ipcLogOutputter->notifyBuffer();
break;
}
case IpcMessageType::Setting:
updateSetting(*m);
break;
default:
LOG((CLOG_DEBUG "ipc message ignored"));
break;
}
}

View File

@ -6,7 +6,7 @@
#pragma once
#include "ipc/IpcServer.h"
#include "common/common.h"
#include <memory>
#include <string>
@ -14,12 +14,13 @@
#include <QCoreApplication>
class Event;
class IEventQueue;
class IpcLogOutputter;
class FileLogOutputter;
class QLocalServer;
namespace deskflow::ipc {
class IpcServer2;
namespace deskflow::core::ipc {
class DaemonIpcServer;
}
#if SYSAPI_WIN32
@ -33,11 +34,11 @@ class DaemonApp : public QCoreApplication
public:
DaemonApp(IEventQueue *events, int argc, char **argv);
~DaemonApp();
int init(int argc, char **argv);
void startAsync();
void mainLoop(bool logToFile, bool foreground = false);
private:
int init(int argc, char **argv);
void daemonize();
void foregroundError(const char *message);
std::string logFilename();
@ -51,9 +52,8 @@ public:
#endif
private:
std::unique_ptr<IpcServer> m_ipcServer;
std::unique_ptr<IpcLogOutputter> m_ipcLogOutputter;
IEventQueue *m_events = nullptr;
FileLogOutputter *m_fileLogOutputter = nullptr;
std::unique_ptr<deskflow::ipc::IpcServer2> m_ipcServer2;
std::unique_ptr<deskflow::core::ipc::DaemonIpcServer> m_ipcServer;
};

View File

@ -0,0 +1,69 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "DaemonIpcServer.h"
#include "base/Log.h"
#include "common/constants.h"
#include <QLocalServer>
#include <QLocalSocket>
namespace deskflow::core::ipc {
DaemonIpcServer::DaemonIpcServer(QObject *parent) : QObject(parent), m_server{new QLocalServer(this)}
{
// Daemon runs as system, but GUI runs as regular user, so we need to allow world access.
m_server->setSocketOptions(QLocalServer::WorldAccessOption);
connect(m_server, &QLocalServer::newConnection, this, &DaemonIpcServer::handleNewConnection);
m_server->removeServer(kDaemonIpcName);
if (m_server->listen(kDaemonIpcName)) {
LOG_DEBUG("ipc server listening on: %s", kDaemonIpcName);
} else {
LOG_ERR("ipc server failed to listen on: %s", kDaemonIpcName);
}
}
DaemonIpcServer::~DaemonIpcServer()
{
m_server->close();
}
void DaemonIpcServer::handleNewConnection()
{
QLocalSocket *clientSocket = m_server->nextPendingConnection();
if (!clientSocket) {
LOG_ERR("ipc server failed to get new connection");
return;
}
LOG_DEBUG("ipc server got new connection");
connect(clientSocket, &QLocalSocket::readyRead, this, [clientSocket]() {
LOG_DEBUG("ipc server ready to read data");
QByteArray data = clientSocket->readAll();
if (data.isEmpty()) {
LOG_WARN("ipc server got empty message");
return;
}
QString dataStr = QString::fromUtf8(data);
if (dataStr.isEmpty()) {
LOG_ERR("ipc server failed to convert message to string");
return;
}
if (dataStr == "hello") {
LOG_DEBUG("ipc server got message: %s", data.constData());
clientSocket->write("hello");
clientSocket->flush();
}
});
}
} // namespace deskflow::core::ipc

View File

@ -10,18 +10,21 @@
class QLocalServer;
namespace deskflow::ipc {
namespace deskflow::core::ipc {
class IpcServer2 : public QObject
class DaemonIpcServer : public QObject
{
Q_OBJECT
public:
IpcServer2(QObject *parent);
~IpcServer2();
DaemonIpcServer(QObject *parent);
~DaemonIpcServer();
private slots:
void handleNewConnection();
private:
QLocalServer *m_server;
};
} // namespace deskflow::ipc
} // namespace deskflow::core::ipc

View File

@ -1,29 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "IpcServer2.h"
#include "base/Log.h"
#include <QLocalServer>
namespace deskflow::ipc {
const auto kServerName = QStringLiteral("deskflow-daemon");
IpcServer2::IpcServer2(QObject *parent) : QObject(parent), m_server{new QLocalServer(this)}
{
m_server->removeServer(kServerName);
m_server->listen(kServerName);
LOG_INFO("ipc server listening on %s", kServerName.toStdString().c_str());
}
IpcServer2::~IpcServer2()
{
m_server->close();
}
} // namespace deskflow::ipc

View File

@ -69,6 +69,8 @@ add_library(${target} STATIC
ipc/QDataStreamProxy.h
ipc/QIpcClient.cpp
ipc/QIpcClient.h
ipc/DaemonIpcClient.cpp
ipc/DaemonIpcClient.h
proxy/QNetworkAccessManagerProxy.cpp
proxy/QNetworkAccessManagerProxy.h
proxy/QProcessProxy.cpp

View File

@ -0,0 +1,66 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "DaemonIpcClient.h"
#include "common/constants.h"
#include <QDebug>
#include <QLocalSocket>
#include <QObject>
namespace deskflow::gui::ipc {
const auto kTimeout = 1000;
DaemonIpcClient::DaemonIpcClient(QObject *parent) : QObject(parent), socket{new QLocalSocket(this)}
{
}
DaemonIpcClient::~DaemonIpcClient()
{
}
void DaemonIpcClient::connect()
{
socket->connectToServer(kDaemonIpcName);
if (!socket->waitForConnected(kTimeout)) {
qCritical() << "ipc client failed to connect to server:" << kDaemonIpcName;
return;
}
socket->write("hello");
if (!socket->waitForBytesWritten(kTimeout)) {
qCritical() << "ipc client failed to write message";
return;
}
if (!socket->waitForReadyRead(kTimeout)) {
qCritical() << "ipc client failed to read response";
return;
}
QByteArray response = socket->readAll();
if (response.isEmpty()) {
qCritical() << "ipc client got empty response";
return;
}
QString responseData = QString::fromUtf8(response);
if (responseData.isEmpty()) {
qCritical() << "ipc client failed to convert response to string";
return;
}
if (responseData != "hello") {
qCritical() << "ipc client got unexpected response: " << responseData;
return;
}
qInfo() << "ipc client connected to server:" << kDaemonIpcName;
}
} // namespace deskflow::gui::ipc

View File

@ -0,0 +1,28 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#pragma once
#include <QObject>
class QLocalSocket;
namespace deskflow::gui::ipc {
class DaemonIpcClient : public QObject
{
Q_OBJECT
public:
DaemonIpcClient(QObject *parent = nullptr);
~DaemonIpcClient();
void connect();
private:
QLocalSocket *socket;
};
} // namespace deskflow::gui::ipc

View File

@ -113,12 +113,12 @@ DWORD MSWindowsProcess::waitForExit()
return exitCode;
}
void MSWindowsProcess::shutdown(IpcServer &ipcServer, int timeout)
void MSWindowsProcess::shutdown(int timeout)
{
shutdown(m_info.hProcess, m_info.dwProcessId, ipcServer, timeout);
shutdown(m_info.hProcess, m_info.dwProcessId, timeout);
}
void MSWindowsProcess::shutdown(HANDLE handle, DWORD pid, IpcServer &ipcServer, int timeout)
void MSWindowsProcess::shutdown(HANDLE handle, DWORD pid, int timeout)
{
LOG_DEBUG("shutting down process %d", pid);
@ -129,9 +129,6 @@ void MSWindowsProcess::shutdown(HANDLE handle, DWORD pid, IpcServer &ipcServer,
return;
}
IpcShutdownMessage shutdown;
ipcServer.send(shutdown, IpcClientType::Node);
// wait for process to exit gracefully.
double start = ARCH->time();
while (true) { // NOSONAR -- Multiple breaks necessary

View File

@ -6,8 +6,6 @@
#pragma once
#include "ipc/IpcServer.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
@ -27,7 +25,7 @@ public:
BOOL startInForeground();
BOOL startAsUser(HANDLE userToken, LPSECURITY_ATTRIBUTES sa);
void shutdown(IpcServer &ipcServer, int timeout = kDefaultShutdownTimeout);
void shutdown(int timeout = kDefaultShutdownTimeout);
DWORD waitForExit();
void createPipes();
std::string readStdOutput();
@ -38,7 +36,7 @@ public:
return m_info;
}
static void shutdown(HANDLE handle, DWORD pid, IpcServer &ipcServer, int timeout = kDefaultShutdownTimeout);
static void shutdown(HANDLE handle, DWORD pid, int timeout = kDefaultShutdownTimeout);
private:
void setStartupInfo(STARTUPINFO &si);

View File

@ -57,17 +57,13 @@ typedef VOID(WINAPI *SendSas)(BOOL asUser);
const char g_activeDesktop[] = {"activeDesktop:"};
MSWindowsWatchdog::MSWindowsWatchdog(
bool autoDetectCommand, IpcServer &ipcServer, IpcLogOutputter &ipcLogOutputter, bool foreground
)
MSWindowsWatchdog::MSWindowsWatchdog(bool autoDetectCommand, bool foreground)
: m_thread(NULL),
m_autoDetectCommand(autoDetectCommand),
m_monitoring(true),
m_commandChanged(false),
m_outputWritePipe(nullptr),
m_outputReadPipe(nullptr),
m_ipcServer(ipcServer),
m_ipcLogOutputter(ipcLogOutputter),
m_elevateProcess(false),
m_processFailures(0),
m_processStarted(false),
@ -265,7 +261,7 @@ void MSWindowsWatchdog::mainLoop(void *)
if (m_process != nullptr) {
LOG((CLOG_DEBUG "terminated running process on exit"));
m_process->shutdown(m_ipcServer);
m_process->shutdown();
m_process.reset();
m_processStarted = false;
}
@ -301,7 +297,7 @@ void MSWindowsWatchdog::startProcess()
if (m_process != nullptr) {
LOG((CLOG_DEBUG "closing existing process to make way for new one"));
m_process->shutdown(m_ipcServer);
m_process->shutdown();
m_process.reset();
m_processStarted = false;
}
@ -409,8 +405,6 @@ void MSWindowsWatchdog::outputLoop(void *)
} else {
buffer[bytesRead] = '\0';
m_ipcLogOutputter.write(kINFO, buffer);
if (m_fileLogOutputter != NULL) {
m_fileLogOutputter->write(kINFO, buffer);
}
@ -433,7 +427,7 @@ void MSWindowsWatchdog::shutdownExistingProcesses()
LOG_INFO("daemon shutting down existing processes");
if (m_process != nullptr) {
m_process->shutdown(m_ipcServer);
m_process->shutdown();
m_process.reset();
m_processStarted = false;
}
@ -468,7 +462,7 @@ void MSWindowsWatchdog::shutdownExistingProcesses()
_stricmp(entry.szExeFile, "deskflow-core.exe") == 0) {
HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
deskflow::platform::MSWindowsProcess::shutdown(handle, entry.th32ProcessID, m_ipcServer);
deskflow::platform::MSWindowsProcess::shutdown(handle, entry.th32ProcessID);
CloseHandle(handle);
}
}

View File

@ -26,7 +26,7 @@ class FileLogOutputter;
class MSWindowsWatchdog
{
public:
MSWindowsWatchdog(bool autoDetectCommand, IpcServer &ipcServer, IpcLogOutputter &ipcLogOutputter, bool foreground);
MSWindowsWatchdog(bool autoDetectCommand, bool foreground);
virtual ~MSWindowsWatchdog();
void startAsync();
@ -65,8 +65,6 @@ private:
HANDLE m_outputWritePipe;
HANDLE m_outputReadPipe;
Thread *m_outputThread;
IpcServer &m_ipcServer;
IpcLogOutputter &m_ipcLogOutputter;
bool m_elevateProcess;
MSWindowsSession m_session;
int m_processFailures;