feat(ipc): Unrecognised IPC client, retry messages, and TLS fingerprint

Also adds version checking for IPC server and client
This commit is contained in:
Nick Bolton 2026-03-26 14:17:56 +00:00
parent 66c9b6607c
commit 59a4747113
28 changed files with 170 additions and 826 deletions

View file

@ -14,7 +14,6 @@
#include "base/NetworkProtocol.h" #include "base/NetworkProtocol.h"
#include "client/ServerProxy.h" #include "client/ServerProxy.h"
#include "common/Settings.h" #include "common/Settings.h"
#include "deskflow/ipc/CoreIpc.h"
#include "deskflow/Clipboard.h" #include "deskflow/Clipboard.h"
#include "deskflow/IPlatformScreen.h" #include "deskflow/IPlatformScreen.h"
#include "deskflow/PacketStreamFilter.h" #include "deskflow/PacketStreamFilter.h"
@ -22,11 +21,14 @@
#include "deskflow/ProtocolUtil.h" #include "deskflow/ProtocolUtil.h"
#include "deskflow/Screen.h" #include "deskflow/Screen.h"
#include "deskflow/StreamChunker.h" #include "deskflow/StreamChunker.h"
#include "deskflow/ipc/CoreIpc.h"
#include "net/IDataSocket.h" #include "net/IDataSocket.h"
#include "net/ISocketFactory.h" #include "net/ISocketFactory.h"
#include "net/SecureSocket.h" #include "net/SecureSocket.h"
#include "net/TCPSocket.h" #include "net/TCPSocket.h"
#include <QMetaEnum>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@ -133,8 +135,11 @@ void Client::disconnect(const char *msg)
} }
} }
void Client::refuseConnection(const char *msg) void Client::refuseConnection(deskflow::core::ConnectionRefusal reason, const char *msg)
{ {
const auto metaEnum = QMetaEnum::fromType<deskflow::core::ConnectionRefusal>();
ipcSendToClient("connectionRefused", metaEnum.valueToKey(static_cast<int>(reason)));
cleanup(); cleanup();
if (msg) { if (msg) {

View file

@ -11,6 +11,7 @@
#include "deskflow/IClient.h" #include "deskflow/IClient.h"
#include "base/EventTypes.h" #include "base/EventTypes.h"
#include "common/Enums.h"
#include "deskflow/IClipboard.h" #include "deskflow/IClipboard.h"
#include "net/NetworkAddress.h" #include "net/NetworkAddress.h"
@ -88,7 +89,7 @@ public:
Disconnects from the server with an optional error message. Disconnects from the server with an optional error message.
Unlike disconnect this function doesn't try to use other ip addresses Unlike disconnect this function doesn't try to use other ip addresses
*/ */
void refuseConnection(const char *msg); void refuseConnection(deskflow::core::ConnectionRefusal reason, const char *msg);
//! Notify of handshake complete //! Notify of handshake complete
/*! /*!

View file

@ -124,6 +124,7 @@ void ServerProxy::handleData()
ServerProxy::ConnectionResult ServerProxy::parseHandshakeMessage(const uint8_t *code) ServerProxy::ConnectionResult ServerProxy::parseHandshakeMessage(const uint8_t *code)
{ {
using enum ConnectionResult; using enum ConnectionResult;
using enum deskflow::core::ConnectionRefusal;
if (memcmp(code, kMsgQInfo, 4) == 0) { if (memcmp(code, kMsgQInfo, 4) == 0) {
queryInfo(); queryInfo();
@ -168,25 +169,25 @@ ServerProxy::ConnectionResult ServerProxy::parseHandshakeMessage(const uint8_t *
int32_t minor; int32_t minor;
ProtocolUtil::readf(m_stream, kMsgEIncompatible + 4, &major, &minor); ProtocolUtil::readf(m_stream, kMsgEIncompatible + 4, &major, &minor);
LOG_ERR("server has incompatible version %d.%d", major, minor); LOG_ERR("server has incompatible version %d.%d", major, minor);
m_client->refuseConnection("server has incompatible version"); m_client->refuseConnection(IncompatibleVersion, "server has incompatible version");
return Disconnect; return Disconnect;
} }
else if (memcmp(code, kMsgEBusy, 4) == 0) { else if (memcmp(code, kMsgEBusy, 4) == 0) {
LOG_ERR("server already has a connected client with name \"%s\"", m_client->getName().c_str()); LOG_ERR("server already has a connected client with name \"%s\"", m_client->getName().c_str());
m_client->refuseConnection("server already has a connected client with our name"); m_client->refuseConnection(AlreadyConnected, "server already has a connected client with our name");
return Disconnect; return Disconnect;
} }
else if (memcmp(code, kMsgEUnknown, 4) == 0) { else if (memcmp(code, kMsgEUnknown, 4) == 0) {
LOG_ERR("server refused client with name \"%s\"", m_client->getName().c_str()); LOG_ERR("server refused client with name \"%s\"", m_client->getName().c_str());
m_client->refuseConnection("server refused client with our name"); m_client->refuseConnection(UnknownClient, "server refused client with our name");
return Disconnect; return Disconnect;
} }
else if (memcmp(code, kMsgEBad, 4) == 0) { else if (memcmp(code, kMsgEBad, 4) == 0) {
LOG_ERR("server disconnected due to a protocol error"); LOG_ERR("server disconnected due to a protocol error");
m_client->refuseConnection("server reported a protocol error"); m_client->refuseConnection(ProtocolError, "server reported a protocol error");
return Disconnect; return Disconnect;
} else if (memcmp(code, kMsgDLanguageSynchronisation, 4) == 0) { } else if (memcmp(code, kMsgDLanguageSynchronisation, 4) == 0) {
setServerLanguages(); setServerLanguages();

View file

@ -43,4 +43,14 @@ enum class ConnectionState
Listening Listening
}; };
Q_ENUM_NS(ConnectionState) Q_ENUM_NS(ConnectionState)
enum class ConnectionRefusal
{
IncompatibleVersion,
AlreadyConnected,
UnknownClient,
ProtocolError
};
Q_ENUM_NS(ConnectionRefusal)
} // namespace deskflow::core } // namespace deskflow::core

View file

@ -7,6 +7,7 @@
#include "IpcServer.h" #include "IpcServer.h"
#include "base/Log.h" #include "base/Log.h"
#include "common/VersionInfo.h"
#include <QLocalServer> #include <QLocalServer>
#include <QLocalSocket> #include <QLocalSocket>
@ -110,8 +111,21 @@ void IpcServer::processMessage(QLocalSocket *clientSocket, const QString &messag
} }
if (const auto &command = parts.at(0); command == "hello") { if (const auto &command = parts.at(0); command == "hello") {
const auto versionId = QStringLiteral("%1+%2").arg(kVersion, kVersionGitSha);
const auto clientVersion = parts.size() >= 2 ? parts.at(1) : QString();
if (clientVersion != versionId) {
LOG_ERR(
"ipc client version mismatch (client: %s, server: %s)",
clientVersion.isEmpty() ? "unknown" : clientVersion.toUtf8().constData(), versionId.toUtf8().constData()
);
writeToClientSocket(clientSocket, "error");
clientSocket->flush();
clientSocket->disconnectFromServer();
return;
}
LOG_DEBUG("ipc server got hello message, sending hello back"); LOG_DEBUG("ipc server got hello message, sending hello back");
writeToClientSocket(clientSocket, "hello"); writeToClientSocket(clientSocket, QString("hello=%1").arg(versionId));
// Replay messages that were queued before any clients connected. // Replay messages that were queued before any clients connected.
LOG_DEBUG1("ipc server replaying %d pending messages", m_pendingMessages.size()); LOG_DEBUG1("ipc server replaying %d pending messages", m_pendingMessages.size());

View file

@ -39,7 +39,6 @@ add_library(${target} STATIC
TlsUtility.h TlsUtility.h
VersionChecker.cpp VersionChecker.cpp
VersionChecker.h VersionChecker.h
config/IServerConfig.h
config/Screen.cpp config/Screen.cpp
config/Screen.h config/Screen.h
config/ScreenConfig.cpp config/ScreenConfig.cpp
@ -48,16 +47,10 @@ add_library(${target} STATIC
config/ScreenList.h config/ScreenList.h
config/ServerConfig.cpp config/ServerConfig.cpp
config/ServerConfig.h config/ServerConfig.h
core/ClientConnection.cpp
core/ClientConnection.h
core/CoreProcess.cpp core/CoreProcess.cpp
core/CoreProcess.h core/CoreProcess.h
core/NetworkMonitor.cpp core/NetworkMonitor.cpp
core/NetworkMonitor.h core/NetworkMonitor.h
core/ServerConnection.cpp
core/ServerConnection.h
core/ServerMessage.cpp
core/ServerMessage.h
dialogs/AboutDialog.cpp dialogs/AboutDialog.cpp
dialogs/AboutDialog.h dialogs/AboutDialog.h
dialogs/AboutDialog.ui dialogs/AboutDialog.ui

View file

@ -58,8 +58,6 @@ using namespace deskflow::gui;
MainWindow::MainWindow() MainWindow::MainWindow()
: ui{std::make_unique<Ui::MainWindow>()}, : ui{std::make_unique<Ui::MainWindow>()},
m_coreProcess(m_serverConfig), m_coreProcess(m_serverConfig),
m_serverConnection(this, m_serverConfig),
m_clientConnection(this),
m_trayIcon{new QSystemTrayIcon(this)}, m_trayIcon{new QSystemTrayIcon(this)},
m_guiDupeChecker{new QLocalServer(this)}, m_guiDupeChecker{new QLocalServer(this)},
m_daemonIpcClient{new ipc::DaemonIpcClient(this)}, m_daemonIpcClient{new ipc::DaemonIpcClient(this)},
@ -269,15 +267,11 @@ void MainWindow::connectSlots()
if (!deskflow::platform::isMac()) if (!deskflow::platform::isMac())
connect(m_trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivated); connect(m_trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivated);
connect(&m_serverConnection, &ServerConnection::configureClient, this, &MainWindow::serverConnectionConfigureClient);
connect(&m_serverConnection, &ServerConnection::clientsChanged, this, &MainWindow::serverClientsChanged);
connect(&m_coreProcess, &CoreProcess::connectedClientsChanged, this, &MainWindow::serverClientsChanged); connect(&m_coreProcess, &CoreProcess::connectedClientsChanged, this, &MainWindow::serverClientsChanged);
connect( connect(&m_coreProcess, &CoreProcess::unrecognisedClient, this, &MainWindow::handleUnrecognisedClient);
&m_serverConnection, &ServerConnection::requestNewClientPrompt, this, &MainWindow::handleNewClientPromptRequest connect(&m_coreProcess, &CoreProcess::connectionRefused, this, &MainWindow::handleConnectionRefused);
); connect(&m_coreProcess, &CoreProcess::retryIn, this, &MainWindow::updateTimeoutDelay);
connect(&m_coreProcess, &CoreProcess::peerFingerprint, this, &MainWindow::handlePeerFingerprint);
connect(&m_clientConnection, &ClientConnection::requestShowError, this, &MainWindow::showClientError);
connect(&m_clientConnection, &ClientConnection::updateTimeoutDelay, this, &MainWindow::updateTimeoutDelay);
if (Settings::value(Settings::Gui::AutoStartCore).toBool()) { if (Settings::value(Settings::Gui::AutoStartCore).toBool()) {
connect(ui->btnToggleCore, &QPushButton::clicked, m_actionStopCore, &QAction::trigger, Qt::UniqueConnection); connect(ui->btnToggleCore, &QPushButton::clicked, m_actionStopCore, &QAction::trigger, Qt::UniqueConnection);
@ -430,8 +424,6 @@ void MainWindow::clearSettings()
m_networkMonitor->stopMonitoring(); m_networkMonitor->stopMonitoring();
disconnect(&m_coreProcess, nullptr, this, nullptr); disconnect(&m_coreProcess, nullptr, this, nullptr);
disconnect(&m_serverConnection, nullptr, this, nullptr);
disconnect(&m_clientConnection, nullptr, this, nullptr);
disconnect(&m_versionChecker, nullptr, this, nullptr); disconnect(&m_versionChecker, nullptr, this, nullptr);
disconnect(m_guiDupeChecker, nullptr, this, nullptr); disconnect(m_guiDupeChecker, nullptr, this, nullptr);
disconnect(m_trayIcon, nullptr, this, nullptr); disconnect(m_trayIcon, nullptr, this, nullptr);
@ -598,12 +590,12 @@ void MainWindow::updateNetworkInfo()
void MainWindow::serverConnectionConfigureClient(const QString &clientName) void MainWindow::serverConnectionConfigureClient(const QString &clientName)
{ {
m_serverConnection.serverConfigDialogVisible(true); m_serverConfigDialogVisible = true;
ServerConfigDialog dialog(this, m_serverConfig); ServerConfigDialog dialog(this, m_serverConfig);
if (dialog.addClient(clientName) && dialog.exec() == QDialog::Accepted) { if (dialog.addClient(clientName) && dialog.exec() == QDialog::Accepted) {
m_coreProcess.restart(); m_coreProcess.restart();
} }
m_serverConnection.serverConfigDialogVisible(false); m_serverConfigDialogVisible = false;
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@ -755,35 +747,64 @@ void MainWindow::setTrayIcon()
void MainWindow::handleLogLine(const QString &line) void MainWindow::handleLogLine(const QString &line)
{ {
m_logDock->appendLine(line); m_logDock->appendLine(line);
updateFromLogLine(line);
} }
void MainWindow::updateFromLogLine(const QString &line) void MainWindow::handleUnrecognisedClient(const QString &clientName)
{ {
checkConnected(line); if (m_ignoredClients.contains(clientName)) {
checkFingerprint(line); qDebug("ignoring %s:", qPrintable(clientName));
} return;
void MainWindow::checkConnected(const QString &line)
{
if (ui->rbModeServer->isChecked()) {
m_serverConnection.handleLogLine(line);
} else {
m_clientConnection.handleLogLine(line);
} }
}
void MainWindow::checkFingerprint(const QString &line) if (m_newClientPromptShowing || m_serverConfigDialogVisible)
{
static const auto tlsPeerMessage = QStringLiteral("peer fingerprint: ");
static const qsizetype msgLen = QString(tlsPeerMessage).length();
const qsizetype midStart = line.indexOf(tlsPeerMessage);
if (midStart == -1)
return; return;
const auto sha256Text = line.mid(midStart + msgLen).remove(':'); if (Settings::value(Settings::Server::ExternalConfig).toBool())
return;
if (m_serverConfig.isFull() || m_serverConfig.screenExists(clientName))
return;
m_newClientPromptShowing = true;
showAndActivate();
if (deskflow::gui::messages::showNewClientPrompt(this, clientName)) {
serverConnectionConfigureClient(clientName);
} else {
m_ignoredClients.insert(clientName);
}
m_newClientPromptShowing = false;
}
void MainWindow::handleConnectionRefused(deskflow::core::ConnectionRefusal reason)
{
if (reason != deskflow::core::ConnectionRefusal::AlreadyConnected)
return;
if (!isVisible() || m_clientErrorVisible)
return;
m_clientErrorVisible = true;
showAndActivate();
const auto address = Settings::value(Settings::Client::RemoteHost).toString();
QMessageBox::warning(
this, tr("%1 Connection Error").arg(kAppName),
tr("<p>Failed to connect to the server '%1'.</p>"
"<p>A Client with your name is already connected to the server.</p>"
"Please ensure that you're using a unique name and that only a "
"single instance of the client process is running.</p>")
.arg(address)
);
m_clientErrorVisible = false;
}
void MainWindow::handlePeerFingerprint(const QString &fingerprint)
{
const auto sha256Text = QString(fingerprint).remove(':');
const Fingerprint sha256 = {QCryptographicHash::Sha256, QByteArray::fromHex(sha256Text.toLatin1())}; const Fingerprint sha256 = {QCryptographicHash::Sha256, QByteArray::fromHex(sha256Text.toLatin1())};
const bool isClient = m_coreProcess.mode() == CoreMode::Client; const bool isClient = m_coreProcess.mode() == CoreMode::Client;
@ -959,7 +980,7 @@ void MainWindow::changeEvent(QEvent *e)
updateModeControlLabels(); updateModeControlLabels();
updateNetworkInfo(); updateNetworkInfo();
updateStatus(); updateStatus();
serverClientsChanged(m_serverConnection.connectedClients()); serverClientsChanged({});
updateText(); updateText();
} }
} }
@ -1167,34 +1188,6 @@ void MainWindow::remoteHostChanged(const QString &newRemoteHost)
} }
} }
void MainWindow::showClientError(deskflow::client::ErrorType error, const QString &address)
{
if (!isVisible() || m_clientErrorVisible || error != deskflow::client::ErrorType::AlreadyConnected)
return;
m_clientErrorVisible = true;
showAndActivate();
QMessageBox::warning(
this, tr("%1 Connection Error").arg(kAppName),
tr("<p>Failed to connect to the server '%1'.</p>"
"<p>A Client with your name is already connected to the server.</p>"
"Please ensure that you're using a unique name and that only a "
"single instance of the client process is running.</p>")
.arg(address)
);
m_clientErrorVisible = false;
}
void MainWindow::handleNewClientPromptRequest(const QString &clientName)
{
showAndActivate();
bool result = deskflow::gui::messages::showNewClientPrompt(this, clientName);
m_serverConnection.handleNewClientResult(clientName, result);
}
void MainWindow::updateIpLabel(const QStringList &addresses) void MainWindow::updateIpLabel(const QStringList &addresses)
{ {
if (m_coreProcess.mode() != CoreMode::Server) { if (m_coreProcess.mode() != CoreMode::Server) {

View file

@ -16,10 +16,8 @@
#include "VersionChecker.h" #include "VersionChecker.h"
#include "config/ServerConfig.h" #include "config/ServerConfig.h"
#include "gui/core/ClientConnection.h"
#include "gui/core/CoreProcess.h" #include "gui/core/CoreProcess.h"
#include "gui/core/NetworkMonitor.h" #include "gui/core/NetworkMonitor.h"
#include "gui/core/ServerConnection.h"
#include "net/Fingerprint.h" #include "net/Fingerprint.h"
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
@ -118,9 +116,9 @@ private:
void setupTrayIcon(); void setupTrayIcon();
void applyConfig(); void applyConfig();
void setTrayIcon(); void setTrayIcon();
void updateFromLogLine(const QString &line); void handleUnrecognisedClient(const QString &clientName);
void checkConnected(const QString &line); void handleConnectionRefused(deskflow::core::ConnectionRefusal reason);
void checkFingerprint(const QString &line); void handlePeerFingerprint(const QString &fingerprint);
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
void secureSocket(bool secureSocket); void secureSocket(bool secureSocket);
void connectSlots(); void connectSlots();
@ -140,17 +138,10 @@ private:
void daemonIpcClientConnectionFailed(); void daemonIpcClientConnectionFailed();
void toggleCanRunCore(bool enableButtons); void toggleCanRunCore(bool enableButtons);
void remoteHostChanged(const QString &newRemoteHost); void remoteHostChanged(const QString &newRemoteHost);
void handleNewClientPromptRequest(const QString &clientName);
void updateIpLabel(const QStringList &addresses); void updateIpLabel(const QStringList &addresses);
void updateTimeoutDelay(int newDelay); void updateTimeoutDelay(int newDelay);
bool canRunCore() const; bool canRunCore() const;
/**
* @brief showClientError
* @param error Error Type
* @param address
*/
void showClientError(deskflow::client::ErrorType error, const QString &address);
/** /**
* @brief trustedFingerprintDatabase get the FingerprintDatabase for the trusted clients or trusted servers. * @brief trustedFingerprintDatabase get the FingerprintDatabase for the trusted clients or trusted servers.
@ -177,8 +168,9 @@ private:
bool m_clientErrorVisible = false; bool m_clientErrorVisible = false;
ServerConfig m_serverConfig; ServerConfig m_serverConfig;
deskflow::gui::CoreProcess m_coreProcess; deskflow::gui::CoreProcess m_coreProcess;
deskflow::gui::ServerConnection m_serverConnection; QSet<QString> m_ignoredClients;
deskflow::gui::ClientConnection m_clientConnection; bool m_newClientPromptShowing = false;
bool m_serverConfigDialogVisible = false;
QSize m_expandedSize = QSize(); QSize m_expandedSize = QSize();
QStringList m_checkedClients; QStringList m_checkedClients;
QStringList m_checkedServers; QStringList m_checkedServers;

View file

@ -1,28 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2024 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#pragma once
#include <QFile>
#include <QString>
#include "ScreenList.h"
namespace deskflow::gui {
// TODO: Remove this class, when the ServerConnectionTests are ported to QTests
class IServerConfig
{
public:
virtual ~IServerConfig() = default;
virtual bool isFull() const = 0;
virtual bool screenExists(const QString &screenName) const = 0;
virtual bool save(const QString &fileName) const = 0;
virtual void save(QFile &file) const = 0;
virtual const ScreenList &screens() const = 0;
};
} // namespace deskflow::gui

View file

@ -9,7 +9,6 @@
#include "base/NetworkProtocol.h" #include "base/NetworkProtocol.h"
#include "gui/Hotkey.h" #include "gui/Hotkey.h"
#include "gui/config/IServerConfig.h"
#include "gui/config/ScreenConfig.h" #include "gui/config/ScreenConfig.h"
#include "gui/config/ScreenList.h" #include "gui/config/ScreenList.h"
@ -31,21 +30,18 @@ const auto kDefaultProtocol = NetworkProtocol::Barrier;
} // namespace deskflow::gui } // namespace deskflow::gui
class ServerConfig : public ScreenConfig, public deskflow::gui::IServerConfig class ServerConfig : public ScreenConfig
{ {
friend class ServerConfigDialog; friend class ServerConfigDialog;
friend QTextStream &operator<<(QTextStream &outStream, const ServerConfig &config); friend QTextStream &operator<<(QTextStream &outStream, const ServerConfig &config);
public: public:
explicit ServerConfig(int columns = kDefaultColumns, int rows = kDefaultRows); explicit ServerConfig(int columns = kDefaultColumns, int rows = kDefaultRows);
~ServerConfig() override = default; ~ServerConfig() = default;
bool operator==(const ServerConfig &sc) const; bool operator==(const ServerConfig &sc) const;
// const ScreenList &screens() const
// Overrides
//
const ScreenList &screens() const override
{ {
return m_Screens; return m_Screens;
} }
@ -131,17 +127,10 @@ public:
} }
static size_t defaultClipboardSharingSize(); static size_t defaultClipboardSharingSize();
// bool save(const QString &fileName) const;
// Overrides bool screenExists(const QString &screenName) const;
// void save(QFile &file) const;
bool save(const QString &fileName) const override; bool isFull() const;
bool screenExists(const QString &screenName) const override;
void save(QFile &file) const override;
bool isFull() const override;
//
// New methods
//
void commit(); void commit();
int numScreens() const; int numScreens() const;
QString getServerName() const; QString getServerName() const;

View file

@ -1,75 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
* SPDX-FileCopyrightText: (C) 2021 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "ClientConnection.h"
#include "common/Settings.h"
#include <QHostAddress>
namespace deskflow::gui {
void ClientConnection::handleLogLine(const QString &logLine)
{
if (logLine.contains("disconnected from server")) {
m_supressMessage = false;
return;
}
if (logLine.contains("retry in ") && Settings::value(Settings::Client::DynamicConnectionRetry).toBool()) {
auto line = logLine.mid(logLine.indexOf("in ") + 3);
int seconds = line.mid(0, line.indexOf(" ")).toInt();
Q_EMIT updateTimeoutDelay(seconds);
return;
}
if (logLine.contains("connected to server")) {
m_supressMessage = true;
return;
}
if (logLine.contains("failed to connect to server")) {
if (m_supressMessage) {
qDebug("message already shown, skipping");
return;
}
// ignore the message if it's about the server refusing by name as
// this will trigger the server to show an 'add client' dialog.
if (logLine.contains("server refused client with our name")) {
qDebug("ignoring client name refused message");
return;
}
showMessage(logLine);
}
}
void ClientConnection::showMessage(const QString &logLine)
{
using enum deskflow::client::ErrorType;
if (logLine.isEmpty())
return;
const auto address = Settings::value(Settings::Client::RemoteHost).toString();
auto error = NoError;
if (logLine.contains("server already has a connected client with our name")) {
error = AlreadyConnected;
} else if (QHostAddress a(address); a.isNull()) {
qDebug("ip not detected, showing hostname error");
error = HostnameError;
} else {
qDebug("ip detected, showing generic error");
error = GenericError;
}
if (error == NoError)
return;
Q_EMIT requestShowError(error, address);
}
} // namespace deskflow::gui

View file

@ -1,48 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
* SPDX-FileCopyrightText: (C) 2021 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#pragma once
#include "common/Enums.h"
#include <QString>
#include <QWidget>
class QWidget;
namespace deskflow::gui {
class ClientConnection : public QObject
{
Q_OBJECT
public:
explicit ClientConnection(QWidget *parent) : m_pParent(parent)
{
// do nothing
}
void handleLogLine(const QString &line);
Q_SIGNALS:
/**
* @brief requestShowError, This signal is emitted when the client
* connection would like the owning process to report an error message
* @param error the type of error being reported
* @param address of the host
*/
void requestShowError(deskflow::client::ErrorType error, const QString &address);
void updateTimeoutDelay(int newTimeout);
private:
void showMessage(const QString &logLine);
QWidget *m_pParent;
bool m_supressMessage = false;
};
} // namespace deskflow::gui

View file

@ -99,7 +99,7 @@ QString CoreProcess::wrapIpv6(const QString &address)
// CoreProcess // CoreProcess
// //
CoreProcess::CoreProcess(const IServerConfig &serverConfig) CoreProcess::CoreProcess(const ServerConfig &serverConfig)
: m_serverConfig(serverConfig), : m_serverConfig(serverConfig),
m_daemonIpcClient{new ipc::DaemonIpcClient(this)} m_daemonIpcClient{new ipc::DaemonIpcClient(this)}
{ {
@ -430,7 +430,7 @@ void CoreProcess::stop(std::optional<ProcessMode> processModeOption)
if (m_coreIpcClient) { if (m_coreIpcClient) {
m_coreIpcClient->disconnectFromServer(); m_coreIpcClient->disconnectFromServer();
delete m_coreIpcClient; m_coreIpcClient->deleteLater();
m_coreIpcClient = nullptr; m_coreIpcClient = nullptr;
} }
@ -548,6 +548,22 @@ void CoreProcess::onCoreIpcMessageReceived(const QString &command, const QString
m_secureSocketVersion = args; m_secureSocketVersion = args;
Q_EMIT securityLevelChanged(args); Q_EMIT securityLevelChanged(args);
} }
} else if (command == "unrecognisedClient") {
Q_EMIT unrecognisedClient(args);
} else if (command == "connectionRefused") {
const auto metaEnum = QMetaEnum::fromType<deskflow::core::ConnectionRefusal>();
bool ok = false;
const auto reason =
static_cast<deskflow::core::ConnectionRefusal>(metaEnum.keyToValue(args.toUtf8().constData(), &ok));
if (ok) {
Q_EMIT connectionRefused(reason);
} else {
qWarning("core ipc got unknown connection refusal: %s", args.toUtf8().constData());
}
} else if (command == "retryIn") {
Q_EMIT retryIn(args.toInt());
} else if (command == "peerFingerprint") {
Q_EMIT peerFingerprint(args);
} }
} }

