feat(ipc): Core IPC server and connected client

This commit is contained in:
Nick Bolton 2025-12-05 17:45:09 +00:00
parent 1ec36645e1
commit 62ccb543dc
14 changed files with 170 additions and 28 deletions

View file

@ -15,6 +15,7 @@
#include "common/ExitCodes.h" #include "common/ExitCodes.h"
#include "deskflow/ClientApp.h" #include "deskflow/ClientApp.h"
#include "deskflow/ServerApp.h" #include "deskflow/ServerApp.h"
#include "deskflow/ipc/CoreIpcServer.h"
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
#include "arch/win32/ArchMiscWindows.h" #include "arch/win32/ArchMiscWindows.h"
@ -104,6 +105,9 @@ int main(int argc, char **argv)
QCoreApplication app(argc, argv); QCoreApplication app(argc, argv);
QCoreApplication::setApplicationName(QStringLiteral("%1 Core").arg(kAppName)); QCoreApplication::setApplicationName(QStringLiteral("%1 Core").arg(kAppName));
const auto ipcServer = new deskflow::core::ipc::CoreIpcServer(&app); // NOSONAR - Qt managed
ipcServer->listen();
QThread coreThread; QThread coreThread;
QObject::connect(&coreThread, &QThread::finished, &app, &QCoreApplication::quit); QObject::connect(&coreThread, &QThread::finished, &app, &QCoreApplication::quit);
coreApp->run(coreThread); coreApp->run(coreThread);

View file

@ -21,6 +21,7 @@ const auto kCopyright = //
"Copyright (C) 2002-2009 Chris Schoeneman"; "Copyright (C) 2002-2009 Chris Schoeneman";
const auto kCoreBinName = "@CORE_BINARY@"; const auto kCoreBinName = "@CORE_BINARY@";
const auto kCoreIpcName = "@CMAKE_PROJECT_NAME@-core";
#ifdef _WIN32 #ifdef _WIN32

View file

@ -83,6 +83,8 @@ add_library(${lib_name} STATIC ${PLATFORM_CODE}
ipc/IpcServer.h ipc/IpcServer.h
ipc/DaemonIpcServer.cpp ipc/DaemonIpcServer.cpp
ipc/DaemonIpcServer.h ipc/DaemonIpcServer.h
ipc/CoreIpcServer.cpp
ipc/CoreIpcServer.h
) )
target_link_libraries(${lib_name} PUBLIC common Qt6::Core Qt6::Network) target_link_libraries(${lib_name} PUBLIC common Qt6::Core Qt6::Network)

View file

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

View file

@ -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 <QObject>
#include <QSet>
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

View file

