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

View file

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

View file

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

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
}
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");

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,6 +20,7 @@
namespace deskflow::gui {
namespace ipc {
class CoreIpcClient;
class DaemonIpcClient;
}
@ -128,6 +129,7 @@ private:
std::optional<ProcessMode> 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;

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