refactor(ipc): Abstract IPC client logic
This commit is contained in:
parent
2e13074580
commit
1ec36645e1
7 changed files with 258 additions and 201 deletions
|
|
@ -81,6 +81,8 @@ add_library(${target} STATIC
|
|||
dialogs/SettingsDialog.cpp
|
||||
dialogs/SettingsDialog.h
|
||||
dialogs/SettingsDialog.ui
|
||||
ipc/IpcClient.cpp
|
||||
ipc/IpcClient.h
|
||||
ipc/DaemonIpcClient.cpp
|
||||
ipc/DaemonIpcClient.h
|
||||
validators/AliasValidator.cpp
|
||||
|
|
|
|||
|
|
@ -916,7 +916,7 @@ void MainWindow::coreProcessStateChanged(ProcessState state)
|
|||
|
||||
void MainWindow::coreConnectionStateChanged(ConnectionState state)
|
||||
{
|
||||
qDebug() << "core connection state changed: " << static_cast<int>(state);
|
||||
qDebug() << "core connection state changed:" << static_cast<int>(state);
|
||||
|
||||
updateStatus();
|
||||
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ bool generateCertificate()
|
|||
try {
|
||||
deskflow::generatePemSelfSignedCert(certPath, keyLength);
|
||||
} catch (const std::exception &e) {
|
||||
qCritical() << "failed to generate self-signed pem cert: " << e.what();
|
||||
qCritical() << "failed to generate self-signed pem cert:" << e.what();
|
||||
return false;
|
||||
}
|
||||
qDebug("tls certificate generated");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
||||
|
|
@ -17,164 +17,9 @@
|
|||
namespace deskflow::gui::ipc {
|
||||
|
||||
const auto kTimeout = 1000;
|
||||
const auto kRetryLimit = 3;
|
||||
|
||||
DaemonIpcClient::DaemonIpcClient(QObject *parent)
|
||||
: QObject(parent),
|
||||
m_socket{new QLocalSocket(this)} // NOSONAR - Qt memory
|
||||
DaemonIpcClient::DaemonIpcClient(QObject *parent) : IpcClient(parent, kDaemonIpcName)
|
||||
{
|
||||
connect(m_socket, &QLocalSocket::disconnected, this, &DaemonIpcClient::handleDisconnected);
|
||||
connect(m_socket, &QLocalSocket::errorOccurred, this, &DaemonIpcClient::handleErrorOccurred);
|
||||
}
|
||||
|
||||
bool DaemonIpcClient::connectToServer()
|
||||
{
|
||||
if (m_state == State::Connecting) {
|
||||
qWarning() << "daemon ipc client already connecting to server";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_state != State::Unconnected) {
|
||||
qDebug() << "daemon ipc client not in unconnected state, disconnecting";
|
||||
disconnectFromServer();
|
||||
}
|
||||
|
||||
if (m_socket->state() != QLocalSocket::UnconnectedState) {
|
||||
qWarning() << "daemon ipc client socket not in unconnected state, disconnecting";
|
||||
disconnectFromServer();
|
||||
}
|
||||
|
||||
for (int i = 0; i < kRetryLimit; ++i) {
|
||||
if (i == 0) {
|
||||
qDebug() << "daemon ipc client connecting to server:" << kDaemonIpcName;
|
||||
} else {
|
||||
qDebug() << "daemon ipc client retrying connection, attempt:" << i + 1;
|
||||
}
|
||||
|
||||
m_state = State::Connecting;
|
||||
m_socket->connectToServer(kDaemonIpcName);
|
||||
|
||||
if (!m_socket->waitForConnected(kTimeout)) {
|
||||
qWarning() << "daemon ipc client failed to connect";
|
||||
disconnectFromServer();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sendMessage("hello", "hello", false)) {
|
||||
qWarning() << "daemon ipc client failed to send hello";
|
||||
disconnectFromServer();
|
||||
continue;
|
||||
}
|
||||
|
||||
m_state = State::Connected;
|
||||
qDebug() << "daemon ipc client connected";
|
||||
Q_EMIT connected();
|
||||
return true;
|
||||
}
|
||||
|
||||
qWarning() << "daemon ipc client failed to connect after" << kRetryLimit << "attempts";
|
||||
disconnectFromServer();
|
||||
Q_EMIT connectionFailed();
|
||||
return false;
|
||||
}
|
||||
|
||||
void DaemonIpcClient::disconnectFromServer()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_state = State::Disconnecting;
|
||||
qDebug() << "daemon ipc client disconnecting from server";
|
||||
m_socket->disconnectFromServer();
|
||||
|
||||
if (m_socket->state() != QLocalSocket::UnconnectedState) {
|
||||
qDebug() << "daemon ipc client waiting for socket to disconnect";
|
||||
m_socket->waitForDisconnected(kTimeout);
|
||||
qDebug() << "daemon ipc client disconnected from server";
|
||||
} else {
|
||||
qDebug() << "daemon ipc client socket already disconnected";
|
||||
}
|
||||
|
||||
m_state = State::Unconnected;
|
||||
}
|
||||
|
||||
void DaemonIpcClient::handleDisconnected()
|
||||
{
|
||||
qDebug() << "daemon ipc client disconnected from server";
|
||||
if (m_state == State::Connected) {
|
||||
Q_EMIT connectionFailed();
|
||||
}
|
||||
|
||||
m_state = State::Unconnected;
|
||||
}
|
||||
|
||||
void DaemonIpcClient::handleErrorOccurred()
|
||||
{
|
||||
qWarning() << "daemon ipc client error:" << m_socket->errorString();
|
||||
disconnectFromServer();
|
||||
|
||||
if (m_state == State::Connected) {
|
||||
Q_EMIT connectionFailed();
|
||||
}
|
||||
}
|
||||
|
||||
bool DaemonIpcClient::sendMessage(const QString &message, const QString &expectAck, const bool expectConnected)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (expectConnected && !isConnected()) {
|
||||
qWarning() << "cannot send command, ipc client not connected";
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray messageData = message.toUtf8() + "\n";
|
||||
m_socket->write(messageData);
|
||||
if (!m_socket->waitForBytesWritten(kTimeout)) {
|
||||
qWarning() << "daemon ipc client failed to write command";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!expectAck.isEmpty()) {
|
||||
qDebug() << "daemon ipc client waiting for ack: " << expectAck;
|
||||
|
||||
if (!m_socket->waitForReadyRead(kTimeout)) {
|
||||
qWarning() << "daemon ipc client socket ready read timed out";
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray response = m_socket->readAll();
|
||||
if (response.isEmpty()) {
|
||||
qWarning() << "daemon ipc client got empty response";
|
||||
return false;
|
||||
}
|
||||
|
||||
QString responseData = QString::fromUtf8(response);
|
||||
if (responseData.isEmpty()) {
|
||||
qWarning() << "daemon ipc client failed to convert response to string";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (responseData != expectAck + "\n") {
|
||||
qWarning() << "daemon ipc client got unexpected response: " << responseData;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "daemon ipc client sent message: " << messageData;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DaemonIpcClient::keepAlive()
|
||||
{
|
||||
if (!isConnected() && !connectToServer()) {
|
||||
qWarning() << "daemon ipc client keep alive failed to connect";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sendMessage("noop")) {
|
||||
qWarning() << "daemon ipc client keep alive ping failed, reconnecting";
|
||||
connectToServer();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DaemonIpcClient::sendLogLevel(const QString &logLevel)
|
||||
|
|
@ -216,12 +61,12 @@ QString DaemonIpcClient::requestLogPath()
|
|||
return QString();
|
||||
}
|
||||
|
||||
if (!m_socket->waitForReadyRead(kTimeout)) {
|
||||
if (!socket()->waitForReadyRead(kTimeout)) {
|
||||
qWarning() << "daemon ipc client failed to read log path response";
|
||||
return QString();
|
||||
}
|
||||
|
||||
QByteArray response = m_socket->readAll();
|
||||
QByteArray response = socket()->readAll();
|
||||
if (response.isEmpty()) {
|
||||
qWarning() << "daemon ipc client got empty log path response";
|
||||
return QString();
|
||||
|
|
@ -236,12 +81,12 @@ QString DaemonIpcClient::requestLogPath()
|
|||
// Trimming removes newline from end of message.
|
||||
QStringList parts = responseData.trimmed().split("=");
|
||||
if (parts.size() != 2) {
|
||||
qWarning() << "daemon ipc client got invalid log path response: " << responseData;
|
||||
qWarning() << "daemon ipc client got invalid log path response:" << responseData;
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (parts[0] != "logPath") {
|
||||
qWarning() << "daemon ipc client got unexpected log path response: " << responseData;
|
||||
qWarning() << "daemon ipc client got unexpected log path response:" << responseData;
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,62 +1,28 @@
|
|||
/*
|
||||
* 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 <QMutex>
|
||||
#include <QObject>
|
||||
#include "IpcClient.h"
|
||||
|
||||
class QLocalSocket;
|
||||
#include <QObject>
|
||||
|
||||
namespace deskflow::gui::ipc {
|
||||
|
||||
class DaemonIpcClient : public QObject
|
||||
class DaemonIpcClient : public IpcClient
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
// Represents underlying socket state and whether the server responded to the hello message.
|
||||
enum class State
|
||||
{
|
||||
Unconnected,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnecting,
|
||||
};
|
||||
|
||||
public:
|
||||
explicit DaemonIpcClient(QObject *parent = nullptr);
|
||||
bool connectToServer();
|
||||
void disconnectFromServer();
|
||||
bool sendLogLevel(const QString &logLevel);
|
||||
bool sendStartProcess(const QString &command, bool elevate);
|
||||
bool sendStopProcess();
|
||||
bool sendClearSettings();
|
||||
QString requestLogPath();
|
||||
|
||||
bool isConnected() const
|
||||
{
|
||||
return m_state == State::Connected;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void connected();
|
||||
void connectionFailed();
|
||||
|
||||
private Q_SLOTS:
|
||||
void handleDisconnected();
|
||||
void handleErrorOccurred();
|
||||
|
||||
private:
|
||||
bool keepAlive();
|
||||
bool sendMessage(const QString &message, const QString &expectAck = "ok", const bool expectConnected = true);
|
||||
|
||||
private:
|
||||
QLocalSocket *m_socket;
|
||||
QMutex m_mutex;
|
||||
State m_state{State::Unconnected};
|
||||
};
|
||||
|
||||
} // namespace deskflow::gui::ipc
|
||||
|
|
|
|||
181
src/lib/gui/ipc/IpcClient.cpp
Normal file
181
src/lib/gui/ipc/IpcClient.cpp
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* 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 "IpcClient.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QLocalSocket>
|
||||
#include <QMutexLocker>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
namespace deskflow::gui::ipc {
|
||||
|
||||
const auto kTimeout = 1000;
|
||||
const auto kRetryLimit = 3;
|
||||
|
||||
IpcClient::IpcClient(QObject *parent, const QString &socketName)
|
||||
: QObject(parent),
|
||||
m_socket{new QLocalSocket(this)},
|
||||
m_socketName(socketName) // NOSONAR - Qt memory
|
||||
{
|
||||
connect(m_socket, &QLocalSocket::disconnected, this, &IpcClient::handleDisconnected);
|
||||
connect(m_socket, &QLocalSocket::errorOccurred, this, &IpcClient::handleErrorOccurred);
|
||||
}
|
||||
|
||||
bool IpcClient::connectToServer()
|
||||
{
|
||||
if (m_state == State::Connecting) {
|
||||
qWarning() << "ipc client already connecting to server";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_state != State::Unconnected) {
|
||||
qDebug() << "ipc client not in unconnected state, disconnecting";
|
||||
disconnectFromServer();
|
||||
}
|
||||
|
||||
if (m_socket->state() != QLocalSocket::UnconnectedState) {
|
||||
qWarning() << "ipc client socket not in unconnected state, disconnecting";
|
||||
disconnectFromServer();
|
||||
}
|
||||
|
||||
for (int i = 0; i < kRetryLimit; ++i) {
|
||||
if (i == 0) {
|
||||
qDebug() << "ipc client connecting to server:" << m_socketName;
|
||||
} else {
|
||||
qDebug() << "ipc client retrying connection, attempt:" << i + 1;
|
||||
}
|
||||
|
||||
m_state = State::Connecting;
|
||||
m_socket->connectToServer(m_socketName);
|
||||
|
||||
if (!m_socket->waitForConnected(kTimeout)) {
|
||||
qWarning() << "ipc client failed to connect";
|
||||
disconnectFromServer();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sendMessage("hello", "hello", false)) {
|
||||
qWarning() << "ipc client failed to send hello";
|
||||
disconnectFromServer();
|
||||
continue;
|
||||
}
|
||||
|
||||
m_state = State::Connected;
|
||||
qDebug() << "ipc client connected";
|
||||
Q_EMIT connected();
|
||||
return true;
|
||||
}
|
||||
|
||||
qWarning() << "ipc client failed to connect after" << kRetryLimit << "attempts";
|
||||
disconnectFromServer();
|
||||
Q_EMIT connectionFailed();
|
||||
return false;
|
||||
}
|
||||
|
||||
void IpcClient::disconnectFromServer()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
m_state = State::Disconnecting;
|
||||
qDebug() << "ipc client disconnecting from server";
|
||||
m_socket->disconnectFromServer();
|
||||
|
||||
if (m_socket->state() != QLocalSocket::UnconnectedState) {
|
||||
qDebug() << "ipc client waiting for socket to disconnect";
|
||||
m_socket->waitForDisconnected(kTimeout);
|
||||
qDebug() << "ipc client disconnected from server";
|
||||
} else {
|
||||
qDebug() << "ipc client socket already disconnected";
|
||||
}
|
||||
|
||||
m_state = State::Unconnected;
|
||||
}
|
||||
|
||||
void IpcClient::handleDisconnected()
|
||||
{
|
||||
qDebug() << "ipc client disconnected from server";
|
||||
if (m_state == State::Connected) {
|
||||
Q_EMIT connectionFailed();
|
||||
}
|
||||
|
||||
m_state = State::Unconnected;
|
||||
}
|
||||
|
||||
void IpcClient::handleErrorOccurred()
|
||||
{
|
||||
qWarning() << "ipc client error:" << m_socket->errorString();
|
||||
disconnectFromServer();
|
||||
|
||||
if (m_state == State::Connected) {
|
||||
Q_EMIT connectionFailed();
|
||||
}
|
||||
}
|
||||
|
||||
bool IpcClient::sendMessage(const QString &message, const QString &expectAck, const bool expectConnected)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
if (expectConnected && !isConnected()) {
|
||||
qWarning() << "cannot send command, ipc client not connected";
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray messageData = message.toUtf8() + "\n";
|
||||
m_socket->write(messageData);
|
||||
if (!m_socket->waitForBytesWritten(kTimeout)) {
|
||||
qWarning() << "ipc client failed to write command";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!expectAck.isEmpty()) {
|
||||
qDebug() << "ipc client waiting for ack:" << expectAck;
|
||||
|
||||
if (!m_socket->waitForReadyRead(kTimeout)) {
|
||||
qWarning() << "ipc client socket ready read timed out";
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray response = m_socket->readAll();
|
||||
if (response.isEmpty()) {
|
||||
qWarning() << "ipc client got empty response";
|
||||
return false;
|
||||
}
|
||||
|
||||
QString responseData = QString::fromUtf8(response);
|
||||
if (responseData.isEmpty()) {
|
||||
qWarning() << "ipc client failed to convert response to string";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (responseData != expectAck + "\n") {
|
||||
qWarning() << "ipc client got unexpected response:" << responseData;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "ipc client sent message:" << messageData;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IpcClient::keepAlive()
|
||||
{
|
||||
if (!isConnected() && !connectToServer()) {
|
||||
qWarning() << "ipc client keep alive failed to connect";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sendMessage("noop")) {
|
||||
qWarning() << "ipc client keep alive ping failed, reconnecting";
|
||||
connectToServer();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace deskflow::gui::ipc
|
||||
63
src/lib/gui/ipc/IpcClient.h
Normal file
63
src/lib/gui/ipc/IpcClient.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
class QLocalSocket;
|
||||
|
||||
namespace deskflow::gui::ipc {
|
||||
|
||||
class IpcClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
// Represents underlying socket state and whether the server responded to the hello message.
|
||||
enum class State
|
||||
{
|
||||
Unconnected,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnecting,
|
||||
};
|
||||
|
||||
public:
|
||||
explicit IpcClient(QObject *parent, const QString &socketName);
|
||||
bool connectToServer();
|
||||
void disconnectFromServer();
|
||||
|
||||
bool isConnected() const
|
||||
{
|
||||
return m_state == State::Connected;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void connected();
|
||||
void connectionFailed();
|
||||
|
||||
private Q_SLOTS:
|
||||
void handleDisconnected();
|
||||
void handleErrorOccurred();
|
||||
|
||||
protected:
|
||||
bool keepAlive();
|
||||
bool sendMessage(const QString &message, const QString &expectAck = "ok", const bool expectConnected = true);
|
||||
|
||||
QLocalSocket *socket() const
|
||||
{
|
||||
return m_socket;
|
||||
}
|
||||
|
||||
private:
|
||||
QLocalSocket *m_socket;
|
||||
State m_state{State::Unconnected};
|
||||
QString m_socketName;
|
||||
QMutex m_mutex;
|
||||
};
|
||||
|
||||
} // namespace deskflow::gui::ipc
|
||||
Loading…
Reference in a new issue