View file

@ -10,7 +10,7 @@
#include "common/Enums.h" #include "common/Enums.h"
#include "common/Settings.h" #include "common/Settings.h"
#include "gui/FileTail.h" #include "gui/FileTail.h"
#include "gui/config/IServerConfig.h" #include "gui/config/ServerConfig.h"
#include <QMutex> #include <QMutex>
#include <QObject> #include <QObject>
@ -29,8 +29,6 @@ class CoreProcess : public QObject
using ConnectionState = deskflow::core::ConnectionState; using ConnectionState = deskflow::core::ConnectionState;
using ProcessMode = Settings::ProcessMode; using ProcessMode = Settings::ProcessMode;
using ProcessState = deskflow::core::ProcessState; using ProcessState = deskflow::core::ProcessState;
using IServerConfig = deskflow::gui::IServerConfig;
Q_OBJECT Q_OBJECT
public: public:
@ -40,7 +38,7 @@ public:
StartFailed StartFailed
}; };
explicit CoreProcess(const IServerConfig &serverConfig); explicit CoreProcess(const ServerConfig &serverConfig);
void start(std::optional<ProcessMode> processMode = std::nullopt); void start(std::optional<ProcessMode> processMode = std::nullopt);
void stop(std::optional<ProcessMode> processMode = std::nullopt); void stop(std::optional<ProcessMode> processMode = std::nullopt);
@ -91,6 +89,10 @@ Q_SIGNALS:
void daemonIpcClientConnectionFailed(); void daemonIpcClientConnectionFailed();
void connectedClientsChanged(const QStringList &clients); void connectedClientsChanged(const QStringList &clients);
void securityLevelChanged(QString securityLevel); void securityLevelChanged(QString securityLevel);
void unrecognisedClient(const QString &clientName);
void connectionRefused(deskflow::core::ConnectionRefusal reason);
void retryIn(int seconds);
void peerFingerprint(const QString &fingerprint);
private Q_SLOTS: private Q_SLOTS:
void onProcessFinished(int exitCode, QProcess::ExitStatus); void onProcessFinished(int exitCode, QProcess::ExitStatus);
@ -116,7 +118,7 @@ private:
static QString processStateToString(const CoreProcess::ProcessState state); static QString processStateToString(const CoreProcess::ProcessState state);
static QString wrapIpv6(const QString &address); static QString wrapIpv6(const QString &address);
const IServerConfig &m_serverConfig; const ServerConfig &m_serverConfig;
QString m_address; QString m_address;
ProcessState m_processState = ProcessState::Stopped; ProcessState m_processState = ProcessState::Stopped;
ConnectionState m_connectionState = ConnectionState::Disconnected; ConnectionState m_connectionState = ConnectionState::Disconnected;

