From 62ccb543dcfe9c47dde20202b12ca65cbc5247e8 Mon Sep 17 00:00:00 2001 From: Nick Bolton Date: Fri, 5 Dec 2025 17:45:09 +0000 Subject: [PATCH] feat(ipc): Core IPC server and connected client --- src/apps/deskflow-core/deskflow-core.cpp | 4 ++++ src/lib/common/Constants.h.in | 1 + src/lib/deskflow/CMakeLists.txt | 2 ++ src/lib/deskflow/ipc/CoreIpcServer.cpp | 28 +++++++++++++++++++++++ src/lib/deskflow/ipc/CoreIpcServer.h | 29 ++++++++++++++++++++++++ src/lib/deskflow/ipc/DaemonIpcServer.cpp | 27 ++++------------------ src/lib/deskflow/ipc/DaemonIpcServer.h | 4 ++-- src/lib/deskflow/ipc/IpcServer.cpp | 26 ++++++++++++++++++--- src/lib/deskflow/ipc/IpcServer.h | 3 ++- src/lib/gui/CMakeLists.txt | 2 ++ src/lib/gui/core/CoreProcess.cpp | 22 ++++++++++++++++++ src/lib/gui/core/CoreProcess.h | 2 ++ src/lib/gui/ipc/CoreIpcClient.cpp | 25 ++++++++++++++++++++ src/lib/gui/ipc/CoreIpcClient.h | 23 +++++++++++++++++++ 14 files changed, 170 insertions(+), 28 deletions(-) create mode 100644 src/lib/deskflow/ipc/CoreIpcServer.cpp create mode 100644 src/lib/deskflow/ipc/CoreIpcServer.h create mode 100644 src/lib/gui/ipc/CoreIpcClient.cpp create mode 100644 src/lib/gui/ipc/CoreIpcClient.h diff --git a/src/apps/deskflow-core/deskflow-core.cpp b/src/apps/deskflow-core/deskflow-core.cpp index 3da8edd3d..143b41ccb 100644 --- a/src/apps/deskflow-core/deskflow-core.cpp +++ b/src/apps/deskflow-core/deskflow-core.cpp @@ -15,6 +15,7 @@ #include "common/ExitCodes.h" #include "deskflow/ClientApp.h" #include "deskflow/ServerApp.h" +#include "deskflow/ipc/CoreIpcServer.h" #if defined(Q_OS_WIN) #include "arch/win32/ArchMiscWindows.h" @@ -104,6 +105,9 @@ int main(int argc, char **argv) QCoreApplication app(argc, argv); QCoreApplication::setApplicationName(QStringLiteral("%1 Core").arg(kAppName)); + const auto ipcServer = new deskflow::core::ipc::CoreIpcServer(&app); // NOSONAR - Qt managed + ipcServer->listen(); + QThread coreThread; QObject::connect(&coreThread, &QThread::finished, &app, &QCoreApplication::quit); coreApp->run(coreThread); diff --git a/src/lib/common/Constants.h.in b/src/lib/common/Constants.h.in index 7d9545973..1aa97f1c0 100644 --- a/src/lib/common/Constants.h.in +++ b/src/lib/common/Constants.h.in @@ -21,6 +21,7 @@ const auto kCopyright = // "Copyright (C) 2002-2009 Chris Schoeneman"; const auto kCoreBinName = "@CORE_BINARY@"; +const auto kCoreIpcName = "@CMAKE_PROJECT_NAME@-core"; #ifdef _WIN32 diff --git a/src/lib/deskflow/CMakeLists.txt b/src/lib/deskflow/CMakeLists.txt index e224ab28f..97021968e 100644 --- a/src/lib/deskflow/CMakeLists.txt +++ b/src/lib/deskflow/CMakeLists.txt @@ -83,6 +83,8 @@ add_library(${lib_name} STATIC ${PLATFORM_CODE} ipc/IpcServer.h ipc/DaemonIpcServer.cpp ipc/DaemonIpcServer.h + ipc/CoreIpcServer.cpp + ipc/CoreIpcServer.h ) target_link_libraries(${lib_name} PUBLIC common Qt6::Core Qt6::Network) diff --git a/src/lib/deskflow/ipc/CoreIpcServer.cpp b/src/lib/deskflow/ipc/CoreIpcServer.cpp new file mode 100644 index 000000000..c67363070 --- /dev/null +++ b/src/lib/deskflow/ipc/CoreIpcServer.cpp @@ -0,0 +1,28 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2025-2026 Symless Ltd. + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#include "CoreIpcServer.h" + +#include "base/Log.h" +#include "common/Constants.h" + +#include + +namespace deskflow::core::ipc { + +CoreIpcServer::CoreIpcServer(QObject *parent) : IpcServer(parent, kCoreIpcName) +{ + // do nothing +} + +void CoreIpcServer::processCommand(QLocalSocket *clientSocket, const QString &command, const QStringList &parts) +{ + Q_UNUSED(clientSocket) + Q_UNUSED(parts) + LOG_WARN("core ipc server got unknown command: %s", command.toUtf8().constData()); +} + +} // namespace deskflow::core::ipc diff --git a/src/lib/deskflow/ipc/CoreIpcServer.h b/src/lib/deskflow/ipc/CoreIpcServer.h new file mode 100644 index 000000000..d0f0397b6 --- /dev/null +++ b/src/lib/deskflow/ipc/CoreIpcServer.h @@ -0,0 +1,29 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2025-2026 Symless Ltd. + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#pragma once + +#include "IpcServer.h" + +#include +#include + +class QLocalSocket; + +namespace deskflow::core::ipc { + +class CoreIpcServer : public IpcServer +{ + Q_OBJECT + +public: + explicit CoreIpcServer(QObject *parent); + +private: + void processCommand(QLocalSocket *clientSocket, const QString &command, const QStringList &parts) override; +}; + +} // namespace deskflow::core::ipc diff --git a/src/lib/deskflow/ipc/DaemonIpcServer.cpp b/src/lib/deskflow/ipc/DaemonIpcServer.cpp index 7d6c40f27..366151a41 100644 --- a/src/lib/deskflow/ipc/DaemonIpcServer.cpp +++ b/src/lib/deskflow/ipc/DaemonIpcServer.cpp @@ -23,29 +23,14 @@ DaemonIpcServer::DaemonIpcServer(QObject *parent, const QString &logFilename) // do nothing } -void DaemonIpcServer::processMessage(QLocalSocket *clientSocket, const QString &message) +void DaemonIpcServer::processCommand(QLocalSocket *clientSocket, const QString &command, const QStringList &parts) { - LOG_DEBUG1("daemon ipc server got message: %s", message.toUtf8().constData()); - const auto parts = message.split('='); - if (parts.size() < 1) { - LOG_ERR("daemon ipc server got invalid message: %s", message.toUtf8().constData()); - writeToClientSocket(clientSocket, kErrorMessage); - return; - } - - const auto &command = parts[0]; - if (command == "hello") { // NOSONAR - if-init is confusing here - LOG_DEBUG("daemon ipc server got hello message, sending hello back"); - writeToClientSocket(clientSocket, "hello"); - } else if (command == "noop") { - LOG_DEBUG("daemon ipc server got noop message"); - writeToClientSocket(clientSocket, kAckMessage); - } else if (command == "logLevel") { + if (command == "logLevel") { processLogLevel(clientSocket, parts); } else if (command == "elevate") { processElevate(clientSocket, parts); } else if (command == "command") { - processCommand(clientSocket, parts); + processCommandMessage(clientSocket, parts); } else if (command == "start") { LOG_DEBUG("daemon ipc server got start message"); Q_EMIT startProcessRequested(); @@ -62,10 +47,8 @@ void DaemonIpcServer::processMessage(QLocalSocket *clientSocket, const QString & Q_EMIT clearSettingsRequested(); writeToClientSocket(clientSocket, kAckMessage); } else { - LOG_WARN("daemon ipc server got unknown message: %s", message.toUtf8().constData()); + LOG_WARN("daemon ipc server got unknown command: %s", command.toUtf8().constData()); } - - clientSocket->flush(); } void DaemonIpcServer::processLogLevel(QLocalSocket *&clientSocket, const QStringList &messageParts) @@ -108,7 +91,7 @@ void DaemonIpcServer::processElevate(QLocalSocket *&clientSocket, const QStringL writeToClientSocket(clientSocket, kAckMessage); } -void DaemonIpcServer::processCommand(QLocalSocket *&clientSocket, const QStringList &messageParts) +void DaemonIpcServer::processCommandMessage(QLocalSocket *&clientSocket, const QStringList &messageParts) { if (messageParts.size() < 2) { LOG_ERR("daemon ipc server got invalid command message"); diff --git a/src/lib/deskflow/ipc/DaemonIpcServer.h b/src/lib/deskflow/ipc/DaemonIpcServer.h index f92cdac24..a4d205b5f 100644 --- a/src/lib/deskflow/ipc/DaemonIpcServer.h +++ b/src/lib/deskflow/ipc/DaemonIpcServer.h @@ -23,10 +23,10 @@ public: explicit DaemonIpcServer(QObject *parent, const QString &logFilename); private: - void processMessage(QLocalSocket *clientSocket, const QString &message) override; + void processCommand(QLocalSocket *clientSocket, const QString &command, const QStringList &parts) override; void processLogLevel(QLocalSocket *&clientSocket, const QStringList &messageParts); void processElevate(QLocalSocket *&clientSocket, const QStringList &messageParts); - void processCommand(QLocalSocket *&clientSocket, const QStringList &messageParts); + void processCommandMessage(QLocalSocket *&clientSocket, const QStringList &messageParts); private: const QString m_logFilename; diff --git a/src/lib/deskflow/ipc/IpcServer.cpp b/src/lib/deskflow/ipc/IpcServer.cpp index 93d1011d8..1cf1467af 100644 --- a/src/lib/deskflow/ipc/IpcServer.cpp +++ b/src/lib/deskflow/ipc/IpcServer.cpp @@ -13,9 +13,6 @@ namespace deskflow::core::ipc { -const auto kAckMessage = "ok"; -const auto kErrorMessage = "error"; - IpcServer::IpcServer(QObject *parent, const QString &serverName) : QObject(parent), m_server{new QLocalServer(this)}, // NOSONAR - Qt memory @@ -102,6 +99,29 @@ void IpcServer::handleErrorOccurred() clientSocket->deleteLater(); } +void IpcServer::processMessage(QLocalSocket *clientSocket, const QString &message) +{ + LOG_DEBUG1("ipc server got message: %s", message.toUtf8().constData()); + const auto parts = message.split('='); + if (parts.isEmpty()) { + LOG_ERR("ipc server got invalid message: %s", message.toUtf8().constData()); + writeToClientSocket(clientSocket, "error"); + return; + } + + if (const auto &command = parts.at(0); command == "hello") { + LOG_DEBUG("ipc server got hello message, sending hello back"); + writeToClientSocket(clientSocket, "hello"); + } else if (command == "noop") { + LOG_DEBUG("ipc server got noop message"); + writeToClientSocket(clientSocket, "ok"); + } else { + processCommand(clientSocket, command, parts); + } + + clientSocket->flush(); +} + void IpcServer::writeToClientSocket(QLocalSocket *&clientSocket, const QString &message) const { QByteArray messageData = message.toUtf8() + '\n'; diff --git a/src/lib/deskflow/ipc/IpcServer.h b/src/lib/deskflow/ipc/IpcServer.h index f08cfa712..7f2c8ffc2 100644 --- a/src/lib/deskflow/ipc/IpcServer.h +++ b/src/lib/deskflow/ipc/IpcServer.h @@ -42,7 +42,8 @@ protected: void writeToClientSocket(QLocalSocket *&clientSocket, const QString &message) const; private: - virtual void processMessage(QLocalSocket *clientSocket, const QString &message) = 0; + void processMessage(QLocalSocket *clientSocket, const QString &message); + virtual void processCommand(QLocalSocket *clientSocket, const QString &command, const QStringList &parts) = 0; void handleNewConnection(); void handleReadyRead(); void handleDisconnected(); diff --git a/src/lib/gui/CMakeLists.txt b/src/lib/gui/CMakeLists.txt index 4416920dc..5d46fea13 100644 --- a/src/lib/gui/CMakeLists.txt +++ b/src/lib/gui/CMakeLists.txt @@ -85,6 +85,8 @@ add_library(${target} STATIC ipc/IpcClient.h ipc/DaemonIpcClient.cpp ipc/DaemonIpcClient.h + ipc/CoreIpcClient.cpp + ipc/CoreIpcClient.h validators/AliasValidator.cpp validators/AliasValidator.h validators/ComputerNameValidator.cpp diff --git a/src/lib/gui/core/CoreProcess.cpp b/src/lib/gui/core/CoreProcess.cpp index 1e7a7a2ad..26eed77ad 100644 --- a/src/lib/gui/core/CoreProcess.cpp +++ b/src/lib/gui/core/CoreProcess.cpp @@ -8,6 +8,7 @@ #include "CoreProcess.h" #include "common/ExitCodes.h" +#include "gui/ipc/CoreIpcClient.h" #include "gui/ipc/DaemonIpcClient.h" #if defined(Q_OS_MACOS) @@ -372,6 +373,21 @@ void CoreProcess::start(std::optional processModeOption) startProcessFromDaemon(args); } + // Don't block the main GUI render thread when connecting to the Core IPC server. + QTimer::singleShot(kRetryDelay, this, [this] { + if (m_processState != ProcessState::Started) { + qWarning("core process failed to start, skipping core ipc connection"); + return; + } + + m_coreIpcClient = new ipc::CoreIpcClient(this); + if (m_coreIpcClient->connectToServer()) { + qInfo("connected to core ipc server"); + } else { + qWarning("failed to establish core ipc connection"); + } + }); + m_lastProcessMode = processMode; } @@ -384,6 +400,12 @@ void CoreProcess::stop(std::optional processModeOption) qInfo("stopping core process (%s mode)", qPrintable(processModeToString(processMode))); + if (m_coreIpcClient) { + m_coreIpcClient->disconnectFromServer(); + delete m_coreIpcClient; + m_coreIpcClient = nullptr; + } + if (m_processState == ProcessState::Starting) { qDebug("core process is starting, cancelling"); setProcessState(ProcessState::Stopped); diff --git a/src/lib/gui/core/CoreProcess.h b/src/lib/gui/core/CoreProcess.h index 71632010f..4db375da9 100644 --- a/src/lib/gui/core/CoreProcess.h +++ b/src/lib/gui/core/CoreProcess.h @@ -20,6 +20,7 @@ namespace deskflow::gui { namespace ipc { +class CoreIpcClient; class DaemonIpcClient; } @@ -128,6 +129,7 @@ private: std::optional m_lastProcessMode = std::nullopt; QTimer m_retryTimer; int m_connections = 0; + deskflow::gui::ipc::CoreIpcClient *m_coreIpcClient = nullptr; deskflow::gui::ipc::DaemonIpcClient *m_daemonIpcClient = nullptr; FileTail *m_daemonFileTail = nullptr; QProcess *m_process = nullptr; diff --git a/src/lib/gui/ipc/CoreIpcClient.cpp b/src/lib/gui/ipc/CoreIpcClient.cpp new file mode 100644 index 000000000..309c762af --- /dev/null +++ b/src/lib/gui/ipc/CoreIpcClient.cpp @@ -0,0 +1,25 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2025-2026 Symless Ltd. + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#include "CoreIpcClient.h" + +#include "common/Constants.h" + +#include +#include +#include +#include + +namespace deskflow::gui::ipc { + +const auto kTimeout = 1000; + +CoreIpcClient::CoreIpcClient(QObject *parent) : IpcClient(parent, kCoreIpcName) +{ + // do nothing +} + +} // namespace deskflow::gui::ipc diff --git a/src/lib/gui/ipc/CoreIpcClient.h b/src/lib/gui/ipc/CoreIpcClient.h new file mode 100644 index 000000000..eb412ef59 --- /dev/null +++ b/src/lib/gui/ipc/CoreIpcClient.h @@ -0,0 +1,23 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2025-2026 Symless Ltd. + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#pragma once + +#include "IpcClient.h" + +#include + +namespace deskflow::gui::ipc { + +class CoreIpcClient : public IpcClient +{ + Q_OBJECT + +public: + explicit CoreIpcClient(QObject *parent = nullptr); +}; + +} // namespace deskflow::gui::ipc