refactor(ipc): Abstract IPC server logic
This commit is contained in:
parent
e4bafaf88f
commit
2e13074580
5 changed files with 202 additions and 145 deletions
|
|
@ -79,6 +79,8 @@ add_library(${lib_name} STATIC ${PLATFORM_CODE}
|
|||
StreamChunker.h
|
||||
languages/LanguageManager.cpp
|
||||
languages/LanguageManager.h
|
||||
ipc/IpcServer.cpp
|
||||
ipc/IpcServer.h
|
||||
ipc/DaemonIpcServer.cpp
|
||||
ipc/DaemonIpcServer.h
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Deskflow -- mouse and keyboard sharing utility
|
||||
* SPDX-FileCopyrightText: (C) 2025 Symless Ltd.
|
||||
* SPDX-FileCopyrightText: (C) 2025-2026 Symless Ltd.
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||
*/
|
||||
|
||||
|
|
@ -9,7 +9,6 @@
|
|||
#include "base/Log.h"
|
||||
#include "common/Constants.h"
|
||||
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
|
||||
namespace deskflow::core::ipc {
|
||||
|
|
@ -18,107 +17,28 @@ const auto kAckMessage = "ok";
|
|||
const auto kErrorMessage = "error";
|
||||
|
||||
DaemonIpcServer::DaemonIpcServer(QObject *parent, const QString &logFilename)
|
||||
: QObject(parent),
|
||||
m_logFilename(logFilename),
|
||||
m_server{new QLocalServer(this)} // NOSONAR - Qt memory
|
||||
: IpcServer(parent, kDaemonIpcName),
|
||||
m_logFilename(logFilename)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
DaemonIpcServer::~DaemonIpcServer()
|
||||
{
|
||||
m_server->close();
|
||||
}
|
||||
|
||||
void DaemonIpcServer::listen()
|
||||
{
|
||||
// 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);
|
||||
QLocalServer::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);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
m_clients.insert(clientSocket);
|
||||
|
||||
connect(clientSocket, &QLocalSocket::readyRead, this, &DaemonIpcServer::handleReadyRead);
|
||||
connect(clientSocket, &QLocalSocket::disconnected, this, &DaemonIpcServer::handleDisconnected);
|
||||
connect(clientSocket, &QLocalSocket::errorOccurred, this, &DaemonIpcServer::handleErrorOccurred);
|
||||
}
|
||||
|
||||
void DaemonIpcServer::handleReadyRead()
|
||||
{
|
||||
const auto clientSocket = qobject_cast<QLocalSocket *>(sender());
|
||||
LOG_DEBUG1("ipc server ready to read data");
|
||||
|
||||
QByteArray data = clientSocket->readAll();
|
||||
if (data.isEmpty()) {
|
||||
LOG_WARN("ipc server got empty message");
|
||||
return;
|
||||
}
|
||||
|
||||
// we don't handle incomplete messages yet; each socket read must have delimiters.
|
||||
if (!data.contains('\n')) {
|
||||
LOG_WARN("ipc server got incomplete message: %s", data.constData());
|
||||
return;
|
||||
}
|
||||
|
||||
// each message is delimited by a newline to keep the protocol super simple.
|
||||
while (data.contains('\n')) {
|
||||
const auto index = data.indexOf('\n');
|
||||
QByteArray messageData = data.left(index);
|
||||
data.remove(0, index + 1);
|
||||
QString message = QString::fromUtf8(messageData);
|
||||
processMessage(clientSocket, message);
|
||||
}
|
||||
}
|
||||
|
||||
void DaemonIpcServer::handleDisconnected()
|
||||
{
|
||||
const auto clientSocket = qobject_cast<QLocalSocket *>(sender());
|
||||
LOG_DEBUG("ipc server client disconnected");
|
||||
m_clients.remove(clientSocket);
|
||||
clientSocket->deleteLater();
|
||||
}
|
||||
|
||||
void DaemonIpcServer::handleErrorOccurred()
|
||||
{
|
||||
const auto clientSocket = qobject_cast<QLocalSocket *>(sender());
|
||||
LOG_ERR("ipc server client error: %s", clientSocket->errorString().toUtf8().constData());
|
||||
m_clients.remove(clientSocket);
|
||||
clientSocket->deleteLater();
|
||||
}
|
||||
|
||||
void DaemonIpcServer::processMessage(QLocalSocket *clientSocket, const QString &message)
|
||||
{
|
||||
LOG_DEBUG1("ipc server got message: %s", message.toUtf8().constData());
|
||||
LOG_DEBUG1("daemon ipc server got message: %s", message.toUtf8().constData());
|
||||
const auto parts = message.split('=');
|
||||
if (parts.size() < 1) {
|
||||
LOG_ERR("ipc server got invalid message: %s", message.toUtf8().constData());
|
||||
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("ipc server got hello message, sending hello back");
|
||||
LOG_DEBUG("daemon ipc server got hello message, sending hello back");
|
||||
writeToClientSocket(clientSocket, "hello");
|
||||
} else if (command == "noop") {
|
||||
LOG_DEBUG("ipc server got noop message");
|
||||
LOG_DEBUG("daemon ipc server got noop message");
|
||||
writeToClientSocket(clientSocket, kAckMessage);
|
||||
} else if (command == "logLevel") {
|
||||
processLogLevel(clientSocket, parts);
|
||||
|
|
@ -127,22 +47,22 @@ void DaemonIpcServer::processMessage(QLocalSocket *clientSocket, const QString &
|
|||
} else if (command == "command") {
|
||||
processCommand(clientSocket, parts);
|
||||
} else if (command == "start") {
|
||||
LOG_DEBUG("ipc server got start message");
|
||||
LOG_DEBUG("daemon ipc server got start message");
|
||||
Q_EMIT startProcessRequested();
|
||||
writeToClientSocket(clientSocket, kAckMessage);
|
||||
} else if (command == "stop") {
|
||||
LOG_DEBUG("ipc server got stop message");
|
||||
LOG_DEBUG("daemon ipc server got stop message");
|
||||
Q_EMIT stopProcessRequested();
|
||||
writeToClientSocket(clientSocket, kAckMessage);
|
||||
} else if (command == "logPath") {
|
||||
LOG_DEBUG("ipc server got log path request");
|
||||
LOG_DEBUG("daemon ipc server got log path request");
|
||||
writeToClientSocket(clientSocket, "logPath=" + m_logFilename.toUtf8());
|
||||
} else if (command == "clearSettings") {
|
||||
LOG_DEBUG("ipc server got clear settings message");
|
||||
LOG_DEBUG("daemon ipc server got clear settings message");
|
||||
Q_EMIT clearSettingsRequested();
|
||||
writeToClientSocket(clientSocket, kAckMessage);
|
||||
} else {
|
||||
LOG_WARN("ipc server got unknown message: %s", message.toUtf8().constData());
|
||||
LOG_WARN("daemon ipc server got unknown message: %s", message.toUtf8().constData());
|
||||
}
|
||||
|
||||
clientSocket->flush();
|
||||
|
|
@ -151,19 +71,19 @@ void DaemonIpcServer::processMessage(QLocalSocket *clientSocket, const QString &
|
|||
void DaemonIpcServer::processLogLevel(QLocalSocket *&clientSocket, const QStringList &messageParts)
|
||||
{
|
||||
if (messageParts.size() < 2) {
|
||||
LOG_ERR("ipc server got invalid log level message");
|
||||
LOG_ERR("daemon ipc server got invalid log level message");
|
||||
writeToClientSocket(clientSocket, kErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &logLevel = messageParts[1];
|
||||
if (logLevel.isEmpty()) {
|
||||
LOG_ERR("ipc server got empty log level");
|
||||
LOG_ERR("daemon ipc server got empty log level");
|
||||
writeToClientSocket(clientSocket, kErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("ipc server got new log level: %s", logLevel.toUtf8().constData());
|
||||
LOG_DEBUG("daemon ipc server got new log level: %s", logLevel.toUtf8().constData());
|
||||
Q_EMIT logLevelChanged(logLevel);
|
||||
writeToClientSocket(clientSocket, kAckMessage);
|
||||
}
|
||||
|
|
@ -171,19 +91,19 @@ void DaemonIpcServer::processLogLevel(QLocalSocket *&clientSocket, const QString
|
|||
void DaemonIpcServer::processElevate(QLocalSocket *&clientSocket, const QStringList &messageParts)
|
||||
{
|
||||
if (messageParts.size() < 2) {
|
||||
LOG_ERR("ipc server got invalid elevate message");
|
||||
LOG_ERR("daemon ipc server got invalid elevate message");
|
||||
writeToClientSocket(clientSocket, kErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &elevate = messageParts[1];
|
||||
if (elevate != "yes" && elevate != "no") {
|
||||
LOG_ERR("ipc server got invalid elevate value: %s", elevate.toUtf8().constData());
|
||||
LOG_ERR("daemon ipc server got invalid elevate value: %s", elevate.toUtf8().constData());
|
||||
writeToClientSocket(clientSocket, kErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("ipc server got new elevate value: %s", elevate.toUtf8().constData());
|
||||
LOG_DEBUG("daemon ipc server got new elevate value: %s", elevate.toUtf8().constData());
|
||||
Q_EMIT elevateModeChanged(elevate == "yes");
|
||||
writeToClientSocket(clientSocket, kAckMessage);
|
||||
}
|
||||
|
|
@ -191,32 +111,21 @@ void DaemonIpcServer::processElevate(QLocalSocket *&clientSocket, const QStringL
|
|||
void DaemonIpcServer::processCommand(QLocalSocket *&clientSocket, const QStringList &messageParts)
|
||||
{
|
||||
if (messageParts.size() < 2) {
|
||||
LOG_ERR("ipc server got invalid command message");
|
||||
LOG_ERR("daemon ipc server got invalid command message");
|
||||
writeToClientSocket(clientSocket, kErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &command = messageParts[1];
|
||||
if (command.isEmpty()) {
|
||||
LOG_ERR("ipc server got empty command");
|
||||
LOG_ERR("daemon ipc server got empty command");
|
||||
writeToClientSocket(clientSocket, kErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG("ipc server got new command: %s", command.toUtf8().constData());
|
||||
LOG_DEBUG("daemon ipc server got new command: %s", command.toUtf8().constData());
|
||||
Q_EMIT commandChanged(command);
|
||||
writeToClientSocket(clientSocket, kAckMessage);
|
||||
}
|
||||
|
||||
void DaemonIpcServer::writeToClientSocket(QLocalSocket *&clientSocket, const QString &message) const
|
||||
{
|
||||
QByteArray messageData = message.toUtf8() + '\n';
|
||||
qint64 bytesWritten = clientSocket->write(messageData);
|
||||
if (bytesWritten != messageData.size()) {
|
||||
LOG_ERR("ipc server failed to write full message to client socket");
|
||||
} else {
|
||||
LOG_DEBUG1("ipc server wrote message to client socket: %s", message.toUtf8().constData());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace deskflow::core::ipc
|
||||
|
|
|
|||
|
|
@ -1,61 +1,35 @@
|
|||
/*
|
||||
* Deskflow -- mouse and keyboard sharing utility
|
||||
* SPDX-FileCopyrightText: (C) 2025 Symless Ltd.
|
||||
* SPDX-FileCopyrightText: (C) 2025-2026 Symless Ltd.
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include "IpcServer.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class QLocalServer;
|
||||
class QLocalSocket;
|
||||
|
||||
namespace deskflow::core::ipc {
|
||||
|
||||
class DaemonIpcServer : public QObject
|
||||
class DaemonIpcServer : public IpcServer
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DaemonIpcServer(QObject *parent, const QString &logFilename);
|
||||
~DaemonIpcServer() override;
|
||||
|
||||
void listen();
|
||||
|
||||
Q_SIGNALS:
|
||||
void logLevelChanged(const QString &logLevel);
|
||||
void elevateModeChanged(bool elevate);
|
||||
void commandChanged(const QString &command);
|
||||
void startProcessRequested();
|
||||
void stopProcessRequested();
|
||||
void clearSettingsRequested();
|
||||
|
||||
private:
|
||||
void processMessage(QLocalSocket *clientSocket, const QString &message);
|
||||
void processMessage(QLocalSocket *clientSocket, const QString &message) override;
|
||||
void processLogLevel(QLocalSocket *&clientSocket, const QStringList &messageParts);
|
||||
void processElevate(QLocalSocket *&clientSocket, const QStringList &messageParts);
|
||||
void processCommand(QLocalSocket *&clientSocket, const QStringList &messageParts);
|
||||
|
||||
/**!
|
||||
* Write a message to the client socket and append a newline character.
|
||||
*
|
||||
* \param clientSocket The client socket to write to.
|
||||
* \param message The message to write (without trailing newline).
|
||||
*/
|
||||
void writeToClientSocket(QLocalSocket *&clientSocket, const QString &message) const;
|
||||
|
||||
private Q_SLOTS:
|
||||
void handleNewConnection();
|
||||
void handleReadyRead();
|
||||
void handleDisconnected();
|
||||
void handleErrorOccurred();
|
||||
|
||||
private:
|
||||
const QString m_logFilename;
|
||||
QLocalServer *m_server;
|
||||
QSet<QLocalSocket *> m_clients;
|
||||
};
|
||||
|
||||
} // namespace deskflow::core::ipc
|
||||
|
|
|
|||
116
src/lib/deskflow/ipc/IpcServer.cpp
Normal file
116
src/lib/deskflow/ipc/IpcServer.cpp
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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 "IpcServer.h"
|
||||
|
||||
#include "base/Log.h"
|
||||
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
|
||||
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
|
||||
m_serverName(serverName)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
IpcServer::~IpcServer()
|
||||
{
|
||||
m_server->close();
|
||||
}
|
||||
|
||||
void IpcServer::listen()
|
||||
{
|
||||
// IPC server normally 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, &IpcServer::handleNewConnection);
|
||||
QLocalServer::removeServer(m_serverName);
|
||||
if (m_server->listen(m_serverName)) {
|
||||
LOG_DEBUG("ipc server listening on: %s", m_serverName.toUtf8().constData());
|
||||
} else {
|
||||
LOG_ERR("ipc server failed to listen on: %s", m_serverName.toUtf8().constData());
|
||||
}
|
||||
}
|
||||
|
||||
void IpcServer::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");
|
||||
m_clients.insert(clientSocket);
|
||||
|
||||
connect(clientSocket, &QLocalSocket::readyRead, this, &IpcServer::handleReadyRead);
|
||||
connect(clientSocket, &QLocalSocket::disconnected, this, &IpcServer::handleDisconnected);
|
||||
connect(clientSocket, &QLocalSocket::errorOccurred, this, &IpcServer::handleErrorOccurred);
|
||||
}
|
||||
|
||||
void IpcServer::handleReadyRead()
|
||||
{
|
||||
const auto clientSocket = qobject_cast<QLocalSocket *>(sender());
|
||||
LOG_DEBUG1("ipc server ready to read data");
|
||||
|
||||
QByteArray data = clientSocket->readAll();
|
||||
if (data.isEmpty()) {
|
||||
LOG_WARN("ipc server got empty message");
|
||||
return;
|
||||
}
|
||||
|
||||
// we don't handle incomplete messages yet; each socket read must have delimiters.
|
||||
if (!data.contains('\n')) {
|
||||
LOG_WARN("ipc server got incomplete message: %s", data.constData());
|
||||
return;
|
||||
}
|
||||
|
||||
// each message is delimited by a newline to keep the protocol super simple.
|
||||
while (data.contains('\n')) {
|
||||
const auto index = data.indexOf('\n');
|
||||
QByteArray messageData = data.left(index);
|
||||
data.remove(0, index + 1);
|
||||
QString message = QString::fromUtf8(messageData);
|
||||
processMessage(clientSocket, message);
|
||||
}
|
||||
}
|
||||
|
||||
void IpcServer::handleDisconnected()
|
||||
{
|
||||
const auto clientSocket = qobject_cast<QLocalSocket *>(sender());
|
||||
LOG_DEBUG("ipc server client disconnected");
|
||||
m_clients.remove(clientSocket);
|
||||
clientSocket->deleteLater();
|
||||
}
|
||||
|
||||
void IpcServer::handleErrorOccurred()
|
||||
{
|
||||
const auto clientSocket = qobject_cast<QLocalSocket *>(sender());
|
||||
LOG_ERR("ipc server client error: %s", clientSocket->errorString().toUtf8().constData());
|
||||
m_clients.remove(clientSocket);
|
||||
clientSocket->deleteLater();
|
||||
}
|
||||
|
||||
void IpcServer::writeToClientSocket(QLocalSocket *&clientSocket, const QString &message) const
|
||||
{
|
||||
QByteArray messageData = message.toUtf8() + '\n';
|
||||
qint64 bytesWritten = clientSocket->write(messageData);
|
||||
if (bytesWritten != messageData.size()) {
|
||||
LOG_ERR("ipc server failed to write full message to client socket");
|
||||
} else {
|
||||
LOG_DEBUG1("ipc server wrote message to client socket: %s", message.toUtf8().constData());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace deskflow::core::ipc
|
||||
56
src/lib/deskflow/ipc/IpcServer.h
Normal file
56
src/lib/deskflow/ipc/IpcServer.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 <QObject>
|
||||
#include <QSet>
|
||||
|
||||
class QLocalServer;
|
||||
class QLocalSocket;
|
||||
|
||||
namespace deskflow::core::ipc {
|
||||
|
||||
class IpcServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit IpcServer(QObject *parent, const QString &serverName);
|
||||
~IpcServer() override;
|
||||
|
||||
void listen();
|
||||
|
||||
Q_SIGNALS:
|
||||
void logLevelChanged(const QString &logLevel);
|
||||
void elevateModeChanged(bool elevate);
|
||||
void commandChanged(const QString &command);
|
||||
void startProcessRequested();
|
||||
void stopProcessRequested();
|
||||
void clearSettingsRequested();
|
||||
|
||||
protected:
|
||||
/**!
|
||||
* Write a message to the client socket and append a newline character.
|
||||
*
|
||||
* \param clientSocket The client socket to write to.
|
||||
* \param message The message to write (without trailing newline).
|
||||
*/
|
||||
void writeToClientSocket(QLocalSocket *&clientSocket, const QString &message) const;
|
||||
|
||||
private:
|
||||
virtual void processMessage(QLocalSocket *clientSocket, const QString &message) = 0;
|
||||
void handleNewConnection();
|
||||
void handleReadyRead();
|
||||
void handleDisconnected();
|
||||
void handleErrorOccurred();
|
||||
|
||||
QLocalServer *m_server;
|
||||
QSet<QLocalSocket *> m_clients;
|
||||
QString m_serverName;
|
||||
};
|
||||
|
||||
} // namespace deskflow::core::ipc
|
||||
Loading…
Reference in a new issue