View file

@ -1,108 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
* SPDX-FileCopyrightText: (C) 2021 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "ServerConnection.h"
#include "ServerMessage.h"
#include "common/Settings.h"
#include <QPushButton>
namespace deskflow::gui {
ServerConnection::ServerConnection(QWidget *parent, IServerConfig &serverConfig)
: m_pParent(parent),
m_serverConfig(serverConfig)
{
}
void ServerConnection::handleLogLine(const QString &logLine)
{
ServerMessage message(logLine);
const auto &clientName = message.getClientName();
if (m_ignoredClients.contains(clientName)) {
qDebug("ignoring %s:", qPrintable(clientName));
return;
}
if (message.isDisconnectedMessage()) {
m_connectedClients.remove(clientName);
Q_EMIT clientsChanged(connectedClients());
return;
}
if (message.isConnectedMessage()) {
m_connectedClients.insert(clientName);
Q_EMIT clientsChanged(connectedClients());
return;
}
if (!message.isNewClientMessage()) {
return;
}
if (m_messageShowing) {
qDebug("new client message already shown, skipping for now");
return;
}
if (m_serverConfigDialogVisible) {
qDebug("server config dialog visible, skipping new client prompt");
return;
}
if (Settings::value(Settings::Server::ExternalConfig).toBool()) {
qDebug("external config enabled, skipping new client prompt");
return;
}
if (m_connectedClients.contains(clientName)) {
qDebug("already got request, skipping new client prompt for: %s", qPrintable(clientName));
return;
}
handleNewClient(clientName);
}
void ServerConnection::handleNewClient(const QString &clientName)
{
if (m_serverConfig.isFull()) {
qDebug("server config full, skipping new client prompt for: %s", qPrintable(clientName));
return;
}
if (m_serverConfig.screenExists(clientName)) {
qDebug("client already added, skipping new client prompt for: %s", qPrintable(clientName));
return;
}
m_messageShowing = true;
Q_EMIT requestNewClientPrompt(clientName);
}
void ServerConnection::handleNewClientResult(const QString &clientName, bool acceptClient)
{
m_messageShowing = false;
if (!acceptClient) {
qDebug("declined dialog, ignoring client: %s", qPrintable(clientName));
m_ignoredClients.insert(clientName);
return;
}
qDebug("accepted dialog, adding client: %s", qPrintable(clientName));
Q_EMIT configureClient(clientName);
m_connectedClients.insert(clientName);
Q_EMIT clientsChanged(connectedClients());
}
QStringList ServerConnection::connectedClients() const
{
return QStringList(m_connectedClients.begin(), m_connectedClients.end());
}
} // namespace deskflow::gui

