Refactored core process management and IPC client (#7425)

* Also ignore `command_pre` for Fedora itself

* Improve warning message

* Fixed layout issues on main window

* Restore fusion theme for Windows dark mode

* Further correct main window layout

* Set CWD for launch on Windows to same dir as .env

* Use signals for tray icon instead of callback

* Reduce complexity for setting tray icon

* Further reduce tray icon complexity

* Reduce tray retry time

* Fixed tray not showing on macOS

* Refactor function names

* Move tray icon to lib

* Decouple server connection class

* Move server connection to lib

* Move client config to lib

* Remove redundant forward declarations

* Fixed some namespaces in the new lib

* Move core process code to new class

* Improve member names on new process class

* Remove copy/pasta code

* Move OSX helpers to lib

* Add .mm to lib config

* Fixed copyright

* Improve reliability of log line handling

* Fixed TLS certificate generate bugs

* Remove client list

* Refactor core process handling to fix various problems

* Fixed process/connection status bugs

* Fixed function signature issue on macOS

* Fixed override warnings

* Fixed string format warning

* Save `wasStarted` state and use that instead

* Use only filename in dialog

* Use lambda for simple slot

* Scroll to bottom if at bottome

* Set value based on position before text added and set horizontal scroll too

* Add 1px tollerance for Linux

* Simplify start/stop mutex

* Always stop service on restart

* Increase scroll bottom threshold to 2

* Log warning instead of critical

* Fixed long-standing dir CD-up hack

* Remove include

* Remove include

* Fixed rogue dumbisense includes

* Account for optional distro_like

* Add QAction include to solve incomplete type

* Remove rogue #pragma

* Static cast log value

* Solve event queue delete warning

* Fixed integ test on Windows 10

* Reduce enum verbosity

* Use static instance instead of global

* Make function const

* Use unique_ptr instead of new and delete, and made some functions const

* Fixed smart pointer use

* Fixed variable shadowing

* Fixed wrong use of using

* Fixed missing namespace using

* Simplify TLS error handling

* Improve UX around certificate errors and success

* Decouple app config from core process through interface

* First iteration of core process test

* Mark dtor as override

* Rename attach launch entry

* Add TODO

* Create proxy for process

* Move IPC client to deps

* Fixed warnings

* Reorganize new GUI lib dir structure

* Update includes to reflect new paths

* Reorg GUI tests

* Abstract IPC client

* Refactor about screen

* Fixed .ui warnings

* Remove redundant include

* Fixed typo

* Fixed typos and add spelling

* Fixed misleading function name

* Improve comment

* Improve code coverage for core process

* Remove noisy log line

* Make global const

* Use default dtor

* Use vector instead of new

* Make ctor explicit

* Make ctor explicit (2)

* Use enum class

* Fixed bad enum

* Stub out core tool

* Stub out dir operation

* Extract byte functions

* Add missing return path

* Simplify byte/int functions

* Fix truncation

* Use ctor member assignment

* Fixed segfault in process proxy

* Fixed print warning

* Make function const

* Cleanup header

* Fixed missing name

* Update ChangeLog

* Make more functions const
This commit is contained in:
Nick Bolton 2024-08-07 15:05:18 +01:00 committed by GitHub
parent 0849010e38
commit 7984535d38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
97 changed files with 3024 additions and 1734 deletions

View file

@ -12,6 +12,12 @@
# Version checker URL to use (useful for testing)
# SYNERGY_VERSION_URL="http://localhost:8787?version=v1"
# Enable debug logging in the GUI (on by default in debug mode)
# SYNERGY_GUI_DEBUG=true
# Enable verbose logging in the GUI (always off by default)
# SYNERGY_GUI_VERBOSE=true
#
# Build
#

View file

@ -26,8 +26,8 @@ env:
SYNERGY_VERSION: ${{ github.event.inputs.version || github.event.release.tag_name }}
SYNERGY_PRODUCT_NAME: ${{ vars.SYNERGY_PRODUCT_NAME }}
SYNERGY_PACKAGE_PREFIX: ${{ vars.SYNERGY_PACKAGE_PREFIX }}
SYNERGY_ENABLE_LICENSING: ${{ vars.SYNERGY_ENABLE_LICENSING }}
SYNERGY_SHOW_DEV_THANKS: ${{ vars.SYNERGY_SHOW_DEV_THANKS }}
SYNERGY_LICENSED_PRODUCT: ${{ vars.SYNERGY_LICENSED_PRODUCT }}
SYNERGY_ENABLE_ACTIVATION: ${{ vars.SYNERGY_ENABLE_ACTIVATION }}
PACKAGE_BUILD: ${{ !github.event.pull_request.draft }}
PACKAGE_UPLOAD: ${{ !github.event.pull_request.draft && github.event_name != 'schedule' }}
UPLOAD_TO_GITHUB: ${{ github.event_name == 'pull_request' && !github.event.pull_request.draft }}

4
.vscode/launch.json vendored
View file

@ -11,7 +11,7 @@
{
"name": "gui windows",
"type": "cppvsdbg",
"cwd": "${workspaceRoot}/build/bin",
"cwd": "${workspaceRoot}",
"request": "launch",
"program": "${workspaceFolder}/build/bin/synergy",
"internalConsoleOptions": "openOnSessionStart",
@ -60,7 +60,7 @@
"preLaunchTask": "build"
},
{
"name": "daemon windows attach",
"name": "windows attach",
"type": "cppvsdbg",
"request": "attach",
"processId": "${command:pickProcess}"

View file

@ -68,6 +68,7 @@ Enhancements:
- #7422 Handle empty value for computer name setting
- #7423 Refactor local/global save/load and show dev thanks message
- #7424 Add Alma Linux and Rocky Linux runners
- #7425 Refactored core process management and IPC client
# 1.14.6

View file

@ -45,14 +45,20 @@ macro(configure_definitions)
add_definitions(-DGIT_SHA_SHORT="${GIT_SHA_SHORT}")
endif()
if(ENABLE_LICENSING)
message(STATUS "Licensing enabled")
add_definitions(-DSYNERGY_ENABLE_LICENSING=1)
if(NOT "$ENV{SYNERGY_PRODUCT_NAME}" STREQUAL "")
set(PRODUCT_NAME $ENV{SYNERGY_PRODUCT_NAME})
endif()
if(LICENSED_PRODUCT)
message(STATUS "Licensed product")
add_definitions(-DSYNERGY_LICENSED_PRODUCT=1)
if(ENABLE_ACTIVATION)
message(STATUS "Activation enabled")
add_definitions(-DSYNERGY_ENABLE_ACTIVATION=1)
endif()
else()
set(PRODUCT_NAME "Synergy 1 Community Edition")
if(NOT "$ENV{SYNERGY_PRODUCT_NAME}" STREQUAL "")
set(PRODUCT_NAME $ENV{SYNERGY_PRODUCT_NAME})
endif()
endif()
if(PRODUCT_NAME)
@ -110,10 +116,13 @@ macro(configure_options)
# coverage is off by default because it's GCC only and a developer preference.
set(DEFAULT_ENABLE_COVERAGE OFF)
# licensing is off by default to make life easier for contributors.
set(DEFAULT_ENABLE_LICENSING OFF)
# licensed product is off by default to show links to github, etc.
set(DEFAULT_LICENSED_PRODUCT OFF)
# by default, show the dev thanks message.
# activation is off by default to make life easier for contributors.
set(DEFAULT_ENABLE_ACTIVATION OFF)
# by default, show the dev thanks message, guides contributions, etc.
set(DEFAULT_SHOW_DEV_THANKS ON)
if("$ENV{SYNERGY_BUILD_MINIMAL}" STREQUAL "true")
@ -129,8 +138,12 @@ macro(configure_options)
set(DEFAULT_BUILD_UNIFIED ON)
endif()
if("$ENV{SYNERGY_ENABLE_LICENSING}" STREQUAL "true")
set(DEFAULT_ENABLE_LICENSING ON)
if("$ENV{SYNERGY_ENABLE_ACTIVATION}" STREQUAL "true")
set(DEFAULT_ENABLE_ACTIVATION ON)
endif()
if("$ENV{SYNERGY_LICENSED_PRODUCT}" STREQUAL "true")
set(DEFAULT_LICENSED_PRODUCT ON)
endif()
if("$ENV{SYNERGY_ENABLE_COVERAGE}" STREQUAL "true")
@ -145,7 +158,8 @@ macro(configure_options)
option(BUILD_INSTALLER "Build installer" ${DEFAULT_BUILD_INSTALLER})
option(BUILD_TESTS "Build tests" ${DEFAULT_BUILD_TESTS})
option(BUILD_UNIFIED "Build unified binary" ${DEFAULT_BUILD_UNIFIED})
option(ENABLE_LICENSING "Enable licensing" ${DEFAULT_ENABLE_LICENSING})
option(ENABLE_ACTIVATION "Enable activation" ${DEFAULT_ENABLE_ACTIVATION})
option(LICENSED_PRODUCT "Show licensing info" ${DEFAULT_LICENSED_PRODUCT})
option(ENABLE_COVERAGE "Enable test coverage" ${DEFAULT_ENABLE_COVERAGE})
option(SHOW_DEV_THANKS "Show dev thanks message" ${DEFAULT_SHOW_DEV_THANKS})

View file

@ -4,23 +4,46 @@
"dictionaryDefinitions": [],
"dictionaries": [],
"words": [
"Schoeneman",
"aqtinstall",
"Axelson",
"Breen",
"codesign",
"codesigning",
"contribs",
"Daun",
"distros",
"dmgbuild",
"dotenv",
"Evenson",
"Feder",
"gdrive",
"Hadzhylov",
"Hetu",
"keychain",
"Keychains",
"Kutytska",
"Lanz",
"LLDB",
"Lysytsia",
"macdeployqt",
"msvc",
"noquote",
"notarytool",
"Oleksandr",
"Olena",
"outputdir",
"Petroules",
"Poschta",
"Priddy",
"runas",
"Sbârnea",
"Schoeneman",
"Serhii",
"Sorin",
"synergyc",
"synergyd",
"synergys",
"Volker",
"winget"
],
"ignoreWords": [],

View file

@ -133,9 +133,12 @@ class Dependencies:
print("Running dependencies prerequisites command")
check = True
if distro_like and "fedora" in distro_like:
print("Fedora-like detected, ignoring return code")
# On Fedora, dnf update returns code 100 when updates are available.
if distro == "fedora" or (distro_like and "fedora" in distro_like):
print(
"Fedora-like detected, "
"ignoring return code on dependencies prerequisites command"
)
# On Fedora-like, dnf update returns code 100 when updates are available.
check = False
linux.run_command(command_pre, check)

View file

@ -29,11 +29,6 @@ file(
src/widgets/*)
file(GLOB GUI_UI_FILES src/*.ui)
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
file(GLOB GUI_MAC_SOURCE_FILES src/*.mm)
list(APPEND GUI_SOURCE_FILES ${GUI_MAC_SOURCE_FILES})
endif()
if(WIN32)
set(GUI_RC_FILES res/win/Synergy.rc ${CMAKE_BINARY_DIR}/src/version.rc)
endif()

View file

@ -21,7 +21,7 @@
#include <QDateTime>
#if defined(Q_OS_MAC)
#include "OSXHelpers.h"
#include "gui/OSXHelpers.h"
#endif
#include "gui/version.h"
@ -29,8 +29,11 @@
AboutDialog::AboutDialog(MainWindow *parent)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint),
Ui::AboutDialogBase() {
setupUi(this);
this->setFixedSize(this->size());
m_pLabelSynergyVersion->setText(synergy::gui::version());
QString buildDateString = QString::fromLocal8Bit(__DATE__).simplified();
@ -59,21 +62,47 @@ void AboutDialog::updateLogo() const {
}
QString AboutDialog::getImportantDevelopers() const {
return QString(
// The ultimate creator
"Chris Schoeneman, "
QStringList awesomePeople;
// Precursor developers
"Richard Lee, Adam Feder, "
// Chris is the ultimate creator, and the one who started it all in 2001.
awesomePeople << "Chris Schoeneman";
// Contributors
"Nick Bolton, Volker Lanz, Ryan Breen, Guido Poschta, "
"Bertrand Landry Hetu, Tom Chadwick, Brent Priddy, Jason Axelson, "
"Jake Petroules, Sorin Sbârnea, "
// Richard and Adam developed CosmoSynergy, the 90's predecessor project.
awesomePeople << "Richard Lee" << "Adam Feder";
// Symless employees
"Kyle Bloom, Daun Chung, Serhii Hadzhylov, "
"Oleksandr Lysytsia, Olena Kutytska.");
// Nick continued the legacy in 2009 started by Chris.
awesomePeople << "Nick Bolton";
// Volker wrote the first version of the GUI (QSynergy) in 2008.
awesomePeople << "Volker Lanz";
// Re-ignited the project in 2008 and rebuilt the community.
awesomePeople << "Sorin Sbârnea";
// Contributors of bug fixes in the early days.
awesomePeople << "Ryan Breen"
<< "Guido Poschta"
<< "Bertrand Landry Hetu"
<< "Tom Chadwick"
<< "Brent Priddy"
<< "Jason Axelson"
<< "Jake Petroules";
// Symless employees (in order of joining).
awesomePeople << "Kyle Bloom"
<< "Daun Chung"
<< "Serhii Hadzhylov"
<< "Oleksandr Lysytsia"
<< "Olena Kutytska"
<< "Owen Phillips"
<< "Daniel Evenson";
for (auto &person : awesomePeople) {
// prevent names from breaking on the space when wrapped
person = person.replace(" ", QString::fromUtf8("&nbsp;"));
}
return awesomePeople.join(", ") + ".";
}
QString AboutDialog::getCopyright() const {

View file

@ -12,28 +12,10 @@
<rect>
<x>0</x>
<y>0</y>
<width>450</width>
<height>495</height>
<width>600</width>
<height>400</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>450</width>
<height>495</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>450</width>
<height>495</height>
</size>
</property>
<property name="windowTitle">
<string>About Synergy</string>
</property>
@ -90,7 +72,6 @@
<widget class="QLabel" name="m_pTitleLabel">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@ -161,6 +142,9 @@
<property name="text">
<string>Developers</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>

View file

@ -19,10 +19,10 @@
#include "CancelActivationDialog.h"
#include "MainWindow.h"
#include "gui/AppConfig.h"
#include "gui/LicenseHandler.h"
#include "gui/config/AppConfig.h"
#include "gui/constants.h"
#include "gui/license_notices.h"
#include "gui/license/LicenseHandler.h"
#include "gui/license/license_notices.h"
#include "gui/styles.h"
#include "license/ProductEdition.h"
#include "license/parse_serial_key.h"

View file

@ -17,7 +17,7 @@
#pragma once
#include "gui/LicenseHandler.h"
#include "gui/license/LicenseHandler.h"
#include <QDialog>
@ -42,8 +42,8 @@ public:
};
public slots:
void reject();
void accept();
void reject() override;
void accept() override;
protected:
void refreshSerialKey();

File diff suppressed because it is too large Load diff

View file

@ -24,21 +24,19 @@
#include <QSettings>
#include <QSystemTrayIcon>
#include <QThread>
#include <memory>
#include "ui_MainWindowBase.h"
#include "ActivationDialog.h"
#include "ClientConnection.h"
#include "ServerConfig.h"
#include "ServerConnection.h"
#include "TrayIcon.h"
#include "global/Ipc.h"
#include "gui/AppConfig.h"
#include "gui/ConfigScopes.h"
#include "gui/QIpcClient.h"
#include "gui/TlsUtility.h"
#include "gui/TrayIcon.h"
#include "gui/VersionChecker.h"
#include "gui/config/AppConfig.h"
#include "gui/config/ConfigScopes.h"
#include "gui/core/ClientConnection.h"
#include "gui/core/CoreProcess.h"
#include "gui/core/ServerConnection.h"
#include "gui/tls/TlsUtility.h"
#include "ui_MainWindowBase.h"
class QAction;
class QMenu;
@ -53,35 +51,22 @@ class QRadioButton;
class QMessageBox;
class QAbstractButton;
class LogDialog;
class QSynergyApplication;
class SetupWizard;
class DataDownloader;
class CommandProcess;
class TlsCertificate;
class MainWindow : public QMainWindow, public Ui::MainWindowBase {
using CoreMode = synergy::gui::CoreProcess::Mode;
using CoreProcess = synergy::gui::CoreProcess;
Q_OBJECT
friend class QSynergyApplication;
friend class SetupWizard;
friend class ActivationDialog;
friend class SettingsDialog;
friend class ServerConnection;
friend class ClientConnection;
public:
enum class CoreState {
Disconnected,
Connecting,
Connected,
Listening,
PendingRetry
};
enum class CoreMode { None, Client, Server };
enum class LogLevel { Error, Info };
enum class RuningState { Started, Stopped };
public:
explicit MainWindow(
@ -89,17 +74,12 @@ public:
~MainWindow() override;
void setVisible(bool visible) override;
CoreMode coreMode() const;
QString coreModeString() const;
CoreMode coreMode() const { return m_CoreProcess.mode(); }
QString address() const;
QString appPath(const QString &name) const;
void open();
ServerConfig &serverConfig() { return m_ServerConfig; }
void autoAddScreen(const QString name);
int showActivationDialog();
void appendLogInfo(const QString &text);
void appendLogDebug(const QString &text);
void appendLogError(const QString &text);
signals:
void created();
@ -115,19 +95,19 @@ private slots:
void onAppConfigTlsChanged();
void onAppConfigScreenNameChanged();
void onAppConfigInvertConnection();
void onCoreProcessStarting();
void onCoreProcessError(CoreProcess::Error error);
void onCoreConnectionStateChanged(CoreProcess::ConnectionState state);
void onCoreProcessStateChanged(CoreProcess::ProcessState state);
void onCoreProcessSecureSocket(bool enabled);
void onLicenseHandlerSerialKeyChanged(const QString &serialKey);
void onLicenseHandlerInvalidLicense();
void onIpcClientReadLogLine(const QString &text);
void onIpcClientErrorMessage(const QString &text);
void onIpcClientInfoMessage(const QString &text);
void onCoreProcessFinished(int exitCode, QProcess::ExitStatus);
void onVersionCheckerUpdateFound(const QString &version);
void onTrayIconCreate(QSystemTrayIcon::ActivationReason reason);
void onTrayIconActivated(QSystemTrayIcon::ActivationReason reason);
void onActionStartCoreTriggered();
void onActionStopCoreTriggered();
void onCoreProcessReadyReadStandardOutput();
void onCoreProcessReadyReadStandardError();
void onWindowSaveTimerTimeout();
void onServerConnectionConfigureClient(const QString &clientName);
// autoconnect slots
void on_m_pButtonApply_clicked();
@ -145,6 +125,8 @@ private slots:
void on_m_pActivate_triggered();
void on_m_pLineEditHostname_returnPressed();
void on_m_pLineEditClientIp_returnPressed();
void on_m_pLineEditHostname_textChanged(const QString &text);
void on_m_pLineEditClientIp_textChanged(const QString &text);
private:
AppConfig &appConfig() { return m_AppConfig; }
@ -154,44 +136,32 @@ private:
void createTrayIcon();
void applyConfig();
void applyCloseToTray() const;
void setIcon(CoreState state) const;
void setCoreState(CoreState state);
void setIcon(CoreProcess::ConnectionState state);
bool checkForApp(int which, QString &app);
bool clientArgs(QStringList &args, QString &app);
bool serverArgs(QStringList &args, QString &app);
void setStatus(const QString &status);
void sendIpcMessage(IpcMessageType type, const char *buffer, bool showErrors);
void updateFromLogLine(const QString &line);
QString getIPAddresses() const;
void stopService();
void stopDesktop();
void enableServer(bool enable);
void enableClient(bool enable);
QString getProfileRootForArg() const;
void checkConnected(const QString &line);
void checkFingerprint(const QString &line);
bool checkSecureSocket(const QString &line);
void checkLicense(const QString &line);
QString getTimeStamp() const;
void restartCore();
void showEvent(QShowEvent *) override;
void closeEvent(QCloseEvent *event) override;
void secureSocket(bool secureSocket);
void windowStateChanged();
void connectSlots() const;
void connectSlots();
void updateWindowTitle();
void processCoreLogLine(const QString &line);
void startCore();
void onCoreProcessRetryStart();
void handleLogLine(const QString &line);
void updateLocalFingerprint();
void updateScreenName();
void saveSettings();
QString configFilename();
bool isCoreActive() const;
void showConfigureServer(const QString &message);
void showConfigureServer() { showConfigureServer(""); }
void showLicenseNotice();
void stopCore();
void restoreWindow();
void saveWindow();
void setupControls();
@ -200,37 +170,28 @@ private:
void showFirstRunMessage();
void showDevThanksMessage();
QString productName() const;
#ifdef Q_OS_MAC
void checkOSXNotification(const QString &line);
#endif
void updateStatus();
VersionChecker m_VersionChecker;
QIpcClient m_IpcClient;
TrayIcon m_TrayIcon;
QMutex m_StopDesktopMutex;
synergy::gui::TrayIcon m_TrayIcon;
bool m_ActivationDialogRunning = false;
QStringList m_PendingClientNames;
RuningState m_ExpectedRunningState = RuningState::Stopped;
std::unique_ptr<QProcess> m_pCoreProcess;
QMenuBar *m_pMenuBar = nullptr;
QMenu *m_pMenuFile = nullptr;
QMenu *m_pMenuEdit = nullptr;
QMenu *m_pMenuWindow = nullptr;
QMenu *m_pMenuHelp = nullptr;
QAbstractButton *m_pCancelButton = nullptr;
CoreState m_CoreState = CoreState::Disconnected;
bool m_AlreadyHidden = false;
bool m_SecureSocket = false;
QString m_SecureSocketVersion = "";
bool m_SaveWindow = false;
LicenseHandler m_LicenseHandler;
synergy::gui::ConfigScopes &m_ConfigScopes;
AppConfig &m_AppConfig;
ServerConfig m_ServerConfig;
ServerConnection m_ServerConnection;
ClientConnection m_ClientConnection;
synergy::gui::CoreProcess m_CoreProcess;
synergy::gui::ServerConnection m_ServerConnection;
synergy::gui::ClientConnection m_ClientConnection;
synergy::gui::TlsUtility m_TlsUtility;
QTimer m_WindowSaveTimer;
};

View file

@ -25,16 +25,13 @@
<property name="windowTitle">
<string>Synergy</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<widget class="QWidget" name="m_pWidgetTopLevel">
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="spacing">
<number>0</number>
</property>
<property name="topMargin">
<number>20</number>
<number>10</number>
</property>
<item>
<layout class="QHBoxLayout" name="m_layout1">
<layout class="QHBoxLayout" name="m_layoutName">
<item>
<widget class="QLabel" name="m_pLabelComputerName">
<property name="text">
@ -67,22 +64,6 @@
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="m_pLabelIpAddresses">
<property name="toolTip">
@ -94,7 +75,7 @@
</widget>
</item>
<item>
<widget class="QWidget" name="m_pMainWidgets" native="true">
<widget class="QWidget" name="m_pWidgetModes" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -102,6 +83,21 @@
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="spacing">
<number>20</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="m_pGroupServer">
<property name="sizePolicy">
@ -116,6 +112,9 @@
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="m_pRadioGroupServer">
<property name="text">
@ -281,6 +280,9 @@
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="m_pRadioGroupClient">
<property name="text">
@ -388,41 +390,15 @@
</widget>
</item>
<item>
<widget class="QLabel" name="m_pLabelLogs">
<property name="text">
<string>Logs</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QFrame" name="m_pGroupLog">
<widget class="QGroupBox" name="m_pGroupLog">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
<property name="title">
<string>Logs</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
@ -469,16 +445,13 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QHBoxLayout" name="m_pLayoutActions">
<property name="spacing">
<number>6</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="topMargin">
<number>16</number>
</property>
<item>
<widget class="QLabel" name="m_pLabelPadlock">
<property name="enabled">
@ -599,9 +572,9 @@
</property>
</action>
<action name="m_pActionStartCore">
<property name="enabled">
<bool>false</bool>
</property>
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Start</string>
</property>

View file

@ -24,4 +24,8 @@
#include <QtGui>
QSynergyApplication::QSynergyApplication(int &argc, char **argv)
: QApplication(argc, argv) {}
: QApplication(argc, argv) {
// causes dark mode to be used on some OS (e.g. Windows)
setStyle("fusion");
}

View file

@ -27,6 +27,8 @@
#include <QtCore>
#include <QtGui>
using namespace synergy::gui;
ScreenSettingsDialog::ScreenSettingsDialog(
QWidget *parent, Screen *pScreen, const ScreenList *pScreens)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint),

View file

@ -340,8 +340,7 @@ int ServerConfig::autoAddScreen(const QString name) {
}
if (findScreenName(name, targetIndex)) {
m_pMainWindow->appendLogDebug(
QString("ignoring screen already in config: %1").arg(name));
qDebug("ignoring screen already in config: %s", qPrintable(name));
return kAutoAddScreenIgnore;
}
@ -428,7 +427,7 @@ bool ServerConfig::isFull() const {
return isFull;
}
bool ServerConfig::isScreenExists(const QString &screenName) const {
bool ServerConfig::screenExists(const QString &screenName) const {
bool isExists = false;
for (const auto &screen : screens()) {

View file

@ -21,6 +21,7 @@
#include "Hotkey.h"
#include "ScreenConfig.h"
#include "ScreenList.h"
#include "gui/config/IServerConfig.h"
#include <QList>
@ -35,9 +36,8 @@ class ServerConfigDialog;
class MainWindow;
class AppConfig;
class ServerConfig : public ScreenConfig {
class ServerConfig : public ScreenConfig, public synergy::gui::IServerConfig {
friend class ServerConfigDialog;
friend class ServerConnection;
friend QTextStream &
operator<<(QTextStream &outStream, const ServerConfig &config);
@ -45,7 +45,7 @@ public:
ServerConfig(
AppConfig &appConfig, MainWindow &mainWindow,
int columns = kDefaultColumns, int rows = kDefaultRows);
~ServerConfig() = default;
~ServerConfig() override = default;
bool operator==(const ServerConfig &sc) const;
@ -64,23 +64,23 @@ public:
int switchCornerSize() const { return m_SwitchCornerSize; }
const QList<bool> &switchCorners() const { return m_SwitchCorners; }
const HotkeyList &hotkeys() const { return m_Hotkeys; }
bool enableDragAndDrop() const { return m_EnableDragAndDrop; }
bool enableDragAndDrop() const override { return m_EnableDragAndDrop; }
bool disableLockToScreen() const { return m_DisableLockToScreen; }
bool clipboardSharing() const { return m_ClipboardSharing; }
size_t clipboardSharingSize() const { return m_ClipboardSharingSize; }
static size_t defaultClipboardSharingSize();
void commit();
bool save(const QString &fileName) const;
void save(QFile &file) const;
bool save(const QString &fileName) const override;
void save(QFile &file) const override;
int numScreens() const;
int autoAddScreen(const QString name);
const QString &getServerName() const;
void updateServerName();
const QString &configFile() const;
bool useExternalConfig() const;
bool isFull() const;
bool isScreenExists(const QString &screenName) const;
bool isFull() const override;
bool screenExists(const QString &screenName) const override;
void addClient(const QString &clientName);
QString getClientAddress() const;
void setClientAddress(const QString &address);

View file

@ -22,7 +22,7 @@
#include "ScreenSetupModel.h"
#include "ServerConfig.h"
#include "gui/AppConfig.h"
#include "gui/config/AppConfig.h"
#include "ui_ServerConfigDialogBase.h"
#include <QDialog>
@ -36,9 +36,9 @@ public:
bool addClient(const QString &clientName);
public slots:
void accept();
void accept() override;
void reject() override;
void showEvent(QShowEvent *event);
void showEvent(QShowEvent *event) override;
void message(const QString &message) { m_Message = message; }
protected slots:

View file

@ -76,7 +76,7 @@
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<spacer name="horizontalSpacer_21">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@ -117,7 +117,7 @@
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<spacer name="horizontalSpacer_22">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@ -342,7 +342,7 @@
<number>9</number>
</property>
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<layout class="QVBoxLayout" name="verticalLayout_21">
<property name="spacing">
<number>0</number>
</property>
@ -482,7 +482,7 @@
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<spacer name="verticalSpacer_41">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
@ -644,7 +644,7 @@
</widget>
</item>
<item row="1" column="5">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<layout class="QHBoxLayout" name="horizontalLayout_21">
<item>
<widget class="QLabel" name="label">
<property name="text">
@ -1105,7 +1105,7 @@ Enabling this setting will disable the server config GUI.</string>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<spacer name="verticalSpacer_1">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>

View file

@ -1,78 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2021 Symless Ltd.
* Copyright (C) 2008 Volker Lanz (vl@fidra.de)
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ServerConnection.h"
#include "MainWindow.h"
#include "ServerConfigDialog.h"
#include "ServerMessage.h"
#include "gui/AppConfig.h"
#include <QMessageBox>
ServerConnection::ServerConnection(MainWindow &parent) : m_parent(parent) {}
void ServerConnection::update(const QString &line) {
ServerMessage message(line);
if (!m_parent.appConfig().useExternalConfig() &&
message.isNewClientMessage() &&
!m_ignoredClients.contains(message.getClientName())) {
addClient(message.getClientName());
}
}
bool ServerConnection::checkMainWindow() {
bool result = m_parent.isActiveWindow();
if (m_parent.isMinimized() || m_parent.isHidden()) {
m_parent.showNormal();
m_parent.activateWindow();
result = true;
}
return result;
}
void ServerConnection::addClient(const QString &clientName) {
if (!m_parent.serverConfig().isFull() &&
!m_parent.serverConfig().isScreenExists(clientName) &&
checkMainWindow()) {
QMessageBox message(&m_parent);
message.addButton(QObject::tr("Ignore"), QMessageBox::RejectRole);
message.addButton(
QObject::tr("Accept and configure"), QMessageBox::AcceptRole);
message.setText(
QObject::tr("%1 client has made a connection request").arg(clientName));
if (message.exec() == QMessageBox::Accepted) {
configureClient(clientName);
} else {
m_ignoredClients.append(clientName);
}
}
}
void ServerConnection::configureClient(const QString &clientName) {
ServerConfigDialog dialog(
&m_parent, m_parent.serverConfig(), m_parent.appConfig());
if (dialog.addClient(clientName) && dialog.exec() == QDialog::Accepted) {
m_parent.restartCore();
}
}

View file

@ -20,9 +20,10 @@
#include "MainWindow.h"
#include "UpgradeDialog.h"
#include "gui/AppConfig.h"
#include "gui/TlsCertificate.h"
#include "gui/config/AppConfig.h"
#include "gui/constants.h"
#include "gui/tls/TlsCertificate.h"
#include "gui/tls/TlsUtility.h"
#include "validators/ScreenNameValidator.h"
#include "validators/ValidationError.h"
@ -31,8 +32,10 @@
#include <QMessageBox>
#include <QtCore>
#include <QtGui>
#include <qglobal.h>
using namespace synergy::license;
using namespace synergy::gui;
const char *const kProProductName = "Synergy 1 Pro";
@ -82,7 +85,7 @@ void SettingsDialog::accept() {
appConfig().setAutoHide(m_pCheckBoxAutoHide->isChecked());
appConfig().setPreventSleep(m_pCheckBoxPreventSleep->isChecked());
appConfig().setTlsCertPath(m_pLineEditCertificatePath->text());
appConfig().setTlsKeyLength(m_pComboBoxKeyLength->currentText());
appConfig().setTlsKeyLength(m_pComboBoxKeyLength->currentText().toInt());
appConfig().setTlsEnabled(m_pCheckBoxEnableCrypto->isChecked());
appConfig().setLanguageSync(m_pCheckBoxLanguageSync->isChecked());
appConfig().setInvertScrollDirection(m_pCheckBoxScrollDirection->isChecked());
@ -136,8 +139,9 @@ void SettingsDialog::updateTlsControls() {
if (QFile(appConfig().tlsCertPath()).exists()) {
updateKeyLengthOnFile(appConfig().tlsCertPath());
} else {
const auto keyLengthText = QString::number(appConfig().tlsKeyLength());
m_pComboBoxKeyLength->setCurrentIndex(
m_pComboBoxKeyLength->findText(appConfig().tlsKeyLength()));
m_pComboBoxKeyLength->findText(keyLengthText));
}
m_pCheckBoxEnableCrypto->setChecked(m_appConfig.tlsEnabled());
@ -189,7 +193,7 @@ void SettingsDialog::on_m_pButtonBrowseLog_clicked() {
void SettingsDialog::on_m_pCheckBoxEnableCrypto_clicked(bool) {
updateTlsControlsEnabled();
if (kLicensingEnabled && !m_tlsUtility.isAvailable()) {
if (kEnableActivation && !m_tlsUtility.isAvailable()) {
auto edition = m_license.productEdition();
if (edition == Edition::kBasic) {
UpgradeDialog upgradeDialog(this);
@ -215,9 +219,11 @@ void SettingsDialog::on_m_pPushButtonBrowseCert_clicked() {
if (!fileName.isEmpty()) {
m_pLineEditCertificatePath->setText(fileName);
// If the tls file exists test its key length and update
if (QFile(appConfig().tlsCertPath()).exists()) {
if (QFile(fileName).exists()) {
updateKeyLengthOnFile(fileName);
} else {
qDebug("no tls certificate file at: %s", qUtf8Printable(fileName));
}
}
updateTlsRegenerateButton();
@ -228,9 +234,8 @@ void SettingsDialog::on_m_pComboBoxKeyLength_currentIndexChanged(int index) {
}
void SettingsDialog::updateTlsRegenerateButton() {
// Disable the Regenerate cert button if the key length is different to saved
auto keyChanged =
appConfig().tlsKeyLength() != m_pComboBoxKeyLength->currentText();
const auto keyLength = m_pComboBoxKeyLength->currentText().toInt();
auto keyChanged = appConfig().tlsKeyLength() != keyLength;
auto pathChanged =
appConfig().tlsCertPath() != m_pLineEditCertificatePath->text();
// NOR the above bools, if any have changed regen should be disabled as it
@ -241,15 +246,22 @@ void SettingsDialog::updateTlsRegenerateButton() {
}
void SettingsDialog::on_m_pPushButtonRegenCert_clicked() {
m_tlsUtility.generateCertificate(true);
if (m_tlsUtility.generateCertificate()) {
QMessageBox::information(
this, tr("TLS Certificate Regenerated"),
tr("TLS certificate regenerated successfully."));
}
}
void SettingsDialog::updateKeyLengthOnFile(const QString &path) {
TlsCertificate ssl;
if (!QFile(path).exists()) {
qFatal("tls certificate file not found: %s", qUtf8Printable(path));
}
auto length = ssl.getCertKeyLength(path);
auto index = m_pComboBoxKeyLength->findText(length);
auto index = m_pComboBoxKeyLength->findText(QString::number(length));
m_pComboBoxKeyLength->setCurrentIndex(index);
// Also update what is in the appconfig to match the file itself
appConfig().setTlsKeyLength(length);
}

View file

@ -20,8 +20,8 @@
#include "ui_SettingsDialogBase.h"
#include "gui/CoreInterface.h"
#include "gui/TlsUtility.h"
#include "gui/core/CoreTool.h"
#include "gui/tls/TlsUtility.h"
#include "license/License.h"
#include "validators/ValidationError.h"
@ -68,7 +68,7 @@ protected:
private:
MainWindow *m_pMainWindow;
AppConfig &m_appConfig;
CoreInterface m_coreInterface;
[[no_unique_address]] CoreTool m_coreTool;
const synergy::license::License &m_license;
synergy::gui::TlsUtility m_tlsUtility;
validators::ValidationError *m_pScreenNameError;

View file

@ -21,6 +21,8 @@
#include "validators/ScreenNameValidator.h"
#include "validators/ValidationError.h"
using namespace synergy::gui;
SetupWizard::SetupWizard(AppConfig &appConfig) : m_appConfig(appConfig) {
setupUi(this);

View file

@ -19,7 +19,7 @@
#include "ui_SetupWizardBase.h"
#include "gui/AppConfig.h"
#include "gui/config/AppConfig.h"
#include <QDialog>
#include <QObject>

View file

@ -1,35 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2021 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TrayIcon.h"
void TrayIcon::tryCreate() const {
QSystemTrayIcon trayIcon; // by creating a new tray icon, we actually make the
// DBus implementation refresh the connection (DBus)
trayIcon.hide(); // we ony hide it in order for the compiler to not
// optimise-out the object (make some use of it)
if (QSystemTrayIcon::isSystemTrayAvailable()) { // this ends up calling the
// underlying DBus connection
// (on DBus)
m_pTrayIcon->show();
m_connector(
m_pTrayIcon.get(),
SIGNAL(activated(QSystemTrayIcon::ActivationReason)));
} else {
QTimer::singleShot(2500, this, &TrayIcon::tryCreate);
}
}

View file

@ -1,73 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2021 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QMenu>
#include <QObject>
#include <QSystemTrayIcon>
#include <QTimer>
#include <functional>
#include <memory>
class TrayIcon : public QObject {
Q_OBJECT
public:
using TConnector = std::function<void(QObject const *, const char *)>;
TrayIcon() {
m_set = [this](const QIcon &icon) {
m_init = [this, icon]() { this->set(icon); };
};
}
template <typename TActionContainer>
void
create(TActionContainer const &actionContainer, TConnector const &connector) {
m_connector = connector;
m_pTrayIconMenu = std::make_unique<QMenu>();
for (auto action : actionContainer) {
if (action) {
m_pTrayIconMenu->addAction(action);
} else {
m_pTrayIconMenu->addSeparator();
}
}
m_pTrayIcon = std::make_unique<QSystemTrayIcon>();
m_pTrayIcon->setContextMenu(m_pTrayIconMenu.get());
m_pTrayIcon->setToolTip("Synergy");
m_set = [this](const QIcon &icon) { m_pTrayIcon->setIcon(icon); };
tryCreate();
if (m_init) {
m_init();
m_init = std::function<void()>();
}
}
void tryCreate() const;
void set(const QIcon &icon) const { m_set(icon); }
private:
std::unique_ptr<QSystemTrayIcon> m_pTrayIcon{};
std::unique_ptr<QMenu> m_pTrayIconMenu{};
TConnector m_connector;
std::function<void()> m_init;
std::function<void(const QIcon &icon)> m_set;
};

View file

@ -20,8 +20,9 @@
#include "QSynergyApplication.h"
#include "SetupWizard.h"
#include "SetupWizardBlocker.h"
#include "gui/AppConfig.h"
#include "gui/ConfigScopes.h"
#include "gui/Logger.h"
#include "gui/config/AppConfig.h"
#include "gui/config/ConfigScopes.h"
#include "gui/constants.h"
#include "gui/dotenv.h"
#include "gui/messages.h"
@ -54,7 +55,7 @@ bool checkMacAssistiveDevices();
int main(int argc, char *argv[]) {
#ifdef Q_OS_DARWIN
#if defined(Q_OS_MAC)
/* Workaround for QTBUG-40332 - "High ping when QNetworkAccessManager is
* instantiated" */
::setenv("QT_BEARER_POLL_TIMEOUT", "-1", 1);
@ -67,9 +68,10 @@ int main(int argc, char *argv[]) {
QSynergyApplication app(argc, argv);
qInstallMessageHandler(synergy::gui::messages::messageHandler);
qInfo("Synergy v%s", synergy::gui::version().toUtf8().constData());
dotenv(".env");
Logger::instance().loadEnvVars();
qInfo("Synergy v%s", synergy::gui::version().toUtf8().constData());
#if defined(Q_OS_MAC)

View file

@ -18,7 +18,10 @@
#include "LineEditValidator.h"
#include "gui/styles.h"
#include <qvalidator.h>
#include <QValidator>
using namespace synergy::gui;
namespace validators {

View file

@ -19,6 +19,8 @@
#include "gui/styles.h"
using namespace synergy::gui;
namespace validators {
void clear(QLabel *label) {

View file

@ -18,8 +18,8 @@
#pragma once
#include <QLabel>
#include <QObject>
#include <QString>
#include <qobject.h>
namespace validators {

View file

@ -18,7 +18,9 @@
#include "ServerStateLabel.h"
#include "ServerMessage.h"
#include "gui/core/ServerMessage.h"
using namespace synergy::gui;
namespace synergy_widgets {
@ -38,7 +40,14 @@ void ServerStateLabel::updateServerState(const QString &line) {
if (m_clients.isEmpty()) {
setText(tr("No clients connected"));
} else {
setText(tr("Connected clients: %1").arg(m_clients.join(", ")));
// unfortunately, we can't rely on the clients list because we don't always
// catch the connect/disconnect messages. so clients tend to get stuck in
// the list even though they're offline.
// in order to properly show a list of clients, we would need the core to
// print a list of connected clients on every connect/disconnect event,
// which could be a bit noisy in the logs (perhaps an ipc message would be
// needed).
setText(tr("Client(s) are connected"));
}
}

View file

@ -23,6 +23,11 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
file(GLOB_RECURSE sources *.cpp)
file(GLOB_RECURSE headers *.h)
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
file(GLOB mac_sources *.mm)
list(APPEND sources ${mac_sources})
endif()
if(ADD_HEADERS_TO_SOURCES)
list(APPEND sources ${headers})
endif()

View file

@ -1,35 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2016 Symless
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// The elevate mode tristate determines two behaviours on Windows.
// The first, switch-on-desk-switch (SodS), passed through synergyd as a
// command line argument to synergy core, determines if the server restarts
// when switching Windows desktops (e.g. when Windows UAC dialog pops up).
// The second, passed as a boolean flag to Synergyd over the IPC inside
// IpcMessageType::CommandMessage, determines whether Synergy should be started
// with elevated privileges.
//
// The matrix for these two behaviours is as follows:
// SodS Elevate
// ___________________________
// ElevateAsNeeded | true | false
// ElevateAlways | false | true
// ElevateNever | false | false
//
enum ElevateMode { ElevateAsNeeded = 0, ElevateAlways = 1, ElevateNever = 2 };

100
src/lib/gui/Logger.cpp Normal file
View file

@ -0,0 +1,100 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Logger.h"
#include "string_utils.h"
#include <QDateTime>
#include <QDir>
#include <QMessageBox>
#include <QTime>
namespace synergy::gui {
Logger Logger::s_instance;
QString printLine(
FILE *out, const QString &type, const QString &message,
const QString &fileLine = "") {
auto datetime = QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm:ss");
auto logLine = QString("[%1] %2: %3").arg(datetime).arg(type).arg(message);
if (!fileLine.isEmpty()) {
logLine += "\n\t" + fileLine;
}
auto logLineUtf = logLine.toUtf8();
auto logLine_c = logLineUtf.constData();
fprintf(out, "%s\n", logLine_c);
fflush(out);
return logLine;
}
void Logger::loadEnvVars() {
const auto debugEnvVar = qEnvironmentVariable("SYNERGY_GUI_DEBUG");
if (!debugEnvVar.isEmpty()) {
m_debug = strToTrue(debugEnvVar);
}
const auto verboseEnvVar = qEnvironmentVariable("SYNERGY_GUI_VERBOSE");
if (!verboseEnvVar.isEmpty()) {
m_verbose = strToTrue(verboseEnvVar);
}
}
void Logger::logVerbose(const QString &message) const {
if (m_verbose) {
printLine(stdout, "VERBOSE", message);
}
}
void Logger::handleMessage(
QtMsgType type, const QMessageLogContext &context, const QString &message) {
QString typeString;
auto out = stdout;
switch (type) {
case QtDebugMsg:
typeString = "DEBUG";
if (!m_debug) {
return;
}
break;
case QtInfoMsg:
typeString = "INFO";
break;
case QtWarningMsg:
typeString = "WARNING";
out = stderr;
break;
case QtCriticalMsg:
typeString = "CRITICAL";
out = stderr;
break;
case QtFatalMsg:
typeString = "FATAL";
out = stderr;
break;
}
const auto fileLine = QString("%1:%2").arg(context.file).arg(context.line);
const auto logLine = printLine(out, typeString, message, fileLine);
emit newLine(logLine);
}
} // namespace synergy::gui

55
src/lib/gui/Logger.h Normal file
View file

@ -0,0 +1,55 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QObject>
#ifdef NDEBUG
const bool kDebug = false;
#else
const bool kDebug = true;
#endif
namespace synergy::gui {
class Logger : public QObject {
Q_OBJECT
public:
static Logger &instance() { return s_instance; }
void loadEnvVars();
void handleMessage(
QtMsgType type, const QMessageLogContext &context,
const QString &message);
void logVerbose(const QString &message) const;
signals:
void newLine(const QString &line);
private:
static Logger s_instance;
bool m_debug = kDebug;
bool m_verbose = false;
};
inline void logVerbose(const QString &message) {
Logger::instance().logVerbose(message);
}
} // namespace synergy::gui

View file

@ -1,6 +1,6 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015 Synergy Si Ltd.
* Copyright (C) 2015 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -15,9 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if !defined(OSXHELPERS__H)
#define OSXHELPERS__H
#pragma once
#include <QString>
@ -28,5 +26,3 @@ bool isOSXDevelopmentBuild();
bool showOSXNotification(const QString &title, const QString &body);
bool isOSXInterfaceStyleDark();
IconsTheme getOSXIconsTheme();
#endif

View file

@ -1,6 +1,6 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015 Synergy Si Ltd.
* Copyright (C) 2015 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License

View file

@ -1,155 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "QIpcClient.h"
#include "IpcReader.h"
#include "global/Ipc.h"
#include <QDataStream>
#include <QHostAddress>
#include <QTimer>
QIpcClient::QIpcClient(const StreamProvider &streamProvider)
: m_ReaderStarted(false),
m_Enabled(false),
m_StreamProvider(streamProvider) {
m_Socket = new QTcpSocket(this);
if (!m_StreamProvider) {
m_StreamProvider = [this]() {
return std::make_shared<QDataStreamProxy>(m_Socket);
};
}
connect(m_Socket, SIGNAL(connected()), this, SLOT(connected()));
connect(
m_Socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this,
SLOT(error(QAbstractSocket::SocketError)));
m_Reader = new IpcReader(m_Socket);
connect(
m_Reader, SIGNAL(readLogLine(const QString &)), this,
SLOT(handleReadLogLine(const QString &)));
}
QIpcClient::~QIpcClient() {
delete m_Reader;
delete m_Socket;
}
void QIpcClient::connected() {
sendHello();
emit infoMessage("connection established");
}
void QIpcClient::connectToHost() {
m_Enabled = true;
emit infoMessage("connecting to service...");
const auto port = static_cast<quint16>(kIpcPort);
m_Socket->connectToHost(QHostAddress(QHostAddress::LocalHost), port);
if (!m_ReaderStarted) {
m_Reader->start();
m_ReaderStarted = true;
}
}
void QIpcClient::disconnectFromHost() {
emit infoMessage("service disconnect");
m_Reader->stop();
m_Socket->close();
}
void QIpcClient::error(QAbstractSocket::SocketError error) {
QString text;
switch (error) {
case 0:
text = "connection refused";
break;
case 1:
text = "remote host closed";
break;
default:
text = QString("code=%1").arg(error);
break;
}
emit errorMessage(QString("ipc connection error, %1").arg(text));
QTimer::singleShot(1000, this, SLOT(retryConnect()));
}
void QIpcClient::retryConnect() {
if (m_Enabled) {
connectToHost();
}
}
void QIpcClient::sendHello() {
auto stream = m_StreamProvider();
stream->writeRawData(kIpcMsgHello, 4);
char typeBuf[1];
typeBuf[0] = static_cast<char>(IpcClientType::GUI);
stream->writeRawData(typeBuf, 1);
}
void QIpcClient::sendCommand(
const QString &command, ElevateMode const elevate) {
auto stream = m_StreamProvider();
stream->writeRawData(kIpcMsgCommand, 4);
std::string stdStringCommand = command.toStdString();
const char *charCommand = stdStringCommand.c_str();
auto length = static_cast<int>(stdStringCommand.length());
char lenBuf[4];
intToBytes(length, lenBuf, 4);
stream->writeRawData(lenBuf, 4);
stream->writeRawData(charCommand, length);
char elevateBuf[1];
// Refer to enum ElevateMode documentation for why this flag is mapped this
// way
elevateBuf[0] = (elevate == ElevateAlways) ? 1 : 0;
stream->writeRawData(elevateBuf, 1);
}
void QIpcClient::handleReadLogLine(const QString &text) {
emit readLogLine(text);
}
// TODO: qt must have a built in way of converting int to bytes.
void QIpcClient::intToBytes(int value, char *buffer, int size) {
if (size == 1) {
buffer[0] = value & 0xff;
} else if (size == 2) {
buffer[0] = (value >> 8) & 0xff;
buffer[1] = value & 0xff;
} else if (size == 4) {
buffer[0] = (value >> 24) & 0xff;
buffer[1] = (value >> 16) & 0xff;
buffer[2] = (value >> 8) & 0xff;
buffer[3] = value & 0xff;
} else {
// TODO: other sizes, if needed.
}
}

View file

@ -1,58 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "gui/CoreInterface.h"
#include <QObject>
class TlsCertificate : public QObject {
Q_OBJECT
public:
explicit TlsCertificate(QObject *parent = nullptr);
public slots:
/// @brief Generates a TLS cert and private key
/// @param [in] QString path The path of the file to be generated
/// @param [in] QString keyLength The size of the private key. default: 2048
/// @param [in] bool Should the file be created regardless of if the file
/// already exists
void generateCertificate(
const QString &path = QString(), const QString &keyLength = "2048",
bool forceGen = false);
/// @brief Get the key length of a TLS private key
/// @param [in] QString path The path of the file to checked
/// @return QString The key legnth as a string
QString getCertKeyLength(const QString &path);
signals:
void error(QString e);
void info(QString i);
void generateFinished();
private:
bool runTool(const QStringList &args);
void generateFingerprint(const QString &certificateFilename);
private:
QString m_ProfileDir;
QString m_ToolOutput;
CoreInterface m_CoreInterface;
};

75
src/lib/gui/TrayIcon.cpp Normal file
View file

@ -0,0 +1,75 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2021 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TrayIcon.h"
#include "constants.h"
namespace synergy::gui {
const auto kShowRetryInterval = 1000;
void TrayIcon::setIcon(const QIcon &icon) {
m_icon = icon;
if (m_pTrayIcon && !icon.isNull()) {
m_pTrayIcon->setIcon(icon);
}
}
void TrayIcon::showRetryLoop() {
// HACK: apparently this is needed to create a dbus connection, and the hide
// is needed to make use of the object so the dbus connection doesn't get
// optimized away by the compiler.
// TODO: we should verify that this hack actually works.
QSystemTrayIcon trayIcon;
trayIcon.hide();
if (QSystemTrayIcon::isSystemTrayAvailable()) {
m_pTrayIcon->show();
} else {
// on some platforms, it's not always possible to create the tray when the
// app starts, so keep trying until it is possible.
qDebug("system tray not ready yet, retrying in %d ms", kShowRetryInterval);
QTimer::singleShot(kShowRetryInterval, this, &TrayIcon::showRetryLoop);
}
}
void TrayIcon::create(std::vector<QAction *> const &actions) {
m_pTrayIconMenu = std::make_unique<QMenu>();
for (auto action : actions) {
if (action) {
m_pTrayIconMenu->addAction(action);
} else {
m_pTrayIconMenu->addSeparator();
}
}
m_pTrayIcon = std::make_unique<QSystemTrayIcon>();
setIcon(m_icon);
connect(
m_pTrayIcon.get(), &QSystemTrayIcon::activated, this,
&TrayIcon::activated);
m_pTrayIcon->setContextMenu(m_pTrayIconMenu.get());
m_pTrayIcon->setToolTip(kAppName);
showRetryLoop();
}
} // namespace synergy::gui

50
src/lib/gui/TrayIcon.h Normal file
View file

@ -0,0 +1,50 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2021 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QMenu>
#include <QObject>
#include <QSystemTrayIcon>
#include <QTimer>
#include <functional>
#include <memory>
namespace synergy::gui {
class TrayIcon : public QObject {
Q_OBJECT
public:
explicit TrayIcon() = default;
void create(std::vector<QAction *> const &actions);
void setIcon(const QIcon &icon);
signals:
void activated(QSystemTrayIcon::ActivationReason reason);
private:
void showRetryLoop();
std::unique_ptr<QSystemTrayIcon> m_pTrayIcon;
std::unique_ptr<QMenu> m_pTrayIconMenu;
std::function<void()> m_init;
std::function<void(const QIcon &icon)> m_setIcon;
QIcon m_icon;
};
} // namespace synergy::gui

42
src/lib/gui/byte_utils.h Normal file
View file

@ -0,0 +1,42 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QByteArray>
#include <QDataStream>
#include <QIODevice>
#include <qlogging.h>
namespace synergy::gui {
inline int bytesToInt(const char *buffer, size_t size) {
QByteArray byteArray(buffer, static_cast<int>(size));
QDataStream stream(byteArray);
int result;
stream >> result;
return result;
}
inline QByteArray intToBytes(int value) {
QByteArray bytes;
QDataStream stream(&bytes, QIODevice::WriteOnly);
stream << value;
return bytes;
}
} // namespace synergy::gui

View file

@ -28,13 +28,15 @@
#include <QtNetwork>
#include <functional>
using ConfigScopes = synergy::gui::ConfigScopes;
using IConfigScopes = synergy::gui::IConfigScopes;
using namespace synergy::gui;
// this should be incremented each time the wizard is changed,
// which will force it to re-run for existing installations.
const int kWizardVersion = 8;
static const char *const kLogLevelNames[] = {
"INFO", "DEBUG", "DEBUG1", "DEBUG2"};
#if defined(Q_OS_WIN)
const char AppConfig::m_CoreServerName[] = "synergys.exe";
const char AppConfig::m_CoreClientName[] = "synergyc.exe";
@ -94,11 +96,9 @@ const char *const AppConfig::m_SettingsName[] = {
"showCloseReminder",
};
static const char *logLevelNames[] = {"INFO", "DEBUG", "DEBUG1", "DEBUG2"};
AppConfig::AppConfig(
synergy::gui::IConfigScopes &scopes, std::shared_ptr<Deps> deps)
: m_scopes(scopes),
: m_Scopes(scopes),
m_pDeps(deps),
m_ScreenName(deps->hostname()) {
determineScope();
@ -120,8 +120,8 @@ void AppConfig::recallFromAllScopes() {
m_WizardLastRun = findInAllScopes(kWizardLastRun, m_WizardLastRun).toInt();
m_LoadFromSystemScope =
findInAllScopes(kLoadSystemSettings, m_LoadFromSystemScope).toBool();
m_licenseNextCheck =
findInAllScopes(kLicenseNextCheck, m_licenseNextCheck).toULongLong();
m_LicenseNextCheck =
findInAllScopes(kLicenseNextCheck, m_LicenseNextCheck).toULongLong();
}
void AppConfig::recallFromCurrentScope() {
@ -165,8 +165,7 @@ void AppConfig::recallFromCurrentScope() {
m_CloseToTray = getFromCurrentScope(kCloseToTray, m_CloseToTray).toBool();
m_TlsEnabled = getFromCurrentScope(kTlsEnabled, m_TlsEnabled).toBool();
m_TlsCertPath = getFromCurrentScope(kTlsCertPath, m_TlsCertPath).toString();
m_TlsKeyLength =
getFromCurrentScope(kTlsKeyLength, m_TlsKeyLength).toString();
m_TlsKeyLength = getFromCurrentScope(kTlsKeyLength, m_TlsKeyLength).toInt();
m_MainWindowPosition = getFromCurrentScope<QPoint>(
kMainWindowPosition, [](const QVariant &v) { return v.toPoint(); });
m_MainWindowSize = getFromCurrentScope<QSize>(
@ -197,13 +196,13 @@ void AppConfig::recallScreenName() {
void AppConfig::commit() {
using enum Setting;
qDebug("comitting app config");
qDebug("committing app config");
saveToAllScopes(kWizardLastRun, m_WizardLastRun);
saveToAllScopes(kLoadSystemSettings, m_LoadFromSystemScope);
saveToAllScopes(kClientGroupChecked, m_ClientGroupChecked);
saveToAllScopes(kServerGroupChecked, m_ServerGroupChecked);
saveToAllScopes(kLicenseNextCheck, m_licenseNextCheck);
saveToAllScopes(kLicenseNextCheck, m_LicenseNextCheck);
if (isActiveScopeWritable()) {
setInCurrentScope(kScreenName, m_ScreenName);
@ -214,7 +213,8 @@ void AppConfig::commit() {
setInCurrentScope(kLogFilename, m_LogFilename);
setInCurrentScope(kStartedBefore, m_StartedBefore);
setInCurrentScope(kElevateMode, static_cast<int>(m_ElevateMode));
setInCurrentScope(kElevateModeLegacy, m_ElevateMode == ElevateAlways);
setInCurrentScope(
kElevateModeLegacy, m_ElevateMode == ElevateMode::kAlways);
setInCurrentScope(kTlsEnabled, m_TlsEnabled);
setInCurrentScope(kAutoHide, m_AutoHide);
setInCurrentScope(kSerialKey, m_SerialKey);
@ -249,11 +249,11 @@ void AppConfig::determineScope() {
// first, try to determine if the system scope should be used according to the
// user scope...
if (m_scopes.scopeContains(
if (m_Scopes.scopeContains(
settingName(Setting::kLoadSystemSettings),
ConfigScopes::Scope::User)) {
auto loadFromSystemScope =
m_scopes
m_Scopes
.getFromScope(
settingName(Setting::kLoadSystemSettings),
m_LoadFromSystemScope, ConfigScopes::Scope::User)
@ -265,7 +265,7 @@ void AppConfig::determineScope() {
// ...failing that, check the system scope instead to see if an arbitrary
// required setting is present. if it is, then we can assume that the system
// scope should be used.
else if (m_scopes.scopeContains(
else if (m_Scopes.scopeContains(
settingName(Setting::kScreenName),
ConfigScopes::Scope::System)) {
setLoadFromSystemScope(true);
@ -275,7 +275,7 @@ void AppConfig::determineScope() {
void AppConfig::recallSerialKey() {
using enum Setting;
if (!m_scopes.scopeContains(settingName(kLoadSystemSettings))) {
if (!m_Scopes.scopeContains(settingName(kLoadSystemSettings))) {
qDebug("no serial key in current scope, skipping");
return;
}
@ -294,7 +294,7 @@ void AppConfig::recallSerialKey() {
void AppConfig::recallElevateMode() {
using enum Setting;
if (!m_scopes.scopeContains(settingName(kElevateMode))) {
if (!m_Scopes.scopeContains(settingName(kElevateMode))) {
qDebug("elevate mode not set yet, skipping");
return;
}
@ -322,24 +322,24 @@ QString AppConfig::settingName(Setting name) {
}
template <typename T> void AppConfig::setInCurrentScope(Setting name, T value) {
m_scopes.setInScope(settingName(name), value);
m_Scopes.setInScope(settingName(name), value);
}
template <typename T> void AppConfig::saveToAllScopes(Setting name, T value) {
m_scopes.setInScope(settingName(name), value, ConfigScopes::Scope::User);
m_scopes.setInScope(settingName(name), value, ConfigScopes::Scope::System);
m_Scopes.setInScope(settingName(name), value, ConfigScopes::Scope::User);
m_Scopes.setInScope(settingName(name), value, ConfigScopes::Scope::System);
}
QVariant AppConfig::getFromCurrentScope(
Setting name, const QVariant &defaultValue) const {
return m_scopes.getFromScope(settingName(name), defaultValue);
return m_Scopes.getFromScope(settingName(name), defaultValue);
}
template <typename T>
std::optional<T> AppConfig::getFromCurrentScope(
Setting name, std::function<T(const QVariant &)> toType) const {
if (m_scopes.scopeContains(settingName(name))) {
return toType(m_scopes.getFromScope(settingName(name)));
if (m_Scopes.scopeContains(settingName(name))) {
return toType(m_Scopes.getFromScope(settingName(name)));
} else {
return std::nullopt;
}
@ -348,7 +348,7 @@ std::optional<T> AppConfig::getFromCurrentScope(
template <typename T>
void AppConfig::setInCurrentScope(Setting name, const std::optional<T> &value) {
if (value.has_value()) {
m_scopes.setInScope(settingName(name), value.value());
m_Scopes.setInScope(settingName(name), value.value());
}
}
@ -359,14 +359,14 @@ AppConfig::findInAllScopes(Setting name, const QVariant &defaultValue) const {
QVariant result(defaultValue);
QString setting(settingName(name));
if (m_scopes.scopeContains(setting)) {
result = m_scopes.getFromScope(setting, defaultValue);
} else if (m_scopes.activeScope() == System) {
if (m_scopes.scopeContains(setting, User)) {
result = m_scopes.getFromScope(setting, defaultValue, User);
if (m_Scopes.scopeContains(setting)) {
result = m_Scopes.getFromScope(setting, defaultValue);
} else if (m_Scopes.activeScope() == System) {
if (m_Scopes.scopeContains(setting, User)) {
result = m_Scopes.getFromScope(setting, defaultValue, User);
}
} else if (m_scopes.scopeContains(setting, System)) {
result = m_scopes.getFromScope(setting, defaultValue, System);
} else if (m_Scopes.scopeContains(setting, System)) {
result = m_Scopes.getFromScope(setting, defaultValue, System);
}
return result;
@ -388,18 +388,18 @@ void AppConfig::loadScope(ConfigScopes::Scope scope) {
qFatal("invalid scope");
}
if (m_scopes.activeScope() == scope) {
if (m_Scopes.activeScope() == scope) {
qDebug("already in required scope, skipping");
return;
}
m_scopes.setActiveScope(scope);
m_Scopes.setActiveScope(scope);
// only signal ready if there is at least one setting in the required scope.
// this prevents the current settings from being set back to default.
if (m_scopes.scopeContains(
settingName(Setting::kScreenName), m_scopes.activeScope())) {
m_scopes.signalReady();
if (m_Scopes.scopeContains(
settingName(Setting::kScreenName), m_Scopes.activeScope())) {
m_Scopes.signalReady();
} else {
qDebug("no screen name in scope, skipping");
}
@ -421,11 +421,11 @@ void AppConfig::setLoadFromSystemScope(bool value) {
}
bool AppConfig::isActiveScopeWritable() const {
return m_scopes.isActiveScopeWritable();
return m_Scopes.isActiveScopeWritable();
}
bool AppConfig::isActiveScopeSystem() const {
return m_scopes.activeScope() == ConfigScopes::Scope::System;
return m_Scopes.activeScope() == ConfigScopes::Scope::System;
}
QString AppConfig::logDir() const {
@ -446,7 +446,7 @@ void AppConfig::persistLogDir() const {
// Begin getters
///////////////////////////////////////////////////////////////////////////////
IConfigScopes &AppConfig::scopes() { return m_scopes; }
IConfigScopes &AppConfig::scopes() { return m_Scopes; }
bool AppConfig::activationHasRun() const { return m_ActivationHasRun; }
@ -464,7 +464,7 @@ bool AppConfig::logToFile() const { return m_LogToFile; }
const QString &AppConfig::logFilename() const { return m_LogFilename; }
QString AppConfig::logLevelText() const { return logLevelNames[logLevel()]; }
QString AppConfig::logLevelText() const { return kLogLevelNames[logLevel()]; }
ProcessMode AppConfig::processMode() const {
return m_EnableService ? ProcessMode::kService : ProcessMode::kDesktop;
@ -493,7 +493,7 @@ bool AppConfig::invertScrollDirection() const {
}
unsigned long long AppConfig::licenseNextCheck() const {
return m_licenseNextCheck;
return m_LicenseNextCheck;
}
bool AppConfig::languageSync() const { return m_LanguageSync; }
@ -504,7 +504,7 @@ bool AppConfig::invertConnection() const { return m_InvertConnection; }
QString AppConfig::tlsCertPath() const { return m_TlsCertPath; }
QString AppConfig::tlsKeyLength() const { return m_TlsKeyLength; }
int AppConfig::tlsKeyLength() const { return m_TlsKeyLength; }
bool AppConfig::enableService() const { return m_EnableService; }
@ -555,18 +555,28 @@ void AppConfig::setTlsEnabled(bool value) {
}
void AppConfig::setTlsCertPath(const QString &value) {
m_TlsChanged = m_TlsCertPath != value;
if (m_TlsCertPath != value) {
// deliberately only set the changed flag if there was a change.
// it's important not to set this flag to false here.
m_TlsChanged = true;
}
m_TlsCertPath = value;
}
void AppConfig::setTlsKeyLength(const QString &value) {
m_TlsChanged = m_TlsKeyLength != value;
void AppConfig::setTlsKeyLength(int value) {
if (m_TlsKeyLength != value) {
// deliberately only set the changed flag if there was a change.
// it's important not to set this flag to false here.
m_TlsChanged = true;
}
m_TlsKeyLength = value;
}
void AppConfig::setSerialKey(const QString &serialKey) {
m_SerialKey = serialKey;
saveToAllScopes(Setting::kSerialKey, m_SerialKey);
}
void AppConfig::setServerGroupChecked(bool newValue) {
m_ServerGroupChecked = newValue;
}
@ -618,7 +628,7 @@ void AppConfig::setElevateMode(ElevateMode em) { m_ElevateMode = em; }
void AppConfig::setAutoHide(bool b) { m_AutoHide = b; }
void AppConfig::setLicenseNextCheck(unsigned long long time) {
m_licenseNextCheck = time;
m_LicenseNextCheck = time;
}
void AppConfig::setInvertScrollDirection(bool newValue) {

View file

@ -18,10 +18,10 @@
#pragma once
#include "ConfigScopes.h"
#include "CoreInterface.h"
#include "ElevateMode.h"
#include "IAppConfig.h"
#include "IConfigScopes.h"
#include "gui/core/CoreTool.h"
#include <QDir>
#include <QHostInfo>
@ -32,10 +32,11 @@
#include <QVariant>
#include <optional>
enum class ProcessMode { kService, kDesktop };
namespace synergy::gui {
const ElevateMode kDefaultElevateMode = ElevateAsNeeded;
const ElevateMode kDefaultElevateMode = ElevateMode::kAutomatic;
const QString kDefaultLogFile = "synergy.log";
const int kDefaultTlsKeyLength = 2048;
#if defined(Q_OS_WIN)
const ProcessMode kDefaultProcessMode = ProcessMode::kService;
@ -49,6 +50,8 @@ const bool kDefaultShowDevThanks = true;
const bool kDefaultShowDevThanks = false;
#endif // SYNERGY_SHOW_DEV_THANKS
} // namespace synergy::gui
/**
* @brief Simply reads and writes app settings.
*
@ -58,6 +61,9 @@ const bool kDefaultShowDevThanks = false;
* becoming a god object.
*/
class AppConfig : public QObject, public synergy::gui::IAppConfig {
using ProcessMode = synergy::gui::ProcessMode;
using IConfigScopes = synergy::gui::IConfigScopes;
Q_OBJECT
private:
@ -110,20 +116,19 @@ private:
public:
struct Deps {
virtual ~Deps() = default;
virtual QString profileDir() const {
return m_coreInterface.getProfileDir();
}
virtual QString profileDir() const { return m_coreTool.getProfileDir(); }
virtual QString hostname() const { return QHostInfo::localHostName(); }
private:
[[no_unique_address]] CoreInterface m_coreInterface;
[[no_unique_address]] CoreTool m_coreTool;
};
explicit AppConfig(
synergy::gui::IConfigScopes &scopes,
IConfigScopes &scopes,
std::shared_ptr<Deps> deps = std::make_shared<Deps>());
synergy::gui::IConfigScopes &scopes();
IConfigScopes &scopes();
void determineScope();
/**
* @brief Commits the current settings to the active scope.
@ -131,50 +136,47 @@ public:
*/
void commit();
void determineScope();
/**
* Getters
*/
void setActivationHasRun(bool value);
ProcessMode processMode() const override;
ElevateMode elevateMode() const override;
bool tlsEnabled() const override;
QString tlsCertPath() const override;
int tlsKeyLength() const override;
QString logLevelText() const override;
const QString &screenName() const override;
bool logToFile() const override;
bool preventSleep() const override;
const QString &logFilename() const override;
QString coreServerName() const override;
QString coreClientName() const override;
bool invertConnection() const override;
void persistLogDir() const override;
QString serialKey() const override;
bool languageSync() const override;
bool invertScrollDirection() const override;
int port() const override;
bool useExternalConfig() const override;
const QString &configFile() const override;
const QString &networkInterface() const override;
bool isActiveScopeWritable() const;
bool isActiveScopeSystem() const;
const QString &screenName() const;
int port() const;
const QString &networkInterface() const;
int logLevel() const;
bool logToFile() const;
const QString &logFilename() const;
QString logLevelText() const;
ProcessMode processMode() const;
bool wizardShouldRun() const;
bool startedBefore() const;
QString coreServerName() const;
QString coreClientName() const;
QString logDir() const;
void persistLogDir() const;
ElevateMode elevateMode() const;
bool autoHide() const;
bool invertScrollDirection() const;
unsigned long long licenseNextCheck() const;
bool languageSync() const;
bool preventSleep() const;
bool invertConnection() const;
bool serverGroupChecked() const;
bool useExternalConfig() const;
const QString &configFile() const;
bool useInternalConfig() const;
bool clientGroupChecked() const;
QString serverHostname() const;
QString lastVersion() const;
bool enableService() const;
bool closeToTray() const;
QString serialKey() const;
bool activationHasRun() const;
bool tlsEnabled() const override;
QString tlsCertPath() const override;
QString tlsKeyLength() const override;
std::optional<QSize> mainWindowSize() const;
std::optional<QPoint> mainWindowPosition() const;
bool showDevThanks() const;
@ -184,6 +186,7 @@ public:
* Setters
*/
void setActivationHasRun(bool value);
void setScreenName(const QString &s);
void setPort(int i);
void setNetworkInterface(const QString &s);
@ -211,7 +214,7 @@ public:
void setEnableService(bool enabled);
void setCloseToTray(bool minimize);
void setTlsCertPath(const QString &path);
void setTlsKeyLength(const QString &length);
void setTlsKeyLength(int length);
void setInvertConnection(bool value);
void setMainWindowSize(const QSize &size);
void setMainWindowPosition(const QPoint &position);
@ -274,30 +277,30 @@ private:
/// @brief This method loads config from specified scope
/// @param [in] scope which should be loaded.
void loadScope(synergy::gui::ConfigScopes::Scope scope);
void loadScope(IConfigScopes::Scope scope);
/**
* @brief Gets a TLS certificate path based on the user's profile dir.
*/
QString defaultTlsCertPath() const;
synergy::gui::IConfigScopes &m_scopes;
synergy::gui::IConfigScopes &m_Scopes;
std::shared_ptr<Deps> m_pDeps;
QString m_ScreenName;
int m_Port = 24800;
QString m_Interface = "";
int m_LogLevel = 0;
bool m_LogToFile = false;
QString m_LogFilename = logDir() + kDefaultLogFile;
QString m_LogFilename = logDir() + synergy::gui::kDefaultLogFile;
int m_WizardLastRun = 0;
bool m_StartedBefore = false;
ElevateMode m_ElevateMode = kDefaultElevateMode;
ElevateMode m_ElevateMode = synergy::gui::kDefaultElevateMode;
QString m_ActivateEmail = "";
bool m_TlsEnabled = true;
bool m_AutoHide = false;
QString m_SerialKey = "";
QString m_LastVersion = "";
unsigned long long m_licenseNextCheck = 0;
unsigned long long m_LicenseNextCheck = 0;
bool m_ActivationHasRun = false;
bool m_InvertScrollDirection = false;
bool m_LanguageSync = true;
@ -309,13 +312,14 @@ private:
bool m_UseInternalConfig = false;
bool m_ClientGroupChecked = false;
QString m_ServerHostname = "";
bool m_EnableService = kDefaultProcessMode == ProcessMode::kService;
bool m_EnableService =
synergy::gui::kDefaultProcessMode == ProcessMode::kService;
bool m_CloseToTray = true;
QString m_TlsCertPath = defaultTlsCertPath();
QString m_TlsKeyLength = "2048";
int m_TlsKeyLength = synergy::gui::kDefaultTlsKeyLength;
std::optional<QSize> m_MainWindowSize;
std::optional<QPoint> m_MainWindowPosition;
bool m_ShowDevThanks = kDefaultShowDevThanks;
bool m_ShowDevThanks = synergy::gui::kDefaultShowDevThanks;
bool m_LoadFromSystemScope = false;
bool m_ShowCloseReminder = true;

View file

@ -18,20 +18,23 @@
#include "ConfigScopes.h"
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <cassert>
#include <memory>
const auto kSystemConfigFilename = "SystemConfig.ini";
#if defined(Q_OS_UNIX)
const auto kUnixSystemConfigPath = "/usr/local/etc/symless/";
#endif
namespace synergy::gui {
QString getSystemSettingPath() {
const QString settingFilename(kSystemConfigFilename);
#if defined(Q_OS_WIN)
return QCoreApplication::applicationDirPath() + "\\";
#elif defined(Q_OS_DARWIN)
return QCoreApplication::applicationDirPath() + QDir::separator();
#elif defined(Q_OS_MAC)
// it would be nice to use /Library dir, but qt has no elevate system.
return kUnixSystemConfigPath + settingFilename;
#elif defined(Q_OS_LINUX)

View file

@ -32,7 +32,7 @@ class ConfigScopes : public QObject, public IConfigScopes {
public:
explicit ConfigScopes();
virtual ~ConfigScopes() = default;
~ConfigScopes() override = default;
void signalReady() override;
void save() override;

View file

@ -0,0 +1,38 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2016 Symless
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
/**
* @brief The elevate mode tristate determines two behaviors on Windows.
*
* The first, switch-on-desk-switch (SodS), passed through synergyd as a
* command line argument to synergy core, determines if the server restarts
* when switching Windows desktops (e.g. when Windows UAC dialog pops up).
* The second, passed as a boolean flag to Synergyd over the IPC inside
* IpcMessageType::CommandMessage, determines whether Synergy should be started
* with elevated privileges.
*
* The matrix for these two behaviors is as follows:
*
* | SodS | Elevate |
* |-----------|------------|
* kAutomatic | true | false |
* kAlways | false | true |
* kNever | false | false |
*/
enum class ElevateMode { kAutomatic = 0, kAlways = 1, kNever = 2 };

View file

@ -0,0 +1,54 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ElevateMode.h"
#include <QString>
namespace synergy::gui {
enum class ProcessMode { kService, kDesktop };
class IAppConfig {
public:
virtual ~IAppConfig() = default;
virtual QString tlsCertPath() const = 0;
virtual int tlsKeyLength() const = 0;
virtual bool tlsEnabled() const = 0;
virtual ProcessMode processMode() const = 0;
virtual ElevateMode elevateMode() const = 0;
virtual QString logLevelText() const = 0;
virtual const QString &screenName() const = 0;
virtual bool preventSleep() const = 0;
virtual bool logToFile() const = 0;
virtual const QString &logFilename() const = 0;
virtual QString coreServerName() const = 0;
virtual QString coreClientName() const = 0;
virtual bool invertConnection() const = 0;
virtual void persistLogDir() const = 0;
virtual QString serialKey() const = 0;
virtual bool languageSync() const = 0;
virtual bool invertScrollDirection() const = 0;
virtual int port() const = 0;
virtual bool useExternalConfig() const = 0;
virtual const QString &configFile() const = 0;
virtual const QString &networkInterface() const = 0;
};
} // namespace synergy::gui

View file

@ -0,0 +1,35 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QFile>
#include <QString>
namespace synergy::gui {
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 bool enableDragAndDrop() const = 0;
};
} // namespace synergy::gui

View file

@ -1,6 +1,6 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2014 Symless Ltd.
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -19,23 +19,36 @@
#include <QString>
namespace synergy::gui {
const auto kAppName = "Synergy";
// TODO: change to `com.symless`. we'll need to gracefully import old settings,
// since qt uses this on some platforms when saving settings.
const auto kAppDomain = "https://symless.com";
const int kDebugLogLevel = 1;
#ifdef SYNERGY_PRODUCT_NAME
const QString kProductName = SYNERGY_PRODUCT_NAME;
#else
const QString kProductName;
#endif
#ifdef SYNERGY_ENABLE_LICENSING
const bool kLicensingEnabled = true;
#ifdef SYNERGY_LICENSED_PRODUCT
const bool kLicensedProduct = true;
#else
const bool kLicensingEnabled = false;
#endif // SYNERGY_ENABLE_LICENSING
const bool kLicensedProduct = false;
#endif
#ifdef SYNERGY_ENABLE_ACTIVATION
#ifndef SYNERGY_LICENSED_PRODUCT
#error "activation requires licensed product"
#endif
const bool kEnableActivation = true;
#else
const bool kEnableActivation = false;
#endif // SYNERGY_ENABLE_ACTIVATION
const auto kLinkBuy = R"(<a href="%1" style="color: %2">Buy now</a>)";
const auto kLinkRenew = R"(<a href="%1" style="color: %2">Renew now</a>)";
@ -43,7 +56,7 @@ const auto kLinkDownload = R"(<a href="%1" style="color: %2">Download now</a>)";
const auto kUrlSourceQuery = "source=gui";
const auto kUrlWebsite = "https://symless.com";
const auto kUrlContribute = "https://github.com/symless/synergy-core";
const auto kUrlGitHub = "https://github.com/symless/synergy-core";
const auto kUrlGnomeTrayFix =
"https://extensions.gnome.org/extension/2890/tray-icons-reloaded/";
const auto kUrlProduct = QString("%1/synergy").arg(kUrlWebsite);
@ -54,3 +67,6 @@ const auto kUrlContact =
const auto kUrlHelp = QString("%1/help?%2").arg(kUrlProduct, kUrlSourceQuery);
const auto kUrlDownload =
QString("%1/download?%2").arg(kUrlProduct, kUrlSourceQuery);
const auto kUrlBugReport = QString("%1/issues").arg(kUrlGitHub);
} // namespace synergy::gui

View file

@ -1,7 +1,6 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2021 Symless Ltd.
* Copyright (C) 2008 Volker Lanz (vl@fidra.de)
* Copyright (C) 2021 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -18,12 +17,14 @@
#include "ClientConnection.h"
#include "MainWindow.h"
#include <QHostAddress>
#include <QMessageBox>
ClientConnection::ClientConnection(MainWindow &parent) : m_parent(parent) {}
namespace synergy::gui {
ClientConnection::ClientConnection(QWidget &parent, AppConfig &appConfig)
: m_parent(parent),
m_appConfig(appConfig) {}
void ClientConnection::update(const QString &line) {
if (m_checkConnection && checkMainWindow()) {
@ -59,13 +60,13 @@ QString ClientConnection::getMessage(const QString &line) const {
message =
QObject::tr("Connection failed.\nYou can’t name 2 computers the same.");
} else {
QHostAddress address(m_parent.appConfig().serverHostname());
QHostAddress address(m_appConfig.serverHostname());
if (address.isNull()) {
message =
QObject::tr(
"We can’t connect to the server \"%1\" try to connect using the "
"server IP address and check your firewall settings.")
.arg(m_parent.appConfig().serverHostname());
.arg(m_appConfig.serverHostname());
}
}
@ -82,3 +83,5 @@ void ClientConnection::showMessage(const QString &message) const {
void ClientConnection::setCheckConnection(bool checkConnection) {
m_checkConnection = checkConnection;
}
} // namespace synergy::gui

View file

@ -1,7 +1,6 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2021 Symless Ltd.
* Copyright (C) 2008 Volker Lanz (vl@fidra.de)
* Copyright (C) 2021 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -16,19 +15,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CLIENTCONNECTION_H
#define CLIENTCONNECTION_H
#pragma once
#include "gui/config/AppConfig.h"
#include <QString>
#include <QWidget>
class MainWindow;
namespace synergy::gui {
class ClientConnection {
MainWindow &m_parent;
bool m_checkConnection = false;
public:
explicit ClientConnection(MainWindow &parent);
explicit ClientConnection(QWidget &parent, AppConfig &appConfig);
void update(const QString &line);
void setCheckConnection(bool checkConnection);
@ -36,6 +34,10 @@ private:
QString getMessage(const QString &line) const;
bool checkMainWindow();
void showMessage(const QString &message) const;
QWidget &m_parent;
AppConfig &m_appConfig;
bool m_checkConnection = false;
};
#endif // CLIENTCONNECTION_H
} // namespace synergy::gui

View file

@ -39,8 +39,7 @@ QString CommandProcess::run() {
}
if (process.waitForFinished()) {
output =
QString::fromLocal8Bit(process.readAllStandardOutput().trimmed());
output = process.readAllStandardOutput().trimmed();
error = process.readAllStandardError().trimmed();
}
}

View file

@ -0,0 +1,696 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "CoreProcess.h"
#include "constants.h"
#include "gui/config/IAppConfig.h"
#include "gui/core/CoreTool.h"
#include <qglobal.h>
#include <qmutex.h>
#if defined(Q_OS_MAC)
#include "OSXHelpers.h"
#endif
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QMessageBox>
#include <QMutexLocker>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QTimer>
namespace synergy::gui {
const int kRetryDelay = 1000;
const auto kLastConfigFilename = "LastConfig.cfg";
const auto kLineSplitRegex = QRegularExpression("\r|\n|\r\n");
//
// free functions
//
QString processModeToString(ProcessMode mode) {
using enum ProcessMode;
switch (mode) {
case kDesktop:
return "desktop";
case kService:
return "service";
default:
qFatal("invalid process mode");
return "";
}
}
/**
* @brief Wraps options that contain spaces in quotes
*
* Useful to handle things like paths with spaces (e.g. "C:\Program Files").
*
* Can also be used to create a representation of a command that can be pasted
* into a terminal.
*/
QString makeQuotedArgs(const QString &app, const QStringList &args) {
QStringList command;
command << app;
command << args;
QStringList quoted;
for (const auto &arg : command) {
if (arg.contains(' ')) {
quoted << QString("\"%1\"").arg(arg);
} else {
quoted << arg;
}
}
return quoted.join(" ");
}
/**
* @brief If IPv6, ensures the IP is surround in square brackets.
*/
QString wrapIpv6(const QString &address) {
if (!address.contains(':') || address.isEmpty()) {
return address;
}
QString wrapped = address;
if (address[0] != '[') {
wrapped.insert(0, '[');
}
if (address[address.size() - 1] != ']') {
wrapped.push_back(']');
}
return address;
}
//
// CoreProcess::Deps
//
QString CoreProcess::Deps::appPath(const QString &name) const {
QDir dir(QCoreApplication::applicationDirPath());
return dir.filePath(name);
}
bool CoreProcess::Deps::fileExists(const QString &path) const {
return QFile::exists(path);
}
QString CoreProcess::Deps::getProfileRoot() const {
CoreTool coreTool;
QDir appDir = coreTool.getProfileDir();
// the core expects the profile root dir, not the app-specific profile dir.
if (!appDir.cdUp()) {
qFatal("failed to cd up to profile root dir");
}
return appDir.absolutePath();
}
//
// CoreProcess
//
CoreProcess::CoreProcess(
IAppConfig &appConfig, IServerConfig &serverConfig,
std::shared_ptr<Deps> deps)
: m_appConfig(appConfig),
m_serverConfig(serverConfig),
m_pDeps(deps) {
connect(
&m_pDeps->ipcClient(), &QIpcClient::read, this, //
&CoreProcess::onIpcClientRead);
connect(
&m_pDeps->ipcClient(), &QIpcClient::serviceReady, this, //
&CoreProcess::onIpcClientServiceReady);
connect(
&m_pDeps->process(), &QProcessProxy::finished, this,
&CoreProcess::onProcessFinished);
connect(
&m_pDeps->process(), &QProcessProxy::readyReadStandardOutput, this,
&CoreProcess::onProcessReadyReadStandardOutput);
connect(
&m_pDeps->process(), &QProcessProxy::readyReadStandardError, this,
&CoreProcess::onProcessReadyReadStandardError);
}
void CoreProcess::onIpcClientServiceReady() {
if (m_processState == ProcessState::Starting) {
qDebug("service ready, continuing core process start");
start();
} else if (m_processState == ProcessState::Stopping) {
qDebug("service ready, continuing core process stop");
stop();
} else {
qCritical("service ready, but process state is not starting or stopping");
}
}
void CoreProcess::onIpcClientError(const QString &text) const {
qCritical().noquote() << text;
if (m_appConfig.processMode() != ProcessMode::kService) {
// if not meant to be in service mode and there is an ipc connection error,
// then abandon the ipc client connection.
m_pDeps->ipcClient().disconnectFromHost();
}
}
void CoreProcess::onIpcClientRead(const QString &text) { handleLogLines(text); }
void CoreProcess::onProcessReadyReadStandardOutput() {
if (m_pDeps->process()) {
handleLogLines(m_pDeps->process().readAllStandardOutput());
}
}
void CoreProcess::onProcessReadyReadStandardError() {
if (m_pDeps->process()) {
handleLogLines(m_pDeps->process().readAllStandardError());
}
}
void CoreProcess::onProcessFinished(int exitCode, QProcess::ExitStatus) {
const auto wasStarted = m_processState == ProcessState::Started;
setProcessState(ProcessState::Stopped);
setConnectionState(ConnectionState::Disconnected);
if (exitCode == 0) {
qDebug("desktop process exited normally");
} else {
qWarning("desktop process exited with error code: %d", exitCode);
}
if (wasStarted) {
qDebug("desktop process was running, retrying in %d ms", kRetryDelay);
QTimer::singleShot(kRetryDelay, [this] { start(); });
}
}
void CoreProcess::startDesktop(const QString &app, const QStringList &args) {
using enum ProcessState;
if (m_processState != Starting) {
qFatal("core process must be in starting state");
}
// only make quoted args for printing the command for convenience; so that the
// core command can be easily copy/pasted to the terminal for testing.
const auto quoted = makeQuotedArgs(app, args);
qInfo("running command: %s", qPrintable(quoted));
m_pDeps->process().start(app, args);
if (m_pDeps->process().waitForStarted()) {
setProcessState(Started);
} else {
setProcessState(Stopped);
emit error(Error::StartFailed);
}
}
void CoreProcess::startService(const QString &app, const QStringList &args) {
using enum ProcessState;
if (m_processState != Starting) {
qFatal("core process must be in starting state");
}
if (!m_pDeps->ipcClient().isConnected()) {
// when service state changes, start will be called again.
qDebug("cannot start process, ipc not connected, connecting instead");
m_pDeps->ipcClient().connectToHost();
return;
}
QString commandQuoted = makeQuotedArgs(app, args);
qInfo("running command: %s", qPrintable(commandQuoted));
m_pDeps->ipcClient().sendCommand(commandQuoted, m_appConfig.elevateMode());
setProcessState(Started);
}
void CoreProcess::stopDesktop() const {
if (m_processState != ProcessState::Stopping) {
qFatal("core process must be in stopping state");
}
if (!m_pDeps->process()) {
qFatal("process not set, cannot stop");
}
qInfo("stopping core desktop process");
if (m_pDeps->process().state() == QProcess::ProcessState::Running) {
qDebug("process is running, closing");
m_pDeps->process().close();
} else {
qDebug("process is not running, skipping terminate");
}
}
void CoreProcess::stopService() {
if (m_processState != ProcessState::Stopping) {
qFatal("core process must be in stopping state");
}
if (!m_pDeps->ipcClient().isConnected()) {
qDebug("cannot stop process, ipc not connected");
return;
}
m_pDeps->ipcClient().sendCommand("", m_appConfig.elevateMode());
setProcessState(ProcessState::Stopped);
}
void CoreProcess::handleLogLines(const QString &text) {
for (const auto &line : text.split(kLineSplitRegex)) {
if (line.isEmpty()) {
continue;
}
#if defined(Q_OS_MAC)
// HACK: macOS 10.13.4+ spamming error lines in logs making them
// impossible to read and debug; giving users a red herring.
if (line.contains("calling TIS/TSM in non-main thread environment")) {
continue;
}
#endif
checkLogLine(line);
emit logLine(line);
}
}
void CoreProcess::start(std::optional<ProcessMode> processModeOption) {
QMutexLocker locker(&m_processMutex);
const auto processMode =
processModeOption.value_or(m_appConfig.processMode());
qInfo(
"starting core %s process (%s mode)", qPrintable(modeString()),
qPrintable(processModeToString(processMode)));
if (m_processState == ProcessState::Started) {
qCritical("core process already started");
return;
}
// allow external listeners to abort the start process (e.g. licensing issue).
setProcessState(ProcessState::Starting);
emit starting();
if (m_processState == ProcessState::Stopped) {
qDebug("core process start was cancelled by listener");
return;
}
#ifdef Q_OS_MAC
requestOSXNotificationPermission();
#endif
setConnectionState(ConnectionState::Connecting);
QString app;
QStringList args;
args << "-f"
<< "--no-tray"
<< "--debug" << m_appConfig.logLevelText();
args << "--name" << m_appConfig.screenName();
if (processMode == ProcessMode::kDesktop) {
m_pDeps->process().create();
} else {
// tell client/server to talk to daemon through ipc.
args << "--ipc";
#if defined(Q_OS_WIN)
// tell the client/server to shut down when a ms windows desk
// is switched; this is because we may need to elevate or not
// based on which desk the user is in (login always needs
// elevation, where as default desk does not).
// Note that this is only enabled when synergy is set to elevate
// 'as needed' (e.g. on a UAC dialog popup) in order to prevent
// unnecessary restarts when synergy was started elevated or
// when it is not allowed to elevate. In these cases restarting
// the server is fruitless.
if (m_appConfig.elevateMode() == ElevateMode::kAutomatic) {
args << "--stop-on-desk-switch";
}
#endif
}
#ifndef Q_OS_LINUX
if (m_serverConfig.enableDragAndDrop()) {
args << "--enable-drag-drop";
}
#endif
if (m_appConfig.tlsEnabled()) {
args << "--enable-crypto";
args << "--tls-cert" << m_appConfig.tlsCertPath();
}
#if defined(Q_OS_WIN)
// on windows, the profile directory changes depending on the user that
// launched the process (e.g. when launched with elevation). setting the
// profile dir on launch ensures it uses the same profile dir is used
// no matter how its relaunched.
args << "--profile-dir" << m_pDeps->getProfileRoot();
#endif
if (m_appConfig.preventSleep()) {
args << "--prevent-sleep";
}
if ((mode() == Mode::Client && !clientArgs(args, app)) ||
(mode() == Mode::Server && !serverArgs(args, app))) {
qDebug("failed to get args for core process, aborting start");
return;
}
qDebug("log level: %s", qPrintable(m_appConfig.logLevelText()));
if (m_appConfig.logToFile())
qInfo("log file: %s", qPrintable(m_appConfig.logFilename()));
if (processMode == ProcessMode::kDesktop) {
startDesktop(app, args);
} else if (processMode == ProcessMode::kService) {
startService(app, args);
}
m_lastProcessMode = processMode;
}
void CoreProcess::stop(std::optional<ProcessMode> processModeOption) {
QMutexLocker locker(&m_processMutex);
const auto processMode =
processModeOption.value_or(m_appConfig.processMode());
qInfo(
"stopping core process (%s mode)",
qPrintable(processModeToString(processMode)));
if (m_processState == ProcessState::Starting) {
qDebug("core process is starting, cancelling");
setProcessState(ProcessState::Stopped);
} else if (m_processState != ProcessState::Stopped) {
setProcessState(ProcessState::Stopping);
if (processMode == ProcessMode::kService) {
stopService();
} else if (processMode == ProcessMode::kDesktop) {
stopDesktop();
}
} else {
qWarning("core process already stopped");
}
setConnectionState(ConnectionState::Disconnected);
}
void CoreProcess::restart() {
using enum ProcessMode;
qDebug("restarting core process");
const auto processMode = m_appConfig.processMode();
if (m_lastProcessMode != processMode) {
if (processMode == kDesktop) {
qDebug("process mode changed to desktop, stopping service process");
stop(kService);
} else if (processMode == kService) {
qDebug("process mode changed to service, stopping desktop process");
stop(kDesktop);
} else {
qFatal("invalid process mode");
}
} else {
// in service mode: though there is technically no need to stop the service
// before restarting it, it does make for cleaner process state tracking,
// especially if something goes wrong with starting the service.
stop();
}
start();
}
void CoreProcess::cleanup() {
qInfo("cleaning up core process");
const auto isDesktop = m_appConfig.processMode() == ProcessMode::kDesktop;
const auto isRunning = m_processState == ProcessState::Started;
if (isDesktop && isRunning) {
stop();
}
m_pDeps->ipcClient().disconnectFromHost();
}
bool CoreProcess::serverArgs(QStringList &args, QString &app) {
app = m_pDeps->appPath(m_appConfig.coreServerName());
if (!m_pDeps->fileExists(app)) {
qFatal("core server binary does not exist");
return false;
}
if (m_appConfig.logToFile()) {
m_appConfig.persistLogDir();
args << "--log" << m_appConfig.logFilename();
}
QString configFilename = persistConfig();
if (configFilename.isEmpty()) {
qFatal("config file name empty for server args");
return false;
}
if (m_appConfig.invertConnection()) {
qDebug("inverting server connection");
if (correctedAddress().isEmpty()) {
emit error(Error::AddressMissing);
qDebug("address is missing for server args");
return false;
}
}
// the address arg is dual purpose; when in listening mode, it's the address
// that the server listens on. when tcp sockets are inverted, it connects to
// that address. this is a bit confusing, and there should be probably be
// different args for different purposes.
args << "--address" << correctedInterface();
args << "-c" << configFilename;
qInfo("config file: %s", qPrintable(configFilename));
if (kEnableActivation && !m_appConfig.serialKey().isEmpty()) {
args << "--serial-key" << m_appConfig.serialKey();
}
return true;
}
bool CoreProcess::clientArgs(QStringList &args, QString &app) {
app = m_pDeps->appPath(m_appConfig.coreClientName());
if (!m_pDeps->fileExists(app)) {
qFatal("core client binary does not exist");
return false;
}
if (m_appConfig.logToFile()) {
m_appConfig.persistLogDir();
args << "--log" << m_appConfig.logFilename();
}
if (m_appConfig.languageSync()) {
args << "--sync-language";
}
if (m_appConfig.invertScrollDirection()) {
args << "--invert-scroll";
}
if (m_appConfig.invertConnection()) {
qDebug("inverting client connection");
args << "--host";
args << ":" + QString::number(m_appConfig.port());
} else {
if (correctedAddress().isEmpty()) {
emit error(Error::AddressMissing);
qDebug("address is missing for client args");
return false;
}
args << correctedAddress() + ":" + QString::number(m_appConfig.port());
}
return true;
}
QString CoreProcess::persistConfig() const {
QString configFullPath;
if (m_appConfig.useExternalConfig()) {
return m_appConfig.configFile();
}
for (auto path :
{QStandardPaths::AppDataLocation, QStandardPaths::AppConfigLocation}) {
auto configDirPath = QStandardPaths::writableLocation(path);
if (!QDir().mkpath(configDirPath)) {
qWarning("failed to create config folder: %s", qPrintable(configDirPath));
continue;
}
QFile configFile(configDirPath + "/" + kLastConfigFilename);
if (!configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qWarning(
"failed to open config file: %s", qPrintable(configFile.fileName()));
continue;
}
m_serverConfig.save(configFile);
configFile.close();
return configFile.fileName();
}
qFatal("failed to persist config file");
return "";
}
QString CoreProcess::modeString() const {
using enum Mode;
switch (m_mode) {
case Server:
return "server";
case Client:
return "client";
default:
qFatal("invalid core mode");
return "";
}
}
void CoreProcess::setConnectionState(ConnectionState state) {
if (m_connectionState == state) {
return;
}
m_connectionState = state;
emit connectionStateChanged(state);
}
void CoreProcess::setProcessState(ProcessState state) {
if (m_processState == state) {
return;
}
m_processState = state;
emit processStateChanged(state);
}
void CoreProcess::checkLogLine(const QString &line) {
using enum ConnectionState;
if (line.contains("connected to server") || line.contains("has connected")) {
setConnectionState(Connected);
} else if (line.contains("started server")) {
setConnectionState(Listening);
} else if (
line.contains("disconnected from server") ||
line.contains("process exited")) {
setConnectionState(Disconnected);
} else if (line.contains("connecting to")) {
setConnectionState(Connecting);
}
checkSecureSocket(line);
// subprocess (synergys, synergyc) is not allowed to show notifications
// process the log from it and show notification from synergy instead
#ifdef Q_OS_MAC
checkOSXNotification(line);
#endif
}
bool CoreProcess::checkSecureSocket(const QString &line) {
static const QString tlsCheckString = "network encryption protocol: ";
const auto index = line.indexOf(tlsCheckString, 0, Qt::CaseInsensitive);
if (index == -1) {
return false;
}
emit secureSocket(true);
m_secureSocketVersion = line.mid(index + tlsCheckString.size());
return true;
}
#ifdef Q_OS_MAC
void CoreProcess::checkOSXNotification(const QString &line) {
static const QString needle = "OSX Notification: ";
if (line.contains(needle) && line.contains('|')) {
int delimiterPosition = line.indexOf('|');
int start = line.indexOf(needle);
QString title = line.mid(
start + needle.length(), delimiterPosition - start - needle.length());
QString body =
line.mid(delimiterPosition + 1, line.length() - delimiterPosition);
if (!showOSXNotification(title, body)) {
qDebug("osx notification was not shown");
}
}
}
#endif
QString CoreProcess::correctedInterface() const {
QString interface = wrapIpv6(m_appConfig.networkInterface());
return interface + ":" + QString::number(m_appConfig.port());
}
QString CoreProcess::correctedAddress() const { return wrapIpv6(m_address); }
} // namespace synergy::gui

View file

@ -0,0 +1,130 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "gui/config/IAppConfig.h"
#include "gui/config/IServerConfig.h"
#include "gui/ipc/QIpcClient.h"
#include "gui/proxy/QProcessProxy.h"
#include <QMutex>
#include <QObject>
#include <QString>
#include <QStringList>
#include <memory>
namespace synergy::gui {
class CoreProcess : public QObject {
using IServerConfig = synergy::gui::IServerConfig;
using QProcessProxy = synergy::gui::proxy::QProcessProxy;
using IQIpcClient = synergy::gui::ipc::IQIpcClient;
Q_OBJECT
public:
struct Deps {
virtual ~Deps() = default;
virtual QProcessProxy &process() { return m_process; }
virtual IQIpcClient &ipcClient() { return m_ipcClient; }
virtual QString appPath(const QString &name) const;
virtual bool fileExists(const QString &path) const;
virtual QString getProfileRoot() const;
private:
QProcessProxy m_process;
QIpcClient m_ipcClient;
};
enum class Mode { None, Client, Server };
enum class Error { AddressMissing, StartFailed };
enum class ProcessState { Starting, Started, Stopping, Stopped };
enum class ConnectionState { Disconnected, Connecting, Connected, Listening };
explicit CoreProcess(
IAppConfig &appConfig, IServerConfig &serverConfig,
std::shared_ptr<Deps> deps = std::make_shared<Deps>());
void extracted(QString &app, QStringList &args);
void start(std::optional<ProcessMode> processMode = std::nullopt);
void stop(std::optional<ProcessMode> processMode = std::nullopt);
void restart();
void cleanup();
// getters
Mode mode() const { return m_mode; }
QString secureSocketVersion() const { return m_secureSocketVersion; }
bool isStarted() const { return m_processState == ProcessState::Started; }
ProcessState processState() const { return m_processState; }
ConnectionState connectionState() const { return m_connectionState; }
// setters
void setAddress(const QString &address) { m_address = address.trimmed(); }
void setMode(Mode mode) { m_mode = mode; }
signals:
void starting();
void error(Error error);
void logLine(const QString &line);
void connectionStateChanged(ConnectionState state);
void processStateChanged(ProcessState state);
void secureSocket(bool enabled);
private slots:
void onIpcClientServiceReady();
void onIpcClientRead(const QString &text);
void onIpcClientError(const QString &text) const;
void onProcessFinished(int exitCode, QProcess::ExitStatus);
void onProcessReadyReadStandardOutput();
void onProcessReadyReadStandardError();
private:
void startDesktop(const QString &app, const QStringList &args);
void startService(const QString &app, const QStringList &args);
void stopDesktop() const;
void stopService();
bool serverArgs(QStringList &args, QString &app);
bool clientArgs(QStringList &args, QString &app);
QString persistConfig() const;
QString modeString() const;
QString processModeString() const;
void setConnectionState(ConnectionState state);
void setProcessState(ProcessState state);
void checkLogLine(const QString &line);
bool checkSecureSocket(const QString &line);
void handleLogLines(const QString &text);
QString correctedInterface() const;
QString correctedAddress() const;
#ifdef Q_OS_MAC
void checkOSXNotification(const QString &line);
#endif
IAppConfig &m_appConfig;
IServerConfig &m_serverConfig;
std::shared_ptr<Deps> m_pDeps;
QString m_address;
ProcessState m_processState = ProcessState::Stopped;
ConnectionState m_connectionState = ConnectionState::Disconnected;
Mode m_mode = Mode::None;
QMutex m_processMutex;
QString m_secureSocketVersion = "";
std::optional<ProcessMode> m_lastProcessMode = std::nullopt;
};
} // namespace synergy::gui

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gui/CoreInterface.h"
#include "gui/core/CoreTool.h"
#include "CommandProcess.h"
@ -32,28 +32,27 @@ static const char kSerialKeyFilename[] = "Synergy.subkey";
static const char kSerialKeyFilename[] = ".synergy.subkey";
#endif
QString CoreInterface::getProfileDir() const {
QString CoreTool::getProfileDir() const {
QStringList args("--get-profile-dir");
return QDir::cleanPath(run(args));
}
QString CoreInterface::getInstalledDir() const {
QString CoreTool::getInstalledDir() const {
QStringList args("--get-installed-dir");
return QDir::cleanPath(run(args));
}
QString CoreInterface::getArch() const {
QString CoreTool::getArch() const {
QStringList args("--get-arch");
return run(args);
}
QString CoreInterface::getSerialKeyFilePath() const {
QString CoreTool::getSerialKeyFilePath() const {
auto filename = getProfileDir() + QDir::separator() + kSerialKeyFilename;
return QDir::cleanPath(filename);
}
QString
CoreInterface::run(const QStringList &args, const QString &input) const {
QString CoreTool::run(const QStringList &args, const QString &input) const {
QString program(QCoreApplication::applicationDirPath() + "/" + kCoreBinary);
CommandProcess commandProcess(program, args, input);

View file

@ -19,7 +19,7 @@
#include <QString>
class CoreInterface {
class CoreTool {
public:
QString getProfileDir() const;
QString getInstalledDir() const;

View file

@ -0,0 +1,90 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2021 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ServerConnection.h"
#include "ServerMessage.h"
#include "gui/config/AppConfig.h"
#include <QMessageBox>
namespace synergy::gui {
ServerConnection::ServerConnection(
QWidget &parent, AppConfig &appConfig, IServerConfig &serverConfig)
: m_parent(parent),
m_appConfig(appConfig),
m_serverConfig(serverConfig) {}
void ServerConnection::update(const QString &line) {
ServerMessage message(line);
if (!m_appConfig.useExternalConfig() && message.isNewClientMessage() &&
!m_ignoredClients.contains(message.getClientName())) {
addClient(message.getClientName());
}
}
// TOOD: merge duplicated code between client and server connection
bool ServerConnection::checkMainWindow() {
bool result = m_parent.isActiveWindow();
if (m_parent.isMinimized() || m_parent.isHidden()) {
m_parent.showNormal();
m_parent.activateWindow();
result = true;
}
return result;
}
void ServerConnection::addClient(const QString &clientName) {
if (m_serverConfig.isFull()) {
qDebug(
"server config full, skipping add client prompt for: %s",
qPrintable(clientName));
return;
}
if (m_serverConfig.screenExists(clientName)) {
qDebug(
"client already added, skipping add client prompt for: %s",
qPrintable(clientName));
return;
}
if (!checkMainWindow()) {
qDebug(
"main window not active, skipping add client prompt for: %s",
qPrintable(clientName));
return;
}
QMessageBox message(&m_parent);
message.addButton(QObject::tr("Ignore"), QMessageBox::RejectRole);
message.addButton(QObject::tr("Add client"), QMessageBox::AcceptRole);
message.setText(
QObject::tr("Client with name '%1' wants to connect").arg(clientName));
if (message.exec() == QMessageBox::Accepted) {
emit configureClient(clientName);
} else {
m_ignoredClients.append(clientName);
}
}
} // namespace synergy::gui

View file

@ -1,7 +1,6 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2021 Symless Ltd.
* Copyright (C) 2008 Volker Lanz (vl@fidra.de)
* Copyright (C) 2021 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -16,26 +15,36 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SERVERCONNECTION_H
#define SERVERCONNECTION_H
#pragma once
#include <QString>
#include <QStringList>
class MainWindow;
#include "gui/config/AppConfig.h"
#include "gui/config/IServerConfig.h"
class ServerConnection {
MainWindow &m_parent;
QStringList m_ignoredClients;
namespace synergy::gui {
class ServerConnection : public QObject {
Q_OBJECT
using IServerConfig = synergy::gui::IServerConfig;
public:
explicit ServerConnection(MainWindow &parent);
explicit ServerConnection(
QWidget &parent, AppConfig &appConfig, IServerConfig &serverConfig);
void update(const QString &line);
signals:
void configureClient(const QString &clientName);
private:
void addClient(const QString &clientName);
void configureClient(const QString &clientName);
bool checkMainWindow();
QWidget &m_parent;
AppConfig &m_appConfig;
IServerConfig &m_serverConfig;
QStringList m_ignoredClients;
};
#endif // SERVERCONNECTION_H
} // namespace synergy::gui

View file

@ -1,7 +1,6 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2021 Symless Ltd.
* Copyright (C) 2008 Volker Lanz (vl@fidra.de)
* Copyright (C) 2021 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -15,8 +14,11 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ServerMessage.h"
namespace synergy::gui {
ServerMessage::ServerMessage(const QString &message)
: m_message(message),
m_clienName(parseClientName(message)) {}
@ -50,3 +52,5 @@ QString ServerMessage::parseClientName(const QString &line) const {
return clientName;
}
} // namespace synergy::gui

View file

@ -1,7 +1,6 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2021 Symless Ltd.
* Copyright (C) 2008 Volker Lanz (vl@fidra.de)
* Copyright (C) 2021 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -16,11 +15,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SERVERMESSAGE_H
#define SERVERMESSAGE_H
#pragma once
#include <QString>
namespace synergy::gui {
class ServerMessage {
QString m_message;
QString m_clienName;
@ -39,4 +39,4 @@ private:
QString parseClientName(const QString &line) const;
};
#endif // SERVERMESSAGE_H
} // namespace synergy::gui

View file

@ -15,18 +15,4 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
namespace synergy::gui {
class IAppConfig {
public:
virtual ~IAppConfig() = default;
virtual QString tlsCertPath() const = 0;
virtual QString tlsKeyLength() const = 0;
virtual bool tlsEnabled() const = 0;
};
} // namespace synergy::gui
// empty .cpp for qt moc

View file

@ -0,0 +1,43 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QObject>
#include <QString>
#include "gui/config/ElevateMode.h"
namespace synergy::gui::ipc {
class IQIpcClient : public QObject {
Q_OBJECT
public:
~IQIpcClient() override = default;
virtual void sendHello() const = 0;
virtual void
sendCommand(const QString &command, ElevateMode elevate) const = 0;
virtual void connectToHost() = 0;
virtual void disconnectFromHost() = 0;
virtual bool isConnected() const = 0;
signals:
void read(const QString &text);
void serviceReady();
};
} // namespace synergy::gui::ipc

View file

@ -16,97 +16,88 @@
*/
#include "IpcReader.h"
#include "Logger.h"
#include "byte_utils.h"
#include "global/Ipc.h"
#include <QByteArray>
#include <QMutex>
#include <QTcpSocket>
#include <iostream>
using namespace synergy::gui;
IpcReader::IpcReader(QTcpSocket *socket) : m_Socket(socket) {}
IpcReader::~IpcReader() {}
void IpcReader::start() {
connect(m_Socket, SIGNAL(readyRead()), this, SLOT(read()));
void IpcReader::start() const {
connect(
m_Socket, &QTcpSocket::readyRead, this, &IpcReader::onSocketReadyRead);
}
void IpcReader::stop() {
disconnect(m_Socket, SIGNAL(readyRead()), this, SLOT(read()));
void IpcReader::stop() const {
disconnect(
m_Socket, &QTcpSocket::readyRead, this, &IpcReader::onSocketReadyRead);
}
void IpcReader::read() {
void IpcReader::onSocketReadyRead() {
QMutexLocker locker(&m_Mutex);
std::cout << "ready read" << std::endl;
logVerbose("ready read");
while (m_Socket->bytesAvailable()) {
std::cout << "bytes available" << std::endl;
logVerbose("bytes available");
char codeBuf[5];
readStream(codeBuf, 4);
codeBuf[4] = 0;
std::cout << "ipc read: " << codeBuf << std::endl;
logVerbose(QString("ipc read: %1").arg(codeBuf));
if (memcmp(codeBuf, kIpcMsgLogLine, 4) == 0) {
std::cout << "reading log line" << std::endl;
logVerbose("reading log line");
char lenBuf[4];
readStream(lenBuf, 4);
int len = bytesToInt(lenBuf, 4);
char *data = new char[len];
readStream(data, len);
QString line = QString::fromUtf8(data, len);
delete[] data;
std::vector<char> dataBuf(len);
readStream(dataBuf.data(), len);
QString text = QString::fromUtf8(dataBuf.data(), len);
readLogLine(line);
emit read(text);
} else if (memcmp(codeBuf, kIpcMsgHelloBack, 4) == 0) {
logVerbose("reading hello back");
emit helloBack();
} else {
std::cerr << "aborting, message invalid" << std::endl;
qCritical("aborting ipc read, message invalid");
return;
}
}
std::cout << "read done" << std::endl;
logVerbose("read done");
}
bool IpcReader::readStream(char *buffer, int length) {
std::cout << "reading stream" << std::endl;
logVerbose("reading stream");
int read = 0;
while (read < length) {
int ask = length - read;
if (m_Socket->bytesAvailable() < ask) {
std::cout << "buffer too short, waiting" << std::endl;
logVerbose("buffer too short, waiting");
m_Socket->waitForReadyRead(-1);
}
int got = m_Socket->read(buffer, ask);
auto got = m_Socket->read(buffer, ask);
read += got;
std::cout << "> ask=" << ask << " got=" << got << " read=" << read
<< std::endl;
logVerbose(QString("ask=%1 got=%2 read=%3").arg(ask).arg(got).arg(read));
if (got == -1) {
std::cout << "socket ended, aborting" << std::endl;
logVerbose("socket ended, aborting");
return false;
} else if (length - read > 0) {
std::cout << "more remains, seek to " << got << std::endl;
logVerbose(QString("more remains, seek to %1").arg(got));
buffer += got;
}
}
return true;
}
int IpcReader::bytesToInt(const char *buffer, int size) {
if (size == 1) {
return (unsigned char)buffer[0];
} else if (size == 2) {
return (((unsigned char)buffer[0]) << 8) + (unsigned char)buffer[1];
} else if (size == 4) {
return (((unsigned char)buffer[0]) << 24) +
(((unsigned char)buffer[1]) << 16) +
(((unsigned char)buffer[2]) << 8) + (unsigned char)buffer[3];
} else {
return 0;
}
}

View file

@ -26,20 +26,20 @@ class IpcReader : public QObject {
Q_OBJECT;
public:
IpcReader(QTcpSocket *socket);
virtual ~IpcReader();
void start();
void stop();
explicit IpcReader(QTcpSocket *socket);
~IpcReader() override = default;
void start() const;
void stop() const;
signals:
void readLogLine(const QString &text);
void read(const QString &text);
void helloBack();
private:
bool readStream(char *buffer, int length);
int bytesToInt(const char *buffer, int size);
private slots:
void read();
void onSocketReadyRead();
private:
QTcpSocket *m_Socket;

View file

@ -28,7 +28,7 @@ public:
}
virtual ~QDataStreamProxy() = default;
virtual qint64 writeRawData(const char *data, qint64 len) {
virtual qint64 writeRawData(const char *data, int len) {
assert(m_Stream);
return m_Stream->writeRawData(data, len);
}

View file

@ -0,0 +1,161 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "QIpcClient.h"
#include "IpcReader.h"
#include "byte_utils.h"
#include "global/Ipc.h"
#include <QDataStream>
#include <QHostAddress>
#include <QTimer>
#include <memory>
const auto kRetryInterval = 1000;
const auto kConnectTimeout = 5000;
using namespace synergy::gui;
QIpcClient::QIpcClient(const StreamProvider &streamProvider)
: m_streamProvider(streamProvider) {
m_pSocket = std::make_unique<QTcpSocket>();
if (!m_streamProvider) {
m_streamProvider = [this]() {
return std::make_shared<QDataStreamProxy>(m_pSocket.get());
};
}
connect(
m_pSocket.get(), &QTcpSocket::connected, this,
&QIpcClient::onSocketConnected);
connect(
m_pSocket.get(), &QTcpSocket::errorOccurred, this,
&QIpcClient::onSocketError);
m_pReader = std::make_unique<IpcReader>(m_pSocket.get());
connect(
m_pReader.get(), &IpcReader::read, this, //
&QIpcClient::onIpcReaderRead);
connect(
m_pReader.get(), &IpcReader::helloBack, this,
&QIpcClient::onIpcReaderHelloBack);
}
void QIpcClient::onSocketConnected() const { sendHello(); }
void QIpcClient::connectToHost() {
m_isConnecting = true;
qInfo("connecting to background service...");
const auto port = static_cast<quint16>(kIpcPort);
m_pSocket->connectToHost(QHostAddress(QHostAddress::LocalHost), port);
if (!m_readerStarted) {
m_pReader->start();
m_readerStarted = true;
}
QTimer::singleShot(kConnectTimeout, this, [this]() {
if (!m_isConnected) {
qCritical("ipc connection timeout");
}
});
}
void QIpcClient::disconnectFromHost() {
m_isConnecting = false;
qInfo("disconnected from background service");
m_pReader->stop();
m_pSocket->close();
m_isConnected = false;
}
void QIpcClient::onSocketError(QAbstractSocket::SocketError socketError) const {
QString text;
switch (socketError) {
case 0:
text = "connection refused";
break;
case 1:
text = "remote host closed";
break;
default:
text = QString("code=%1").arg(socketError);
break;
}
qWarning("ipc connection error, %s", qUtf8Printable(text));
QTimer::singleShot(kRetryInterval, this, &QIpcClient::onRetryConnect);
}
void QIpcClient::onRetryConnect() {
if (m_isConnected) {
qDebug("ipc already connected, skipping retry");
return;
} else if (!m_isConnecting) {
qDebug("ipc not connecting, skipping retry");
return;
}
qInfo("retrying connection to background service...");
connectToHost();
}
void QIpcClient::sendHello() const {
qDebug("sending ipc hello message");
auto stream = m_streamProvider();
stream->writeRawData(kIpcMsgHello, 4);
char typeBuf[1];
typeBuf[0] = static_cast<char>(IpcClientType::GUI);
stream->writeRawData(typeBuf, 1);
}
void QIpcClient::sendCommand(
const QString &command, ElevateMode const elevate) const {
qDebug("sending ipc command: %s", qUtf8Printable(command));
auto stream = m_streamProvider();
stream->writeRawData(kIpcMsgCommand, 4);
std::string stdStringCommand = command.toStdString();
const char *charCommand = stdStringCommand.c_str();
auto length = static_cast<int>(stdStringCommand.length());
QByteArray lenBuf = intToBytes(length);
if (lenBuf.size() != 4) {
qFatal("unexpected int buffer size: %lld", lenBuf.size());
}
stream->writeRawData(lenBuf, 4);
stream->writeRawData(charCommand, length);
char elevateBuf[1];
// see enum ElevateMode documentation for why this flag is mapped this way
elevateBuf[0] = (elevate == ElevateMode::kAlways) ? 1 : 0;
stream->writeRawData(elevateBuf, 1);
}
void QIpcClient::onIpcReaderHelloBack() {
qDebug("ipc hello back received");
m_isConnected = true;
serviceReady();
}
void QIpcClient::onIpcReaderRead(const QString &text) { emit read(text); }

View file

@ -21,46 +21,41 @@
#include <QDataStream>
#include <QObject>
#include <QTcpSocket>
#include <memory>
#include "ElevateMode.h"
#include "IpcReader.h"
#include "QDataStreamProxy.h"
#include "gui/config/ElevateMode.h"
#include "gui/ipc/IQIpcClient.h"
class IpcReader;
class QIpcClient : public QObject {
class QIpcClient : public synergy::gui::ipc::IQIpcClient {
Q_OBJECT
public:
using StreamProvider = std::function<std::shared_ptr<QDataStreamProxy>()>;
explicit QIpcClient(const StreamProvider &streamProvider = nullptr);
~QIpcClient() override;
void sendHello();
void sendCommand(const QString &command, ElevateMode elevate);
void connectToHost();
void disconnectFromHost();
public slots:
void retryConnect();
private:
void intToBytes(int value, char *buffer, int size);
void sendHello() const override;
void sendCommand(const QString &command, ElevateMode elevate) const override;
void connectToHost() override;
void disconnectFromHost() override;
bool isConnected() const override { return m_isConnected; }
private slots:
void connected();
void error(QAbstractSocket::SocketError error);
void handleReadLogLine(const QString &text);
signals:
void readLogLine(const QString &text);
void infoMessage(const QString &text);
void errorMessage(const QString &text);
void onRetryConnect();
void onSocketConnected() const;
void onIpcReaderHelloBack();
void onSocketError(QAbstractSocket::SocketError error) const;
void onIpcReaderRead(const QString &text);
private:
QTcpSocket *m_Socket;
IpcReader *m_Reader;
bool m_ReaderStarted;
bool m_Enabled;
StreamProvider m_StreamProvider;
std::unique_ptr<QTcpSocket> m_pSocket;
std::unique_ptr<IpcReader> m_pReader;
bool m_readerStarted = false;
StreamProvider m_streamProvider;
bool m_isConnected = false;
bool m_isConnecting = false;
};

View file

@ -52,6 +52,6 @@ signals:
void invalidLicense() const;
private:
bool m_enabled = kLicensingEnabled;
bool m_enabled = synergy::gui::kEnableActivation;
License m_license = License::invalid();
};

View file

@ -35,6 +35,7 @@ QString licenseNotice(const License &license) {
return subscriptionLicenseNotice(license);
} else {
qFatal("license notice only for time limited licenses");
return "";
}
}

View file

@ -17,75 +17,100 @@
#include "messages.h"
#include "Logger.h"
#include "constants.h"
#include "styles.h"
#include <QAction>
#include <QDateTime>
#include <QDir>
#include <QMessageBox>
#include <QTime>
#include <memory>
namespace synergy::gui::messages {
struct Errors {
static std::unique_ptr<QMessageBox> s_criticalMessage;
static QStringList s_ignoredErrors;
};
std::unique_ptr<QMessageBox> Errors::s_criticalMessage;
QStringList Errors::s_ignoredErrors;
void showErrorDialog(
const QString &message, const QMessageLogContext &context, QtMsgType type) {
auto filename = QFileInfo(context.file).fileName();
auto contextString = QString("%1:%2").arg(filename).arg(context.line);
auto title = type == QtFatalMsg ? "Fatal error" : "Critical error";
QString text;
if (type == QtFatalMsg) {
text = "<p>Sorry, a fatal error has occurred and the application must "
"now exit.</p>";
} else {
text = "<p>Sorry, a critical error has occurred.</p>";
}
if (kLicensedProduct) {
text += QString(R"(<p>Please <a href="%1" style="color: %2">contact us</a>)"
" and copy/paste the following error:</p>")
.arg(kUrlContact, kColorSecondary);
} else {
text +=
QString(R"(<p>Please <a href="%1" style="color: %2">report a bug</a>)"
" and copy/paste the following error:</p>")
.arg(kUrlBugReport, kColorSecondary);
}
text += QString("<pre>%3\n\n%4</pre>").arg(message, contextString);
if (type == QtFatalMsg) {
// create a blocking message box for fatal errors, as we want to wait
// until the dialog is dismissed before aborting the app.
QMessageBox::critical(nullptr, title, text);
} else if (!Errors::s_ignoredErrors.contains(message)) {
// prevent message boxes piling up by deleting the last one if it exists.
// if none exists yet, then nothing will happen.
Errors::s_criticalMessage.reset();
// as we don't abort for critical messages, create a new non-blocking
// message box. this is so that we don't block the message handler; if we
// did, we would prevent new messages from being logged properly.
// the memory will stay allocated until the app exits, which is acceptable.
Errors::s_criticalMessage = std::make_unique<QMessageBox>(
QMessageBox::Critical, title, text,
QMessageBox::Ok | QMessageBox::Ignore);
Errors::s_criticalMessage->open();
QAction::connect(
Errors::s_criticalMessage.get(), &QMessageBox::finished, //
[message](int result) {
if (result == QMessageBox::Ignore) {
Errors::s_ignoredErrors.append(message);
}
});
}
}
void messageHandler(
QtMsgType type, const QMessageLogContext &context, const QString &message) {
auto datetime = QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm:ss");
const auto filename = QString(context.file).split("/").last();
auto function = context.function ? context.function : "";
Logger::instance().handleMessage(type, context, message);
QString typeString;
auto out = stdout;
switch (type) {
case QtDebugMsg:
typeString = "DEBUG";
break;
case QtInfoMsg:
typeString = "INFO";
break;
case QtWarningMsg:
typeString = "WARNING";
out = stderr;
break;
case QtCriticalMsg:
typeString = "CRITICAL";
out = stderr;
break;
case QtFatalMsg:
typeString = "FATAL";
out = stderr;
break;
if (type == QtFatalMsg || type == QtCriticalMsg) {
showErrorDialog(message, context, type);
}
auto logLine = QString("[%1] %2: %3\n\t%4:%5, %6")
.arg(datetime)
.arg(typeString)
.arg(message)
.arg(filename)
.arg(context.line)
.arg(function);
auto logLineUtf = logLine.toUtf8();
auto logLine_c = logLineUtf.constData();
fprintf(out, "%s\n", logLine_c);
if (type == QtFatalMsg) {
auto contextString =
QString("%1:%2\n%3").arg(filename).arg(context.line).arg(function);
QMessageBox::critical(
nullptr, "Fatal error",
QString("<p>Sorry, a fatal error has occurred "
"and the application must now exit.</p>"
"<p>Please "
R"(<a href="%1" style="color: %2">contact us</a>)"
" and copy/paste the following error:</p>"
"<pre>%3\n\n%4</pre>")
.arg(kUrlContact, kColorSecondary, message, contextString));
// developers: if you hit this line in your debugger, traverse the stack to
// find the cause of the fatal error.
// important: crash the app on fatal error to prevent the app being used in
// a broken state.
// developers: if you hit this line in your debugger, traverse the stack
// to find the cause of the fatal error. important: crash the app on fatal
// error to prevent the app being used in a broken state.
//
// hint: if you don't want to crash, but still want to show an error
// message, use `qCritical()` instead of `qFatal()`. you should use
// fatal errors when the app is in an unrecoverable state; i.e. it cannot
// function correctly in it's current state and must be restarted.
abort();
}
}
@ -143,16 +168,16 @@ void showDevThanks(QWidget *parent, const QString &productName) {
QMessageBox::information(
parent, "Thank you!",
QString(
"<p>Thanks for using %1.</p>"
"<p>If you enjoy using this app, you can support the developers by "
R"(<a href="%1" style="color: %2")>purchasing a license</a>)"
" or "
R"(<a href="%3" style="color: %4")>contributing code</a>.)"
"</p>"
"<p>This message will only appear once.</p>")
QString("<p>Thanks for using %1.</p>"
"<p>If you enjoy using this app, you can support the <br />"
"developers by "
R"(<a href="%2" style="color: %3")>purchasing a license</a>)"
" or "
R"(<a href="%4" style="color: %5")>contributing code</a>.)"
"</p>"
"<p>This message will only appear once.</p>")
.arg(
productName, kUrlPurchase, kColorSecondary, kUrlContribute,
productName, kUrlPurchase, kColorSecondary, kUrlGitHub,
kColorSecondary));
}

View file

@ -0,0 +1,63 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "QProcessProxy.h"
namespace synergy::gui::proxy {
void QProcessProxy::create() {
m_pProcess = std::make_unique<QProcess>();
connect(
m_pProcess.get(), &QProcess::finished, this,
[this](int exitCode, QProcess::ExitStatus exitStatus) {
emit finished(exitCode, exitStatus);
});
connect(
m_pProcess.get(), &QProcess::readyReadStandardOutput, //
this, [this]() { emit readyReadStandardOutput(); });
connect(
m_pProcess.get(), &QProcess::readyReadStandardError, //
this, [this]() { emit readyReadStandardError(); });
}
QProcessProxy::operator bool() const { return m_pProcess.get(); }
void QProcessProxy::start(
const QString &program, const QStringList &arguments) {
m_pProcess->start(program, arguments);
}
bool QProcessProxy::waitForStarted() { return m_pProcess->waitForStarted(); }
QProcess::ProcessState QProcessProxy::state() const {
return m_pProcess->state();
}
void QProcessProxy::close() { m_pProcess->close(); }
QString QProcessProxy::readAllStandardOutput() {
return m_pProcess->readAllStandardOutput();
}
QString QProcessProxy::readAllStandardError() {
return m_pProcess->readAllStandardError();
}
} // namespace synergy::gui::proxy

View file

@ -0,0 +1,48 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QObject>
#include <QProcess>
namespace synergy::gui::proxy {
class QProcessProxy : public QObject {
Q_OBJECT
public:
~QProcessProxy() override = default;
explicit virtual operator bool() const;
virtual void create();
virtual void start(const QString &program, const QStringList &arguments = {});
virtual bool waitForStarted();
virtual QProcess::ProcessState state() const;
virtual void close();
virtual QString readAllStandardOutput();
virtual QString readAllStandardError();
signals:
void finished(int exitCode, QProcess::ExitStatus exitStatus);
void readyReadStandardOutput();
void readyReadStandardError();
private:
std::unique_ptr<QProcess> m_pProcess;
};
} // namespace synergy::gui::proxy

View file

@ -0,0 +1,27 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
/**
* @brief Useful for environment variables that have string boolean values.
*/
inline bool strToTrue(const QString &str) {
return str.toLower() == "true" || str == "1";
}

View file

@ -19,6 +19,8 @@
#include <QString>
namespace synergy::gui {
const auto kColorWhite = "#ffffff";
const auto kColorPrimary = "#ff7c00";
const auto kColorSecondary = "#4285f4";
@ -47,3 +49,5 @@ const auto kStyleNoticeLabel = //
QString("padding: 3px 5px; border-radius: 3px;"
"background-color: %1; color: %2")
.arg(kColorNotice, kColorWhite);
} // namespace synergy::gui

View file

@ -22,13 +22,10 @@
#include <QCoreApplication>
#include <QDir>
#include <QProcess>
#include <optional>
// RSA Bit length (e.g. 1024/2048/4096)
static const char *const kCertificateKeyLength = "rsa:";
// fingerprint hashing algorithm
static const char *const kCertificateHashAlgorithm = "-sha256";
static const char *const kCertificateLifetime = "365";
static const char *const kCertificateSubjectInfo = "/CN=Synergy";
static const char *const kCertificateFilename = "Synergy.pem";
@ -42,9 +39,10 @@ static const char *const kConfigFile = "synergy.conf";
static const char *const kUnixOpenSslCommand = "openssl";
#endif
namespace synergy::gui {
#if defined(Q_OS_WIN)
namespace synergy::gui {
QString openSslWindowsDir() {
auto appDir = QDir(QCoreApplication::applicationDirPath());
@ -83,16 +81,16 @@ QString openSslWindowsBinary() {
return path;
}
#endif
} // namespace synergy::gui
using namespace synergy::gui;
#endif
TlsCertificate::TlsCertificate(QObject *parent) : QObject(parent) {
m_ProfileDir = m_CoreInterface.getProfileDir();
if (m_ProfileDir.isEmpty()) {
emit error(tr("Failed to get profile directory."));
m_profileDir = m_coreTool.getProfileDir();
if (m_profileDir.isEmpty()) {
qCritical("empty profile directory result");
}
}
@ -108,92 +106,118 @@ bool TlsCertificate::runTool(const QStringList &args) {
#if defined(Q_OS_WIN)
auto openSslDir = QDir(openSslWindowsDir());
auto config = QDir::cleanPath(openSslDir.filePath(kConfigFile));
if (!QFile::exists(config)) {
qDebug("openssl config file not found: %s", qUtf8Printable(config));
// if the expected production file location doesn't exist, try the dev path.
config = QDir::cleanPath(QString("res/openssl/%1").arg(kConfigFile));
// if it still isn't there, then there's something seriously wrong.
if (!QFile::exists(config)) {
qFatal() << "openssl config file not found: " << config;
}
}
environment << QString("OPENSSL_CONF=%1").arg(config);
#endif
qDebug(
"running: %s %s", qUtf8Printable(program),
qUtf8Printable(args.join(" ")));
QProcess process;
for (const auto &envVar : environment) {
qDebug("setting env var %s", qUtf8Printable(envVar));
}
process.setEnvironment(environment);
process.start(program, args);
bool success = process.waitForStarted();
QString standardError;
QString stderrOutput;
if (success && process.waitForFinished()) {
m_ToolOutput = process.readAllStandardOutput().trimmed();
standardError = process.readAllStandardError().trimmed();
m_toolStdout = process.readAllStandardOutput().trimmed();
stderrOutput = process.readAllStandardError().trimmed();
}
if (int code = process.exitCode(); !success || code != 0) {
emit error(QString("SSL tool failed: %1\n\nCode: %2\nError: %3")
.arg(program)
.arg(process.exitCode())
.arg(standardError.isEmpty() ? "Unknown" : standardError));
qDebug(
"openssl failed with code %d: %s", code, qUtf8Printable(stderrOutput));
qCritical(
"failed to generate TLS certificate:\n\n%s",
qUtf8Printable(stderrOutput));
return false;
}
return true;
}
void TlsCertificate::generateCertificate(
const QString &path, const QString &keyLength, bool forceGen) {
bool TlsCertificate::generateCertificate(const QString &path, int keyLength) {
QString sslDirPath =
QString("%1%2%3").arg(m_ProfileDir).arg(QDir::separator()).arg(kSslDir);
QString("%1%2%3").arg(m_profileDir).arg(QDir::separator()).arg(kSslDir);
QString filename = QString("%1%2%3")
.arg(sslDirPath)
.arg(QDir::separator())
.arg(kCertificateFilename);
QString defaultPath = QString("%1%2%3")
.arg(sslDirPath)
.arg(QDir::separator())
.arg(kCertificateFilename);
QString keySize = kCertificateKeyLength + keyLength;
QString keySize = kCertificateKeyLength + QString::number(keyLength);
const QString pathToUse = path.isEmpty() ? filename : path;
const QString pathToUse =
QDir::cleanPath(path.isEmpty() ? defaultPath : path);
if (QFile file(pathToUse); !file.exists() || forceGen) {
QStringList arguments;
qDebug("generating tls certificate: %s", qUtf8Printable(pathToUse));
// self signed certificate
arguments.append("req");
arguments.append("-x509");
arguments.append("-nodes");
QStringList arguments;
// valide duration
arguments.append("-days");
arguments.append(kCertificateLifetime);
// self signed certificate
arguments.append("req");
arguments.append("-x509");
arguments.append("-nodes");
// subject information
arguments.append("-subj");
// valide duration
arguments.append("-days");
arguments.append(kCertificateLifetime);
QString subInfo(kCertificateSubjectInfo);
arguments.append(subInfo);
// subject information
arguments.append("-subj");
// private key
arguments.append("-newkey");
arguments.append(keySize);
QString subInfo(kCertificateSubjectInfo);
arguments.append(subInfo);
if (QDir sslDir(sslDirPath); !sslDir.exists()) {
sslDir.mkpath(".");
}
// private key
arguments.append("-newkey");
arguments.append(keySize);
// key output filename
arguments.append("-keyout");
arguments.append(pathToUse);
// certificate output filename
arguments.append("-out");
arguments.append(pathToUse);
if (!runTool(arguments)) {
return;
}
generateFingerprint(pathToUse);
emit info(tr("SSL certificate generated."));
if (QDir sslDir(sslDirPath); !sslDir.exists()) {
sslDir.mkpath(".");
}
emit generateFinished();
// key output filename
arguments.append("-keyout");
arguments.append(pathToUse);
// certificate output filename
arguments.append("-out");
arguments.append(pathToUse);
if (runTool(arguments)) {
qDebug("tls certificate generated");
return generateFingerprint(pathToUse);
} else {
qCritical("failed to generate tls certificate");
return false;
}
}
void TlsCertificate::generateFingerprint(const QString &certificateFilename) {
bool TlsCertificate::generateFingerprint(const QString &certificateFilename) {
qDebug("generating tls fingerprint");
QStringList arguments;
arguments.append("x509");
arguments.append("-fingerprint");
@ -203,23 +227,26 @@ void TlsCertificate::generateFingerprint(const QString &certificateFilename) {
arguments.append(certificateFilename);
if (!runTool(arguments)) {
return;
qCritical("failed to generate tls fingerprint");
return false;
}
// find the fingerprint from the tool output
auto i = m_ToolOutput.indexOf("=");
auto i = m_toolStdout.indexOf("=");
if (i != -1) {
i++;
QString fingerprint = m_ToolOutput.mid(i, m_ToolOutput.size() - i);
QString fingerprint = m_toolStdout.mid(i, m_toolStdout.size() - i);
TlsFingerprint::local().trust(fingerprint, false);
emit info(tr("SSL fingerprint generated."));
qDebug("tls fingerprint generated");
return true;
} else {
emit error(tr("Failed to find SSL fingerprint."));
qCritical("failed to find tls fingerprint in tls tool output");
return false;
}
}
QString TlsCertificate::getCertKeyLength(const QString &path) {
int TlsCertificate::getCertKeyLength(const QString &path) {
QStringList arguments;
arguments.append("rsa");
@ -229,17 +256,19 @@ QString TlsCertificate::getCertKeyLength(const QString &path) {
arguments.append("-noout");
if (!runTool(arguments)) {
return QString();
qFatal("failed to get key length from certificate");
return 0;
}
const QString searchStart("Private-Key: (");
const QString searchEnd(" bit");
// Get the line that contains the key length from the output
const auto indexStart = m_ToolOutput.indexOf(searchStart);
const auto indexEnd = m_ToolOutput.indexOf(searchEnd, indexStart);
const auto indexStart = m_toolStdout.indexOf(searchStart);
const auto indexEnd = m_toolStdout.indexOf(searchEnd, indexStart);
const auto start = indexStart + searchStart.length();
const auto end = indexEnd - (indexStart + searchStart.length());
auto keyLength = m_ToolOutput.mid(start, end);
auto keyLength = m_toolStdout.mid(start, end);
return keyLength;
return keyLength.toInt();
}

View file

@ -0,0 +1,41 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "gui/core/CoreTool.h"
#include <QObject>
class TlsCertificate : public QObject {
Q_OBJECT
public:
explicit TlsCertificate(QObject *parent = nullptr);
bool generateCertificate(const QString &path, int keyLength);
int getCertKeyLength(const QString &path);
private:
bool runTool(const QStringList &args);
bool generateFingerprint(const QString &certificateFilename);
private:
QString m_profileDir;
QString m_toolStdout;
[[no_unique_address]] CoreTool m_coreTool;
};

View file

@ -17,7 +17,7 @@
#include "TlsFingerprint.h"
#include "gui/CoreInterface.h"
#include "gui/core/CoreTool.h"
#include <QDir>
#include <QTextStream>
@ -27,11 +27,10 @@ static const char kLocalFilename[] = "Local.txt";
static const char kTrustedServersFilename[] = "TrustedServers.txt";
static const char kTrustedClientsFilename[] = "TrustedClients.txt";
TlsFingerprint::TlsFingerprint(const QString &filename) {
m_Filename = filename;
}
TlsFingerprint::TlsFingerprint(const QString &filename)
: m_Filename(filename) {}
void TlsFingerprint::trust(const QString &fingerprintText, bool append) {
void TlsFingerprint::trust(const QString &fingerprintText, bool append) const {
TlsFingerprint::persistDirectory();
QIODevice::OpenMode openMode;
@ -59,7 +58,7 @@ bool TlsFingerprint::fileExists() const {
return file.exists();
}
bool TlsFingerprint::isTrusted(const QString &fingerprintText) {
bool TlsFingerprint::isTrusted(const QString &fingerprintText) const {
QStringList list = readList();
foreach (QString trusted, list) {
if (trusted == fingerprintText) {
@ -69,7 +68,7 @@ bool TlsFingerprint::isTrusted(const QString &fingerprintText) {
return false;
}
QStringList TlsFingerprint::readList(const int readTo) {
QStringList TlsFingerprint::readList(const int readTo) const {
QStringList list;
QString dirName = TlsFingerprint::directoryPath();
@ -93,7 +92,7 @@ QStringList TlsFingerprint::readList(const int readTo) {
return list;
}
QString TlsFingerprint::readFirst() {
QString TlsFingerprint::readFirst() const {
QStringList list = readList(1);
return list.at(0);
}
@ -111,8 +110,8 @@ void TlsFingerprint::persistDirectory() {
}
QString TlsFingerprint::directoryPath() {
CoreInterface coreInterface;
QString profileDir = coreInterface.getProfileDir();
CoreTool coreTool;
QString profileDir = coreTool.getProfileDir();
return QString("%1/%2").arg(profileDir).arg(kDirName);
}

View file

@ -21,15 +21,7 @@
class TlsFingerprint {
private:
TlsFingerprint(const QString &filename);
public:
void trust(const QString &fingerprintText, bool append = true);
bool isTrusted(const QString &fingerprintText);
QStringList readList(const int readTo = -1);
QString readFirst();
QString filePath() const;
bool fileExists() const;
explicit TlsFingerprint(const QString &filename);
public:
static TlsFingerprint local();
@ -40,6 +32,13 @@ public:
static bool localFingerprintExists();
static void persistDirectory();
void trust(const QString &fingerprintText, bool append = true) const;
bool isTrusted(const QString &fingerprintText) const;
QStringList readList(const int readTo = -1) const;
QString readFirst() const;
QString filePath() const;
bool fileExists() const;
private:
QString m_Filename;
};

View file

@ -20,10 +20,17 @@
#include "TlsCertificate.h"
#include "constants.h"
#include <QString>
namespace synergy::gui {
TlsUtility::TlsUtility(
const IAppConfig &appConfig, const license::License &license)
: m_appConfig(appConfig),
m_license(license) {}
bool TlsUtility::isAvailable() const {
return !kLicensingEnabled || m_license.isTlsAvailable();
return !kEnableActivation || m_license.isTlsAvailable();
}
bool TlsUtility::isAvailableAndEnabled() const {
@ -31,20 +38,20 @@ bool TlsUtility::isAvailableAndEnabled() const {
return isAvailable() && config.tlsEnabled();
}
void TlsUtility::generateCertificate(bool replace) const {
bool TlsUtility::generateCertificate() {
qDebug("generating tls certificate, "
"all clients must trust the new fingerprint");
if (!isAvailableAndEnabled()) {
qFatal("unable to generate tls certificate, "
"tls is either not available or not enabled");
qCritical("unable to generate tls certificate, "
"tls is either not available or not enabled");
return false;
}
auto path = m_appConfig.tlsCertPath();
auto length = m_appConfig.tlsKeyLength();
TlsCertificate cert;
cert.generateCertificate(path, length, replace);
return m_certificate.generateCertificate(path, length);
}
} // namespace synergy::gui

View file

@ -17,18 +17,23 @@
#pragma once
#include "IAppConfig.h"
#include "gui/config/IAppConfig.h"
#include "TlsCertificate.h"
#include "license/License.h"
#include <QObject>
namespace synergy::gui {
class TlsUtility {
class TlsUtility : public QObject {
Q_OBJECT
public:
explicit TlsUtility(
const IAppConfig &appConfig, const license::License &license)
: m_appConfig(appConfig),
m_license(license) {}
const IAppConfig &appConfig, const license::License &license);
bool generateCertificate();
/**
* @brief Combines the availability and the enabled status of TLS.
@ -44,14 +49,10 @@ public:
*/
bool isAvailable() const;
/**
* @param replace Replace certificate on disk (default: false).
*/
void generateCertificate(bool replace = false) const;
private:
const IAppConfig &m_appConfig;
const license::License &m_license;
TlsCertificate m_certificate;
};
} // namespace synergy::gui

View file

@ -56,7 +56,11 @@ Screen::~Screen() {
disable();
}
assert(!m_enabled);
// TODO: why assert this? it appears to be false when an elevated dialog
// appears on windows and the process is killed.
assert(m_entered == m_isPrimary);
delete m_screen;
LOG((CLOG_DEBUG "closed display"));
}

View file

@ -15,14 +15,23 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gui/CommandProcess.h"
#include "gui/core/CommandProcess.h"
#include <gtest/gtest.h>
TEST(CommandProcessTests, run_commandSucceeds_returnsOutput) {
// it seems that on windows, you can sometimes just call echo (this worked
// with windows 2022), but on windows 10 you can't call echo directly.
#if defined(Q_OS_WIN)
const QString command = "cmd";
const QStringList arguments = {"/C", "echo Hello, World!"};
const QString input = "";
#elif defined(Q_OS_UNIX)
const QString command = "echo";
const QStringList arguments = {"Hello, World!"};
const QString input = "";
#endif // Q_OS_WIN
CommandProcess commandProcess(command, arguments, input);

View file

@ -15,41 +15,41 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gui/CoreInterface.h"
#include "gui/core/CoreTool.h"
#include "shared/gui/QtCoreTest.h"
#include <gtest/gtest.h>
class CoreInterfaceTests : public QtCoreTest {};
class CoreToolTests : public QtCoreTest {};
TEST_F(CoreInterfaceTests, getProfileDir_noMock_returnsNonEmpty) {
CoreInterface coreInterface;
TEST_F(CoreToolTests, getProfileDir_noMock_returnsNonEmpty) {
CoreTool coreTool;
QString profileDir = coreInterface.getProfileDir();
QString profileDir = coreTool.getProfileDir();
EXPECT_FALSE(profileDir.isEmpty());
}
TEST_F(CoreInterfaceTests, getInstalledDir_noMock_returnsNonEmpty) {
CoreInterface coreInterface;
TEST_F(CoreToolTests, getInstalledDir_noMock_returnsNonEmpty) {
CoreTool coreTool;
QString installedDir = coreInterface.getInstalledDir();
QString installedDir = coreTool.getInstalledDir();
EXPECT_FALSE(installedDir.isEmpty());
}
TEST_F(CoreInterfaceTests, getArch_noMock_returnsNonEmpty) {
CoreInterface coreInterface;
TEST_F(CoreToolTests, getArch_noMock_returnsNonEmpty) {
CoreTool coreTool;
QString arch = coreInterface.getArch();
QString arch = coreTool.getArch();
EXPECT_FALSE(arch.isEmpty());
}
TEST_F(CoreInterfaceTests, getSerialKeyFilePath_noMock_returnsNonEmpty) {
CoreInterface coreInterface;
TEST_F(CoreToolTests, getSerialKeyFilePath_noMock_returnsNonEmpty) {
CoreTool coreTool;
QString serialKeyFilePath = coreInterface.getSerialKeyFilePath();
QString serialKeyFilePath = coreTool.getSerialKeyFilePath();
EXPECT_FALSE(serialKeyFilePath.isEmpty());
}

View file

@ -33,7 +33,7 @@ void TestEventQueue::initQuitTimeout(double timeout) {
void TestEventQueue::cleanupQuitTimeout() {
removeHandler(Event::kTimer, m_pQuitTimeoutTimer);
delete m_pQuitTimeoutTimer;
deleteTimer(m_pQuitTimeoutTimer);
m_pQuitTimeoutTimer = nullptr;
}

View file

@ -0,0 +1,40 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gui/byte_utils.h"
#include <gtest/gtest.h>
using namespace synergy::gui;
TEST(byte_utils_tests, bytesToInt_size4) {
char buffer[4] = {0x01, 0x02, 0x03, 0x04};
const auto i = bytesToInt(buffer, 4);
EXPECT_EQ(i, 0x01020304);
}
TEST(byte_utils_tests, intToBytes_size4) {
QByteArray bytes = intToBytes(0x01020304);
EXPECT_EQ(bytes.size(), 4);
EXPECT_EQ(bytes[0], 0x01);
EXPECT_EQ(bytes[1], 0x02);
EXPECT_EQ(bytes[2], 0x03);
EXPECT_EQ(bytes[3], 0x04);
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gui/AppConfig.h"
#include "gui/config/AppConfig.h"
#include "gmock/gmock.h"
#include <gmock/gmock.h>

View file

@ -0,0 +1,231 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2024 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gui/config/IAppConfig.h"
#include "gui/core/CoreProcess.h"
#include "gui/ipc/IQIpcClient.h"
#include "gui/proxy/QProcessProxy.h"
#include "gmock/gmock.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace synergy::gui;
using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::ReturnRef;
class MockAppConfig : public IAppConfig {
public:
MockAppConfig() {
ON_CALL(*this, screenName()).WillByDefault(ReturnRef(m_stubName));
ON_CALL(*this, networkInterface())
.WillByDefault(ReturnRef(m_stubInterface));
ON_CALL(*this, logLevelText()).WillByDefault(Return("stub log level"));
}
MOCK_METHOD(QString, tlsCertPath, (), (const, override));
MOCK_METHOD(int, tlsKeyLength, (), (const, override));
MOCK_METHOD(bool, tlsEnabled, (), (const, override));
MOCK_METHOD(ProcessMode, processMode, (), (const, override));
MOCK_METHOD(ElevateMode, elevateMode, (), (const, override));
MOCK_METHOD(QString, logLevelText, (), (const, override));
MOCK_METHOD(const QString &, screenName, (), (const, override));
MOCK_METHOD(bool, preventSleep, (), (const, override));
MOCK_METHOD(bool, logToFile, (), (const, override));
MOCK_METHOD(const QString &, logFilename, (), (const, override));
MOCK_METHOD(QString, coreServerName, (), (const, override));
MOCK_METHOD(QString, coreClientName, (), (const, override));
MOCK_METHOD(bool, invertConnection, (), (const, override));
MOCK_METHOD(void, persistLogDir, (), (const, override));
MOCK_METHOD(QString, serialKey, (), (const, override));
MOCK_METHOD(bool, languageSync, (), (const, override));
MOCK_METHOD(bool, invertScrollDirection, (), (const, override));
MOCK_METHOD(int, port, (), (const, override));
MOCK_METHOD(bool, useExternalConfig, (), (const, override));
MOCK_METHOD(const QString &, configFile, (), (const, override));
MOCK_METHOD(const QString &, networkInterface, (), (const, override));
private:
const QString m_stubName = "stub name";
const QString m_stubInterface = "stub interface";
const QString m_stubAddress = "stub address";
};
class MockServerConfig : public IServerConfig {
public:
MOCK_METHOD(bool, isFull, (), (const, override));
MOCK_METHOD(
bool, screenExists, (const QString &screenName), (const, override));
MOCK_METHOD(bool, save, (const QString &fileName), (const, override));
MOCK_METHOD(void, save, (QFile & file), (const, override));
MOCK_METHOD(bool, enableDragAndDrop, (), (const, override));
};
class MockQProcessProxy : public proxy::QProcessProxy {
public:
operator bool() const override { return toBool(); }
MockQProcessProxy() {
ON_CALL(*this, toBool()).WillByDefault(Return(true));
ON_CALL(*this, state())
.WillByDefault(Return(QProcess::ProcessState::Running));
ON_CALL(*this, waitForStarted()).WillByDefault(Return(true));
}
MOCK_METHOD(bool, toBool, (), (const));
MOCK_METHOD(void, create, (), (override));
MOCK_METHOD(
void, start, (const QString &program, const QStringList &arguments),
(override));
MOCK_METHOD(bool, waitForStarted, (), (override));
MOCK_METHOD(QProcess::ProcessState, state, (), (const, override));
MOCK_METHOD(void, close, (), (override));
MOCK_METHOD(QString, readAllStandardOutput, (), (override));
MOCK_METHOD(QString, readAllStandardError, (), (override));
};
class MockQIpcClient : public ipc::IQIpcClient {
public:
MockQIpcClient() {
ON_CALL(*this, isConnected()).WillByDefault(Return(true));
}
MOCK_METHOD(void, sendHello, (), (const, override));
MOCK_METHOD(
void, sendCommand, (const QString &command, ElevateMode elevate),
(const, override));
MOCK_METHOD(void, connectToHost, (), (override));
MOCK_METHOD(void, disconnectFromHost, (), (override));
MOCK_METHOD(bool, isConnected, (), (const, override));
};
class MockDeps : public CoreProcess::Deps {
public:
MockDeps() {
ON_CALL(*this, process()).WillByDefault(ReturnRef(m_process));
ON_CALL(*this, ipcClient()).WillByDefault(ReturnRef(m_ipcClient));
ON_CALL(*this, appPath(_)).WillByDefault(Return("stub app path"));
ON_CALL(*this, fileExists(_)).WillByDefault(Return(true));
ON_CALL(*this, getProfileRoot()).WillByDefault(Return("stub profile"));
}
MOCK_METHOD(proxy::QProcessProxy &, process, (), (override));
MOCK_METHOD(ipc::IQIpcClient &, ipcClient, (), (override));
MOCK_METHOD(QString, appPath, (const QString &name), (const, override));
MOCK_METHOD(bool, fileExists, (const QString &path), (const, override));
MOCK_METHOD(QString, getProfileRoot, (), (const, override));
NiceMock<MockQProcessProxy> m_process;
NiceMock<MockQIpcClient> m_ipcClient;
};
class CoreProcessTests : public ::testing::Test {
public:
CoreProcessTests() : m_coreProcess(m_appConfig, m_serverConfig, m_pDeps) {}
NiceMock<MockAppConfig> m_appConfig;
NiceMock<MockServerConfig> m_serverConfig;
std::shared_ptr<NiceMock<MockDeps>> m_pDeps =
std::make_shared<NiceMock<MockDeps>>();
CoreProcess m_coreProcess;
};
TEST_F(CoreProcessTests, start_serverDesktop_callsProcessStart) {
m_coreProcess.setMode(CoreProcess::Mode::Server);
EXPECT_CALL(m_pDeps->m_process, start(_, _)).Times(1);
m_coreProcess.start(ProcessMode::kDesktop);
}
TEST_F(CoreProcessTests, start_serverService_callsSendCommand) {
m_coreProcess.setMode(CoreProcess::Mode::Server);
EXPECT_CALL(m_pDeps->m_ipcClient, sendCommand(_, _)).Times(1);
m_coreProcess.start(ProcessMode::kService);
}
TEST_F(CoreProcessTests, start_clientDesktop_callsProcessStart) {
m_coreProcess.setMode(CoreProcess::Mode::Client);
m_coreProcess.setAddress("stub address");
EXPECT_CALL(m_pDeps->m_process, start(_, _)).Times(1);
m_coreProcess.start(ProcessMode::kDesktop);
}
TEST_F(CoreProcessTests, start_clientService_callsSendCommand) {
m_coreProcess.setMode(CoreProcess::Mode::Client);
m_coreProcess.setAddress("stub address");
EXPECT_CALL(m_pDeps->m_ipcClient, sendCommand(_, _)).Times(1);
m_coreProcess.start(ProcessMode::kService);
}
TEST_F(CoreProcessTests, stop_serverDesktop_callsProcessClose) {
m_coreProcess.setMode(CoreProcess::Mode::Server);
m_coreProcess.start();
EXPECT_CALL(m_pDeps->m_process, close()).Times(1);
m_coreProcess.stop(ProcessMode::kDesktop);
}
TEST_F(CoreProcessTests, stop_serverService_callsSendCommand) {
m_coreProcess.setMode(CoreProcess::Mode::Server);
m_coreProcess.start();
EXPECT_CALL(m_pDeps->m_ipcClient, sendCommand(_, _)).Times(1);
m_coreProcess.stop(ProcessMode::kService);
}
TEST_F(CoreProcessTests, stop_clientDesktop_callsProcessClose) {
m_coreProcess.setMode(CoreProcess::Mode::Client);
m_coreProcess.setAddress("stub address");
m_coreProcess.start();
EXPECT_CALL(m_pDeps->m_process, close()).Times(1);
m_coreProcess.stop(ProcessMode::kDesktop);
}
TEST_F(CoreProcessTests, stop_clientService_callsSendCommand) {
m_coreProcess.setMode(CoreProcess::Mode::Client);
m_coreProcess.setAddress("stub address");
m_coreProcess.start();
EXPECT_CALL(m_pDeps->m_ipcClient, sendCommand(_, _)).Times(1);
m_coreProcess.stop(ProcessMode::kService);
}
TEST_F(CoreProcessTests, restart_serverDesktop_callsProcessStart) {
ON_CALL(m_appConfig, processMode())
.WillByDefault(Return(ProcessMode::kDesktop));
m_coreProcess.setMode(CoreProcess::Mode::Server);
m_coreProcess.start();
EXPECT_CALL(m_pDeps->m_process, close()).Times(1);
EXPECT_CALL(m_pDeps->m_process, start(_, _)).Times(1);
m_coreProcess.restart();
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gui/QIpcClient.h"
#include "gui/ipc/QIpcClient.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@ -25,7 +25,7 @@ using testing::StrEq;
class MockStream : public QDataStreamProxy {
public:
MOCK_METHOD(qint64, writeRawData, (const char *, qint64), (override));
MOCK_METHOD(qint64, writeRawData, (const char *, int), (override));
};
TEST(QIpcClientTests, sendCommand_anyCommand_commandSent) {
@ -38,5 +38,5 @@ TEST(QIpcClientTests, sendCommand_anyCommand_commandSent) {
EXPECT_CALL(*mockStream, writeRawData(StrEq("test"), 4)).Times(1);
QIpcClient ipcClient(streamProvider);
ipcClient.sendCommand("test", ElevateMode::ElevateAsNeeded);
ipcClient.sendCommand("test", ElevateMode::kAutomatic);
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gui/LicenseHandler.h"
#include "gui/license/LicenseHandler.h"
#include <chrono>
#include <gtest/gtest.h>

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gui/license_notices.h"
#include "gui/license/license_notices.h"
#include <chrono>
#include <gmock/gmock.h>