@ -23,29 +23,14 @@ DaemonIpcServer::DaemonIpcServer(QObject *parent, const QString &logFilename)
// do nothing // 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()); if (command == "logLevel") {
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") {
processLogLevel(clientSocket, parts); processLogLevel(clientSocket, parts);
} else if (command == "elevate") { } else if (command == "elevate") {
processElevate(clientSocket, parts); processElevate(clientSocket, parts);
} else if (command == "command") { } else if (command == "command") {
processCommand(clientSocket, parts); processCommandMessage(clientSocket, parts);
} else if (command == "start") { } else if (command == "start") {
LOG_DEBUG("daemon ipc server got start message"); LOG_DEBUG("daemon ipc server got start message");
Q_EMIT startProcessRequested(); Q_EMIT startProcessRequested();
@ -62,10 +47,8 @@ void DaemonIpcServer::processMessage(QLocalSocket *clientSocket, const QString &
Q_EMIT clearSettingsRequested(); Q_EMIT clearSettingsRequested();
writeToClientSocket(clientSocket, kAckMessage); writeToClientSocket(clientSocket, kAckMessage);
} else { } 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) void DaemonIpcServer::processLogLevel(QLocalSocket *&clientSocket, const QStringList &messageParts)
@ -108,7 +91,7 @@ void DaemonIpcServer::processElevate(QLocalSocket *&clientSocket, const QStringL
writeToClientSocket(clientSocket, kAckMessage); writeToClientSocket(clientSocket, kAckMessage);
} }
void DaemonIpcServer::processCommand(QLocalSocket *&clientSocket, const QStringList &messageParts) void DaemonIpcServer::processCommandMessage(QLocalSocket *&clientSocket, const QStringList &messageParts)
{ {
if (messageParts.size() < 2) { if (messageParts.size() < 2) {
LOG_ERR("daemon ipc server got invalid command message"); LOG_ERR("daemon ipc server got invalid command message");

View file

@ -23,10 +23,10 @@ public:
explicit DaemonIpcServer(QObject *parent, const QString &logFilename); explicit DaemonIpcServer(QObject *parent, const QString &logFilename);
private: 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 processLogLevel(QLocalSocket *&clientSocket, const QStringList &messageParts);
void processElevate(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: private:
const QString m_logFilename; const QString m_logFilename;

View file

@ -13,9 +13,6 @@
namespace deskflow::core::ipc { namespace deskflow::core::ipc {
const auto kAckMessage = "ok";
const auto kErrorMessage = "error";
IpcServer::IpcServer(QObject *parent, const QString &serverName) IpcServer::IpcServer(QObject *parent, const QString &serverName)
: QObject(parent), : QObject(parent),
m_server{new QLocalServer(this)}, // NOSONAR - Qt memory m_server{new QLocalServer(this)}, // NOSONAR - Qt memory
@ -102,6 +99,29 @@ void IpcServer::handleErrorOccurred()
clientSocket->deleteLater(); 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 void IpcServer::writeToClientSocket(QLocalSocket *&clientSocket, const QString &message) const
{ {
QByteArray messageData = message.toUtf8() + '\n'; QByteArray messageData = message.toUtf8() + '\n';

View file

@ -42,7 +42,8 @@ protected:
void writeToClientSocket(QLocalSocket *&clientSocket, const QString &message) const; void writeToClientSocket(QLocalSocket *&clientSocket, const QString &message) const;
private: 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 handleNewConnection();
void handleReadyRead(); void handleReadyRead();
void handleDisconnected(); void handleDisconnected();

View file

@ -85,6 +85,8 @@ add_library(${target} STATIC
ipc/IpcClient.h ipc/IpcClient.h
ipc/DaemonIpcClient.cpp ipc/DaemonIpcClient.cpp
ipc/DaemonIpcClient.h ipc/DaemonIpcClient.h
ipc/CoreIpcClient.cpp
ipc/CoreIpcClient.h
validators/AliasValidator.cpp validators/AliasValidator.cpp
validators/AliasValidator.h validators/AliasValidator.h
validators/ComputerNameValidator.cpp validators/ComputerNameValidator.cpp

View file

@ -8,6 +8,7 @@
#include "CoreProcess.h" #include "CoreProcess.h"
#include "common/ExitCodes.h" #include "common/ExitCodes.h"
#include "gui/ipc/CoreIpcClient.h"
#include "gui/ipc/DaemonIpcClient.h" #include "gui/ipc/DaemonIpcClient.h"
#if defined(Q_OS_MACOS) #if defined(Q_OS_MACOS)
@ -372,6 +373,21 @@ void CoreProcess::start(std::optional<ProcessMode> processModeOption)
startProcessFromDaemon(args); 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; m_lastProcessMode = processMode;
} }
@ -384,6 +400,12 @@ void CoreProcess::stop(std::optional<ProcessMode> processModeOption)
qInfo("stopping core process (%s mode)", qPrintable(processModeToString(processMode))); 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) { if (m_processState == ProcessState::Starting) {
qDebug("core process is starting, cancelling"); qDebug("core process is starting, cancelling");
setProcessState(ProcessState::Stopped); setProcessState(ProcessState::Stopped);

View file

@ -20,6 +20,7 @@
namespace deskflow::gui { namespace deskflow::gui {
namespace ipc { namespace ipc {
class CoreIpcClient;
class DaemonIpcClient; class DaemonIpcClient;
} }
@ -128,6 +129,7 @@ private:
std::optional<ProcessMode> m_lastProcessMode = std::nullopt; std::optional<ProcessMode> m_lastProcessMode = std::nullopt;
QTimer m_retryTimer; QTimer m_retryTimer;
int m_connections = 0; int m_connections = 0;
deskflow::gui::ipc::CoreIpcClient *m_coreIpcClient = nullptr;
deskflow::gui::ipc::DaemonIpcClient *m_daemonIpcClient = nullptr; deskflow::gui::ipc::DaemonIpcClient *m_daemonIpcClient = nullptr;
FileTail *m_daemonFileTail = nullptr; FileTail *m_daemonFileTail = nullptr;
QProcess *m_process = nullptr; QProcess *m_process = nullptr;

View file

@ -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 <QDebug>
#include <QLocalSocket>
#include <QObject>
#include <QString>
namespace deskflow::gui::ipc {
const auto kTimeout = 1000;
CoreIpcClient::CoreIpcClient(QObject *parent) : IpcClient(parent, kCoreIpcName)
{
// do nothing
}
} // namespace deskflow::gui::ipc

View file

@ -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 <QObject>
namespace deskflow::gui::ipc {
class CoreIpcClient : public IpcClient
{
Q_OBJECT
public:
explicit CoreIpcClient(QObject *parent = nullptr);
};
} // namespace deskflow::gui::ipc