View file

@ -1,49 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
* SPDX-FileCopyrightText: (C) 2021 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#pragma once
#include <QString>
#include "gui/Messages.h"
#include "gui/config/IServerConfig.h"
namespace deskflow::gui {
class ServerConnection : public QObject
{
Q_OBJECT
using IServerConfig = deskflow::gui::IServerConfig;
public:
explicit ServerConnection(QWidget *parent, IServerConfig &serverConfig);
void handleLogLine(const QString &logLine);
void serverConfigDialogVisible(bool visible)
{
m_serverConfigDialogVisible = visible;
}
QStringList connectedClients() const;
void handleNewClientResult(const QString &clientName, bool acceptClient);
Q_SIGNALS:
void requestNewClientPrompt(const QString &clientName);
void configureClient(const QString &clientName);
void clientsChanged(const QStringList &clients);
private:
void handleNewClient(const QString &clientName);
QWidget *m_pParent;
IServerConfig &m_serverConfig;
QSet<QString> m_connectedClients;
QSet<QString> m_ignoredClients;
bool m_messageShowing = false;
bool m_serverConfigDialogVisible = false;
};
} // namespace deskflow::gui

View file

@ -1,54 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2021 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "ServerMessage.h"
namespace deskflow::gui {
ServerMessage::ServerMessage(const QString &message) : m_message(message), m_clientName(parseClientName(message))
{
// do nothing
}
bool ServerMessage::isNewClientMessage() const
{
return m_message.contains("unrecognised client name");
}
bool ServerMessage::isExitMessage() const
{
return m_message.contains("process exited");
}
bool ServerMessage::isConnectedMessage() const
{
return m_message.contains("has connected");
}
bool ServerMessage::isDisconnectedMessage() const
{
return m_message.contains("has disconnected");
}
const QString &ServerMessage::getClientName() const
{
return m_clientName;
}
QString ServerMessage::parseClientName(const QString &line) const
{
QString clientName("Unknown");
auto nameStart = line.indexOf('"') + 1;
if (auto nameEnd = line.indexOf('"', nameStart); nameEnd > nameStart) {
clientName = line.mid(nameStart, nameEnd - nameStart);
}
return clientName;
}
} // namespace deskflow::gui

View file

@ -1,32 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2021 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#pragma once
#include <QString>
namespace deskflow::gui {
class ServerMessage
{
QString m_message;
QString m_clientName;
public:
explicit ServerMessage(const QString &message);
bool isNewClientMessage() const;
bool isExitMessage() const;
bool isConnectedMessage() const;
bool isDisconnectedMessage() const;
const QString &getClientName() const;
private:
QString parseClientName(const QString &line) const;
};
} // namespace deskflow::gui

View file

@ -23,7 +23,7 @@
using namespace deskflow::gui; using namespace deskflow::gui;
SettingsDialog::SettingsDialog(QWidget *parent, const IServerConfig &serverConfig) SettingsDialog::SettingsDialog(QWidget *parent, const ServerConfig &serverConfig)
: QDialog(parent), : QDialog(parent),
ui{std::make_unique<Ui::SettingsDialog>()}, ui{std::make_unique<Ui::SettingsDialog>()},
m_serverConfig(serverConfig) m_serverConfig(serverConfig)

View file

@ -9,7 +9,7 @@
#pragma once #pragma once
#include <QDialog> #include <QDialog>
#include "gui/config/IServerConfig.h" #include "gui/config/ServerConfig.h"
namespace Ui { namespace Ui {
class SettingsDialog; class SettingsDialog;
@ -17,13 +17,11 @@ class SettingsDialog;
class SettingsDialog : public QDialog class SettingsDialog : public QDialog
{ {
using IServerConfig = deskflow::gui::IServerConfig;
Q_OBJECT Q_OBJECT
public: public:
void extracted(); void extracted();
SettingsDialog(QWidget *parent, const IServerConfig &serverConfig); SettingsDialog(QWidget *parent, const ServerConfig &serverConfig);
~SettingsDialog() override; ~SettingsDialog() override;
Q_SIGNALS: Q_SIGNALS:
@ -86,5 +84,5 @@ private:
bool m_interfaceSetOnLoad = false; bool m_interfaceSetOnLoad = false;
std::unique_ptr<Ui::SettingsDialog> ui; std::unique_ptr<Ui::SettingsDialog> ui;
const IServerConfig &m_serverConfig; const ServerConfig &m_serverConfig;
}; };

View file

@ -6,15 +6,14 @@
#include "IpcClient.h" #include "IpcClient.h"
#include "common/VersionInfo.h"
#include <QDebug> #include <QDebug>
#include <QLocalSocket> #include <QLocalSocket>
#include <QTimer> #include <QTimer>
namespace deskflow::gui::ipc { namespace deskflow::gui::ipc {
const auto kTimeout = 1000;
const auto kRetryLimit = 3;
IpcClient::IpcClient(QObject *parent, const QString &socketName) IpcClient::IpcClient(QObject *parent, const QString &socketName)
: QObject(parent), : QObject(parent),
m_socket{new QLocalSocket(this)}, m_socket{new QLocalSocket(this)},
@ -48,6 +47,8 @@ void IpcClient::connectToServer()
void IpcClient::attemptConnection() void IpcClient::attemptConnection()
{ {
const auto kRetryLimit = 3;
if (m_retryCount >= kRetryLimit) { if (m_retryCount >= kRetryLimit) {
qWarning() << "ipc client failed to connect after" << kRetryLimit << "attempts"; qWarning() << "ipc client failed to connect after" << kRetryLimit << "attempts";
m_state = State::Unconnected; m_state = State::Unconnected;
@ -67,8 +68,9 @@ void IpcClient::attemptConnection()
connect( connect(
m_socket, &QLocalSocket::connected, this, m_socket, &QLocalSocket::connected, this,
[this] { [this] {
m_socket->write("hello\n"); const auto versionId = QStringLiteral("%1+%2").arg(kVersion, kVersionGitSha);
qDebug() << "ipc client sent hello"; m_socket->write(QString("hello=%1\n").arg(versionId).toUtf8());
qDebug() << "ipc client sent hello with version:" << versionId;
}, },
Qt::SingleShotConnection Qt::SingleShotConnection
); );
@ -141,10 +143,8 @@ void IpcClient::handleReadyRead()
continue; continue;
} }
if (m_state == State::Connecting && parts.at(0) == "hello") { if (m_state == State::Connecting) {
m_state = State::Connected; handleHandshakeMessage(parts);
qDebug() << "ipc client connected";
Q_EMIT connected();
continue; continue;
} }
@ -156,6 +156,26 @@ void IpcClient::handleReadyRead()
} }
} }
void IpcClient::handleHandshakeMessage(const QStringList &parts)
{
if (parts.at(0) != "hello") {
return;
}
const auto versionId = QStringLiteral("%1+%2").arg(kVersion, kVersionGitSha);
const auto serverVersion = parts.size() >= 2 ? parts.at(1) : QString();
if (serverVersion != versionId) {
qCritical() << "ipc version mismatch (client:" << versionId << "server:" << serverVersion << ")";
disconnectFromServer();
Q_EMIT connectionFailed();
return;
}
m_state = State::Connected;
qDebug() << "ipc client connected";
Q_EMIT connected();
}
void IpcClient::sendMessage(const QString &message) void IpcClient::sendMessage(const QString &message)
{ {
if (m_state != State::Connected) { if (m_state != State::Connected) {

View file

@ -55,6 +55,7 @@ protected:
private: private:
void attemptConnection(); void attemptConnection();
void handleHandshakeMessage(const QStringList &parts);
QLocalSocket *m_socket; QLocalSocket *m_socket;
State m_state{State::Unconnected}; State m_state{State::Unconnected};

View file

@ -11,7 +11,6 @@
#include "base/IEventQueue.h" #include "base/IEventQueue.h"
#include "base/Log.h" #include "base/Log.h"
#include "deskflow/AppUtil.h" #include "deskflow/AppUtil.h"
#include "deskflow/ipc/CoreIpc.h"
#include "deskflow/DeskflowException.h" #include "deskflow/DeskflowException.h"
#include "deskflow/IPlatformScreen.h" #include "deskflow/IPlatformScreen.h"
#include "deskflow/OptionTypes.h" #include "deskflow/OptionTypes.h"
@ -19,6 +18,7 @@
#include "deskflow/ProtocolTypes.h" #include "deskflow/ProtocolTypes.h"
#include "deskflow/Screen.h" #include "deskflow/Screen.h"
#include "deskflow/StreamChunker.h" #include "deskflow/StreamChunker.h"
#include "deskflow/ipc/CoreIpc.h"
#include "net/TCPSocket.h" #include "net/TCPSocket.h"
#include "server/ClientListener.h" #include "server/ClientListener.h"
#include "server/ClientProxy.h" #include "server/ClientProxy.h"
@ -235,6 +235,7 @@ void Server::adoptClient(BaseClientProxy *client)
// name must be in our configuration // name must be in our configuration
if (!m_config->isScreen(client->getName())) { if (!m_config->isScreen(client->getName())) {
LOG_WARN("unrecognised client name \"%s\", check server config", client->getName().c_str()); LOG_WARN("unrecognised client name \"%s\", check server config", client->getName().c_str());
ipcSendToClient("unrecognisedClient", QString::fromStdString(client->getName()));
closeClient(client, kMsgEUnknown); closeClient(client, kMsgEUnknown);
return; return;
} }

View file

@ -1,25 +1,9 @@
# SPDX-FileCopyrightText: (C) 2025 Deskflow Developers # SPDX-FileCopyrightText: (C) 2025 Deskflow Developers
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
create_test(
NAME ClientConnectionTests
DEPENDS gui
SOURCE ClientConnectionTests.cpp
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/gui"
)
create_test(
NAME ServerConnectionTests
DEPENDS gui
SOURCE ServerConnectionTests.cpp
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/gui"
)
create_test( create_test(
NAME NetworkMonitorTests NAME NetworkMonitorTests
DEPENDS gui DEPENDS gui
SOURCE NetworkMonitorTests.cpp SOURCE NetworkMonitorTests.cpp
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/gui" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/gui"
) )

View file

@ -1,130 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello <sithlord48@gmail.com>
* SPDX-FileCopyrightText: (C) 2024 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "ClientConnectionTests.h"
#include "gui/core/ClientConnection.h"
#include <common/Settings.h>
#include <QSignalSpy>
using namespace deskflow::gui;
void ClientConnectionTests::initTestCase()
{
QDir dir;
QVERIFY(dir.mkpath(m_settingsPath));
QFile oldSettings(m_settingsFile);
if (oldSettings.exists())
oldSettings.remove();
Settings::setSettingsFile(m_settingsFile);
Settings::setStateFile(m_stateFile);
}
void ClientConnectionTests::handleLogLine_alreadyConnected_showError()
{
ClientConnection clientConnection(nullptr);
const auto serverName = QStringLiteral("test server");
Settings::setValue(Settings::Client::RemoteHost, serverName);
QSignalSpy spy(&clientConnection, &ClientConnection::requestShowError);
QVERIFY(spy.isValid());
clientConnection.handleLogLine(
"failed to connect to server\n"
"server already has a connected client with our name"
);
QCOMPARE(spy.count(), 1);
}
void ClientConnectionTests::handleLogLine_withHostname_showError()
{
ClientConnection clientConnection(nullptr);
const auto serverName = QStringLiteral("test server");
Settings::setValue(Settings::Client::RemoteHost, serverName);
QSignalSpy spy(&clientConnection, &ClientConnection::requestShowError);
QVERIFY(spy.isValid());
clientConnection.handleLogLine("failed to connect to server");
QCOMPARE(spy.count(), 1);
}
void ClientConnectionTests::handleLogLine_withIpAddress_showError()
{
ClientConnection clientConnection(nullptr);
const auto serverName = QStringLiteral("1.1.1.1");
Settings::setValue(Settings::Client::RemoteHost, serverName);
QSignalSpy spy(&clientConnection, &ClientConnection::requestShowError);
QVERIFY(spy.isValid());
clientConnection.handleLogLine("failed to connect to server");
QCOMPARE(spy.count(), 1);
}
void ClientConnectionTests::handleLogLine_serverRefusedClient_shouldNotShowError()
{
ClientConnection clientConnection(nullptr);
QSignalSpy spy(&clientConnection, &ClientConnection::requestShowError);
QVERIFY(spy.isValid());
clientConnection.handleLogLine(
"failed to connect to server\n"
"server refused client with our name"
);
QCOMPARE(spy.count(), 0);
}
void ClientConnectionTests::handleLogLine_connected_shouldPreventFutureError()
{
ClientConnection clientConnection(nullptr);
clientConnection.handleLogLine("connected to server");
QSignalSpy spy(&clientConnection, &ClientConnection::requestShowError);
QVERIFY(spy.isValid());
clientConnection.handleLogLine("failed to connect to server");
QCOMPARE(spy.count(), 0);
}
void ClientConnectionTests::handleLogLine_connectToggled_showAfterDisconnect()
{
ClientConnection clientConnection(nullptr);
clientConnection.handleLogLine("connected to server");
QSignalSpy spy(&clientConnection, &ClientConnection::requestShowError);
QVERIFY(spy.isValid());
clientConnection.handleLogLine("failed to connect to server");
clientConnection.handleLogLine("disconnected from server");
clientConnection.handleLogLine("failed to connect to server");
QCOMPARE(spy.count(), 1);
}
void ClientConnectionTests::handleLogLine_otherMessage_shouldNotShowError()
{
ClientConnection clientConnection(nullptr);
QSignalSpy spy(&clientConnection, &ClientConnection::requestShowError);
QVERIFY(spy.isValid());
clientConnection.handleLogLine("hello world");
QCOMPARE(spy.count(), 0);
}
QTEST_MAIN(ClientConnectionTests)

View file

@ -1,27 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello <sithlord48@gmail.com>
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include <QTest>
class ClientConnectionTests : public QObject
{
Q_OBJECT
private Q_SLOTS:
// Test are run in order top to bottom
void initTestCase();
void handleLogLine_alreadyConnected_showError();
void handleLogLine_withHostname_showError();
void handleLogLine_withIpAddress_showError();
void handleLogLine_serverRefusedClient_shouldNotShowError();
void handleLogLine_connected_shouldPreventFutureError();
void handleLogLine_connectToggled_showAfterDisconnect();
void handleLogLine_otherMessage_shouldNotShowError();
private:
inline static const QString m_settingsPath = QStringLiteral("tmp/test");
inline static const QString m_settingsFile = QStringLiteral("%1/Deskflow.conf").arg(m_settingsPath);
inline static const QString m_stateFile = QStringLiteral("%1/Deskflow.state").arg(m_settingsPath);
};

View file

@ -1,101 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello <sithlord48@gmail.com>
* SPDX-FileCopyrightText: (C) 2024 Symless Ltd.
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "ServerConnectionTests.h"
#include "gui/config/ServerConfig.h"
#include "gui/core/ServerConnection.h"
#include <common/Settings.h>
#include <QSignalSpy>
using namespace deskflow::gui;
class FullServerConfig : public ServerConfig
{
public:
bool isFull() const override
{
return true;
}
};
class ScreenExistsServerConfig : public ServerConfig
{
public:
bool screenExists(const QString &screenName) const override
{
return true;
}
};
void ServerConnectionTests::initTestCase()
{
QDir dir;
QVERIFY(dir.mkpath(m_settingsPath));
QFile oldSettings(m_settingsFile);
if (oldSettings.exists())
oldSettings.remove();
Settings::setSettingsFile(m_settingsFile);
Settings::setStateFile(m_stateFile);
}
void ServerConnectionTests::handleLogLine_newClient_shouldShowPrompt()
{
ServerConfig m_serverConfig;
ServerConnection serverConnection(nullptr, m_serverConfig);
QString clientName = "test client";
QSignalSpy spy(&serverConnection, &ServerConnection::requestNewClientPrompt);
QVERIFY(spy.isValid());
serverConnection.handleLogLine(R"(unrecognised client name "test client")");
QCOMPARE(spy.count(), 1);
}
void ServerConnectionTests::handleLogLine_ignoredClient_shouldNotShowPrompt()
{
ServerConfig m_serverConfig;
ServerConnection serverConnection(nullptr, m_serverConfig);
QString clientName = "test client";
serverConnection.handleNewClientResult(clientName, false);
QSignalSpy spy(&serverConnection, &ServerConnection::requestNewClientPrompt);
QVERIFY(spy.isValid());
serverConnection.handleLogLine(R"(unrecognised client name "test client")");
QCOMPARE(spy.count(), 0);
}
void ServerConnectionTests::handleLogLine_serverConfigFull_shouldNotShowPrompt()
{
FullServerConfig m_serverConfig;
ServerConnection serverConnection(nullptr, m_serverConfig);
QSignalSpy spy(&serverConnection, &ServerConnection::requestNewClientPrompt);
QVERIFY(spy.isValid());
serverConnection.handleLogLine(R"(unrecognised client name "test client")");
QCOMPARE(spy.count(), 0);
}
void ServerConnectionTests::handleLogLine_screenExists_shouldNotShowPrompt()
{
ScreenExistsServerConfig m_serverConfig;
ServerConnection serverConnection(nullptr, m_serverConfig);
QSignalSpy spy(&serverConnection, &ServerConnection::requestNewClientPrompt);
QVERIFY(spy.isValid());
serverConnection.handleLogLine(R"(unrecognised client name "test client")");
QCOMPARE(spy.count(), 0);
}
QTEST_MAIN(ServerConnectionTests)

View file

@ -1,24 +0,0 @@
/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2025 Chris Rizzitello <sithlord48@gmail.com>
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include <QTest>
class ServerConnectionTests : public QObject
{
Q_OBJECT
private Q_SLOTS:
// Test are run in order top to bottom
void initTestCase();
void handleLogLine_newClient_shouldShowPrompt();
void handleLogLine_ignoredClient_shouldNotShowPrompt();
void handleLogLine_serverConfigFull_shouldNotShowPrompt();
void handleLogLine_screenExists_shouldNotShowPrompt();
private:
inline static const QString m_settingsPath = QStringLiteral("tmp/test");
inline static const QString m_settingsFile = QStringLiteral("%1/Deskflow.conf").arg(m_settingsPath);
inline static const QString m_stateFile = QStringLiteral("%1/Deskflow.state").arg(m_settingsPath);
};