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) # Version checker URL to use (useful for testing)
# SYNERGY_VERSION_URL="http://localhost:8787?version=v1" # 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 # Build
# #

View file

@ -26,8 +26,8 @@ env:
SYNERGY_VERSION: ${{ github.event.inputs.version || github.event.release.tag_name }} SYNERGY_VERSION: ${{ github.event.inputs.version || github.event.release.tag_name }}
SYNERGY_PRODUCT_NAME: ${{ vars.SYNERGY_PRODUCT_NAME }} SYNERGY_PRODUCT_NAME: ${{ vars.SYNERGY_PRODUCT_NAME }}
SYNERGY_PACKAGE_PREFIX: ${{ vars.SYNERGY_PACKAGE_PREFIX }} SYNERGY_PACKAGE_PREFIX: ${{ vars.SYNERGY_PACKAGE_PREFIX }}
SYNERGY_ENABLE_LICENSING: ${{ vars.SYNERGY_ENABLE_LICENSING }} SYNERGY_LICENSED_PRODUCT: ${{ vars.SYNERGY_LICENSED_PRODUCT }}
SYNERGY_SHOW_DEV_THANKS: ${{ vars.SYNERGY_SHOW_DEV_THANKS }} SYNERGY_ENABLE_ACTIVATION: ${{ vars.SYNERGY_ENABLE_ACTIVATION }}
PACKAGE_BUILD: ${{ !github.event.pull_request.draft }} PACKAGE_BUILD: ${{ !github.event.pull_request.draft }}
PACKAGE_UPLOAD: ${{ !github.event.pull_request.draft && github.event_name != 'schedule' }} PACKAGE_UPLOAD: ${{ !github.event.pull_request.draft && github.event_name != 'schedule' }}
UPLOAD_TO_GITHUB: ${{ github.event_name == 'pull_request' && !github.event.pull_request.draft }} 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", "name": "gui windows",
"type": "cppvsdbg", "type": "cppvsdbg",
"cwd": "${workspaceRoot}/build/bin", "cwd": "${workspaceRoot}",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/build/bin/synergy", "program": "${workspaceFolder}/build/bin/synergy",
"internalConsoleOptions": "openOnSessionStart", "internalConsoleOptions": "openOnSessionStart",
@ -60,7 +60,7 @@
"preLaunchTask": "build" "preLaunchTask": "build"
}, },
{ {
"name": "daemon windows attach", "name": "windows attach",
"type": "cppvsdbg", "type": "cppvsdbg",
"request": "attach", "request": "attach",
"processId": "${command:pickProcess}" "processId": "${command:pickProcess}"

View file

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

View file

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

View file

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

View file

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

View file

@ -29,11 +29,6 @@ file(
src/widgets/*) src/widgets/*)
file(GLOB GUI_UI_FILES src/*.ui) 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) if(WIN32)
set(GUI_RC_FILES res/win/Synergy.rc ${CMAKE_BINARY_DIR}/src/version.rc) set(GUI_RC_FILES res/win/Synergy.rc ${CMAKE_BINARY_DIR}/src/version.rc)
endif() endif()

View file

@ -21,7 +21,7 @@
#include <QDateTime> #include <QDateTime>
#if defined(Q_OS_MAC) #if defined(Q_OS_MAC)
#include "OSXHelpers.h" #include "gui/OSXHelpers.h"
#endif #endif
#include "gui/version.h" #include "gui/version.h"
@ -29,8 +29,11 @@
AboutDialog::AboutDialog(MainWindow *parent) AboutDialog::AboutDialog(MainWindow *parent)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint), : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint),
Ui::AboutDialogBase() { Ui::AboutDialogBase() {
setupUi(this); setupUi(this);
this->setFixedSize(this->size());
m_pLabelSynergyVersion->setText(synergy::gui::version()); m_pLabelSynergyVersion->setText(synergy::gui::version());
QString buildDateString = QString::fromLocal8Bit(__DATE__).simplified(); QString buildDateString = QString::fromLocal8Bit(__DATE__).simplified();
@ -59,21 +62,47 @@ void AboutDialog::updateLogo() const {
} }
QString AboutDialog::getImportantDevelopers() const { QString AboutDialog::getImportantDevelopers() const {
return QString( QStringList awesomePeople;
// The ultimate creator
"Chris Schoeneman, "
// Precursor developers // Chris is the ultimate creator, and the one who started it all in 2001.
"Richard Lee, Adam Feder, " awesomePeople << "Chris Schoeneman";
// Contributors // Richard and Adam developed CosmoSynergy, the 90's predecessor project.
"Nick Bolton, Volker Lanz, Ryan Breen, Guido Poschta, " awesomePeople << "Richard Lee" << "Adam Feder";
"Bertrand Landry Hetu, Tom Chadwick, Brent Priddy, Jason Axelson, "
"Jake Petroules, Sorin Sbârnea, "
// Symless employees // Nick continued the legacy in 2009 started by Chris.
"Kyle Bloom, Daun Chung, Serhii Hadzhylov, " awesomePeople << "Nick Bolton";
"Oleksandr Lysytsia, Olena Kutytska.");
// 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 { QString AboutDialog::getCopyright() const {

View file

@ -12,28 +12,10 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>450</width> <width>600</width>
<height>495</height> <height>400</height>
</rect> </rect>
</property> </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"> <property name="windowTitle">
<string>About Synergy</string> <string>About Synergy</string>
</property> </property>
@ -90,7 +72,6 @@
<widget class="QLabel" name="m_pTitleLabel"> <widget class="QLabel" name="m_pTitleLabel">
<property name="font"> <property name="font">
<font> <font>
<weight>75</weight>
<bold>true</bold> <bold>true</bold>
</font> </font>
</property> </property>
@ -161,6 +142,9 @@
<property name="text"> <property name="text">
<string>Developers</string> <string>Developers</string>
</property> </property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>

View file

@ -19,10 +19,10 @@
#include "CancelActivationDialog.h" #include "CancelActivationDialog.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "gui/AppConfig.h" #include "gui/config/AppConfig.h"
#include "gui/LicenseHandler.h"
#include "gui/constants.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 "gui/styles.h"
#include "license/ProductEdition.h" #include "license/ProductEdition.h"
#include "license/parse_serial_key.h" #include "license/parse_serial_key.h"

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -24,4 +24,8 @@
#include <QtGui> #include <QtGui>
QSynergyApplication::QSynergyApplication(int &argc, char **argv) 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 <QtCore>
#include <QtGui> #include <QtGui>
using namespace synergy::gui;
ScreenSettingsDialog::ScreenSettingsDialog( ScreenSettingsDialog::ScreenSettingsDialog(
QWidget *parent, Screen *pScreen, const ScreenList *pScreens) QWidget *parent, Screen *pScreen, const ScreenList *pScreens)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint), : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint),

View file

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

View file

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

View file

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

View file

@ -76,7 +76,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer_2"> <spacer name="horizontalSpacer_21">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
@ -117,7 +117,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer_3"> <spacer name="horizontalSpacer_22">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
@ -342,7 +342,7 @@
<number>9</number> <number>9</number>
</property> </property>
<item row="1" column="0"> <item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_21">
<property name="spacing"> <property name="spacing">
<number>0</number> <number>0</number>
</property> </property>
@ -482,7 +482,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="verticalSpacer_4"> <spacer name="verticalSpacer_41">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
@ -644,7 +644,7 @@
</widget> </widget>
</item> </item>
<item row="1" column="5"> <item row="1" column="5">
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_21">
<item> <item>
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
@ -1105,7 +1105,7 @@ Enabling this setting will disable the server config GUI.</string>
</layout> </layout>
</item> </item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer_1">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </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 "MainWindow.h"
#include "UpgradeDialog.h" #include "UpgradeDialog.h"
#include "gui/AppConfig.h" #include "gui/config/AppConfig.h"
#include "gui/TlsCertificate.h"
#include "gui/constants.h" #include "gui/constants.h"
#include "gui/tls/TlsCertificate.h"
#include "gui/tls/TlsUtility.h"
#include "validators/ScreenNameValidator.h" #include "validators/ScreenNameValidator.h"
#include "validators/ValidationError.h" #include "validators/ValidationError.h"
@ -31,8 +32,10 @@
#include <QMessageBox> #include <QMessageBox>
#include <QtCore> #include <QtCore>
#include <QtGui> #include <QtGui>
#include <qglobal.h>
using namespace synergy::license; using namespace synergy::license;
using namespace synergy::gui;
const char *const kProProductName = "Synergy 1 Pro"; const char *const kProProductName = "Synergy 1 Pro";
@ -82,7 +85,7 @@ void SettingsDialog::accept() {
appConfig().setAutoHide(m_pCheckBoxAutoHide->isChecked()); appConfig().setAutoHide(m_pCheckBoxAutoHide->isChecked());
appConfig().setPreventSleep(m_pCheckBoxPreventSleep->isChecked()); appConfig().setPreventSleep(m_pCheckBoxPreventSleep->isChecked());
appConfig().setTlsCertPath(m_pLineEditCertificatePath->text()); appConfig().setTlsCertPath(m_pLineEditCertificatePath->text());
appConfig().setTlsKeyLength(m_pComboBoxKeyLength->currentText()); appConfig().setTlsKeyLength(m_pComboBoxKeyLength->currentText().toInt());
appConfig().setTlsEnabled(m_pCheckBoxEnableCrypto->isChecked()); appConfig().setTlsEnabled(m_pCheckBoxEnableCrypto->isChecked());
appConfig().setLanguageSync(m_pCheckBoxLanguageSync->isChecked()); appConfig().setLanguageSync(m_pCheckBoxLanguageSync->isChecked());
appConfig().setInvertScrollDirection(m_pCheckBoxScrollDirection->isChecked()); appConfig().setInvertScrollDirection(m_pCheckBoxScrollDirection->isChecked());
@ -136,8 +139,9 @@ void SettingsDialog::updateTlsControls() {
if (QFile(appConfig().tlsCertPath()).exists()) { if (QFile(appConfig().tlsCertPath()).exists()) {
updateKeyLengthOnFile(appConfig().tlsCertPath()); updateKeyLengthOnFile(appConfig().tlsCertPath());
} else { } else {
const auto keyLengthText = QString::number(appConfig().tlsKeyLength());
m_pComboBoxKeyLength->setCurrentIndex( m_pComboBoxKeyLength->setCurrentIndex(
m_pComboBoxKeyLength->findText(appConfig().tlsKeyLength())); m_pComboBoxKeyLength->findText(keyLengthText));
} }
m_pCheckBoxEnableCrypto->setChecked(m_appConfig.tlsEnabled()); m_pCheckBoxEnableCrypto->setChecked(m_appConfig.tlsEnabled());
@ -189,7 +193,7 @@ void SettingsDialog::on_m_pButtonBrowseLog_clicked() {
void SettingsDialog::on_m_pCheckBoxEnableCrypto_clicked(bool) { void SettingsDialog::on_m_pCheckBoxEnableCrypto_clicked(bool) {
updateTlsControlsEnabled(); updateTlsControlsEnabled();
if (kLicensingEnabled && !m_tlsUtility.isAvailable()) { if (kEnableActivation && !m_tlsUtility.isAvailable()) {
auto edition = m_license.productEdition(); auto edition = m_license.productEdition();
if (edition == Edition::kBasic) { if (edition == Edition::kBasic) {
UpgradeDialog upgradeDialog(this); UpgradeDialog upgradeDialog(this);
@ -215,9 +219,11 @@ void SettingsDialog::on_m_pPushButtonBrowseCert_clicked() {
if (!fileName.isEmpty()) { if (!fileName.isEmpty()) {
m_pLineEditCertificatePath->setText(fileName); 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); updateKeyLengthOnFile(fileName);
} else {
qDebug("no tls certificate file at: %s", qUtf8Printable(fileName));
} }
} }
updateTlsRegenerateButton(); updateTlsRegenerateButton();
@ -228,9 +234,8 @@ void SettingsDialog::on_m_pComboBoxKeyLength_currentIndexChanged(int index) {
} }
void SettingsDialog::updateTlsRegenerateButton() { void SettingsDialog::updateTlsRegenerateButton() {
// Disable the Regenerate cert button if the key length is different to saved const auto keyLength = m_pComboBoxKeyLength->currentText().toInt();
auto keyChanged = auto keyChanged = appConfig().tlsKeyLength() != keyLength;
appConfig().tlsKeyLength() != m_pComboBoxKeyLength->currentText();
auto pathChanged = auto pathChanged =
appConfig().tlsCertPath() != m_pLineEditCertificatePath->text(); appConfig().tlsCertPath() != m_pLineEditCertificatePath->text();
// NOR the above bools, if any have changed regen should be disabled as it // 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() { 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) { void SettingsDialog::updateKeyLengthOnFile(const QString &path) {
TlsCertificate ssl; TlsCertificate ssl;
if (!QFile(path).exists()) {
qFatal("tls certificate file not found: %s", qUtf8Printable(path));
}
auto length = ssl.getCertKeyLength(path); auto length = ssl.getCertKeyLength(path);
auto index = m_pComboBoxKeyLength->findText(length); auto index = m_pComboBoxKeyLength->findText(QString::number(length));
m_pComboBoxKeyLength->setCurrentIndex(index); m_pComboBoxKeyLength->setCurrentIndex(index);
// Also update what is in the appconfig to match the file itself
appConfig().setTlsKeyLength(length); appConfig().setTlsKeyLength(length);
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -18,7 +18,9 @@
#include "ServerStateLabel.h" #include "ServerStateLabel.h"
#include "ServerMessage.h" #include "gui/core/ServerMessage.h"
using namespace synergy::gui;
namespace synergy_widgets { namespace synergy_widgets {
@ -38,7 +40,14 @@ void ServerStateLabel::updateServerState(const QString &line) {
if (m_clients.isEmpty()) { if (m_clients.isEmpty()) {
setText(tr("No clients connected")); setText(tr("No clients connected"));
} else { } 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 sources *.cpp)
file(GLOB_RECURSE headers *.h) 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) if(ADD_HEADERS_TO_SOURCES)
list(APPEND sources ${headers}) list(APPEND sources ${headers})
endif() 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 * 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 * This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#if !defined(OSXHELPERS__H) #pragma once
#define OSXHELPERS__H
#include <QString> #include <QString>
@ -28,5 +26,3 @@ bool isOSXDevelopmentBuild();
bool showOSXNotification(const QString &title, const QString &body); bool showOSXNotification(const QString &title, const QString &body);
bool isOSXInterfaceStyleDark(); bool isOSXInterfaceStyleDark();
IconsTheme getOSXIconsTheme(); IconsTheme getOSXIconsTheme();
#endif

View file

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

View file

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

View file

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

View file

@ -32,7 +32,7 @@ class ConfigScopes : public QObject, public IConfigScopes {
public: public:
explicit ConfigScopes(); explicit ConfigScopes();
virtual ~ConfigScopes() = default; ~ConfigScopes() override = default;
void signalReady() override; void signalReady() override;
void save() 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 * 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 * This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -19,23 +19,36 @@
#include <QString> #include <QString>
namespace synergy::gui {
const auto kAppName = "Synergy"; const auto kAppName = "Synergy";
// TODO: change to `com.symless`. we'll need to gracefully import old settings, // TODO: change to `com.symless`. we'll need to gracefully import old settings,
// since qt uses this on some platforms when saving settings. // since qt uses this on some platforms when saving settings.
const auto kAppDomain = "https://symless.com"; const auto kAppDomain = "https://symless.com";
const int kDebugLogLevel = 1;
#ifdef SYNERGY_PRODUCT_NAME #ifdef SYNERGY_PRODUCT_NAME
const QString kProductName = SYNERGY_PRODUCT_NAME; const QString kProductName = SYNERGY_PRODUCT_NAME;
#else #else
const QString kProductName; const QString kProductName;
#endif #endif
#ifdef SYNERGY_ENABLE_LICENSING #ifdef SYNERGY_LICENSED_PRODUCT
const bool kLicensingEnabled = true; const bool kLicensedProduct = true;
#else #else
const bool kLicensingEnabled = false; const bool kLicensedProduct = false;
#endif // SYNERGY_ENABLE_LICENSING #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 kLinkBuy = R"(<a href="%1" style="color: %2">Buy now</a>)";
const auto kLinkRenew = R"(<a href="%1" style="color: %2">Renew 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 kUrlSourceQuery = "source=gui";
const auto kUrlWebsite = "https://symless.com"; 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 = const auto kUrlGnomeTrayFix =
"https://extensions.gnome.org/extension/2890/tray-icons-reloaded/"; "https://extensions.gnome.org/extension/2890/tray-icons-reloaded/";
const auto kUrlProduct = QString("%1/synergy").arg(kUrlWebsite); 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 kUrlHelp = QString("%1/help?%2").arg(kUrlProduct, kUrlSourceQuery);
const auto kUrlDownload = const auto kUrlDownload =
QString("%1/download?%2").arg(kUrlProduct, kUrlSourceQuery); 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 * synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2021 Symless Ltd. * Copyright (C) 2021 Symless Ltd.
* Copyright (C) 2008 Volker Lanz (vl@fidra.de)
* *
* This package is free software; you can redistribute it and/or * This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -18,12 +17,14 @@
#include "ClientConnection.h" #include "ClientConnection.h"
#include "MainWindow.h"
#include <QHostAddress> #include <QHostAddress>
#include <QMessageBox> #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) { void ClientConnection::update(const QString &line) {
if (m_checkConnection && checkMainWindow()) { if (m_checkConnection && checkMainWindow()) {
@ -59,13 +60,13 @@ QString ClientConnection::getMessage(const QString &line) const {
message = message =
QObject::tr("Connection failed.\nYou can’t name 2 computers the same."); QObject::tr("Connection failed.\nYou can’t name 2 computers the same.");
} else { } else {
QHostAddress address(m_parent.appConfig().serverHostname()); QHostAddress address(m_appConfig.serverHostname());
if (address.isNull()) { if (address.isNull()) {
message = message =
QObject::tr( QObject::tr(
"We can’t connect to the server \"%1\" try to connect using the " "We can’t connect to the server \"%1\" try to connect using the "
"server IP address and check your firewall settings.") "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) { void ClientConnection::setCheckConnection(bool checkConnection) {
m_checkConnection = checkConnection; m_checkConnection = checkConnection;
} }
} // namespace synergy::gui

View file

@ -1,7 +1,6 @@
/* /*
* synergy -- mouse and keyboard sharing utility * synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2021 Symless Ltd. * Copyright (C) 2021 Symless Ltd.
* Copyright (C) 2008 Volker Lanz (vl@fidra.de)
* *
* This package is free software; you can redistribute it and/or * This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef CLIENTCONNECTION_H #pragma once
#define CLIENTCONNECTION_H
#include "gui/config/AppConfig.h"
#include <QString> #include <QString>
#include <QWidget>
class MainWindow; namespace synergy::gui {
class ClientConnection { class ClientConnection {
MainWindow &m_parent;
bool m_checkConnection = false;
public: public:
explicit ClientConnection(MainWindow &parent); explicit ClientConnection(QWidget &parent, AppConfig &appConfig);
void update(const QString &line); void update(const QString &line);
void setCheckConnection(bool checkConnection); void setCheckConnection(bool checkConnection);
@ -36,6 +34,10 @@ private:
QString getMessage(const QString &line) const; QString getMessage(const QString &line) const;
bool checkMainWindow(); bool checkMainWindow();
void showMessage(const QString &message) const; 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()) { if (process.waitForFinished()) {
output = output = process.readAllStandardOutput().trimmed();
QString::fromLocal8Bit(process.readAllStandardOutput().trimmed());
error = process.readAllStandardError().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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "gui/CoreInterface.h" #include "gui/core/CoreTool.h"
#include "CommandProcess.h" #include "CommandProcess.h"
@ -32,28 +32,27 @@ static const char kSerialKeyFilename[] = "Synergy.subkey";
static const char kSerialKeyFilename[] = ".synergy.subkey"; static const char kSerialKeyFilename[] = ".synergy.subkey";
#endif #endif
QString CoreInterface::getProfileDir() const { QString CoreTool::getProfileDir() const {
QStringList args("--get-profile-dir"); QStringList args("--get-profile-dir");
return QDir::cleanPath(run(args)); return QDir::cleanPath(run(args));
} }
QString CoreInterface::getInstalledDir() const { QString CoreTool::getInstalledDir() const {
QStringList args("--get-installed-dir"); QStringList args("--get-installed-dir");
return QDir::cleanPath(run(args)); return QDir::cleanPath(run(args));
} }
QString CoreInterface::getArch() const { QString CoreTool::getArch() const {
QStringList args("--get-arch"); QStringList args("--get-arch");
return run(args); return run(args);
} }
QString CoreInterface::getSerialKeyFilePath() const { QString CoreTool::getSerialKeyFilePath() const {
auto filename = getProfileDir() + QDir::separator() + kSerialKeyFilename; auto filename = getProfileDir() + QDir::separator() + kSerialKeyFilename;
return QDir::cleanPath(filename); return QDir::cleanPath(filename);
} }
QString QString CoreTool::run(const QStringList &args, const QString &input) const {
CoreInterface::run(const QStringList &args, const QString &input) const {
QString program(QCoreApplication::applicationDirPath() + "/" + kCoreBinary); QString program(QCoreApplication::applicationDirPath() + "/" + kCoreBinary);
CommandProcess commandProcess(program, args, input); CommandProcess commandProcess(program, args, input);

View file

@ -19,7 +19,7 @@
#include <QString> #include <QString>
class CoreInterface { class CoreTool {
public: public:
QString getProfileDir() const; QString getProfileDir() const;
QString getInstalledDir() 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 * synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2021 Symless Ltd. * Copyright (C) 2021 Symless Ltd.
* Copyright (C) 2008 Volker Lanz (vl@fidra.de)
* *
* This package is free software; you can redistribute it and/or * This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef SERVERCONNECTION_H #pragma once
#define SERVERCONNECTION_H
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
class MainWindow; #include "gui/config/AppConfig.h"
#include "gui/config/IServerConfig.h"
class ServerConnection { namespace synergy::gui {
MainWindow &m_parent;
QStringList m_ignoredClients; class ServerConnection : public QObject {
Q_OBJECT
using IServerConfig = synergy::gui::IServerConfig;
public: public:
explicit ServerConnection(MainWindow &parent); explicit ServerConnection(
QWidget &parent, AppConfig &appConfig, IServerConfig &serverConfig);
void update(const QString &line); void update(const QString &line);
signals:
void configureClient(const QString &clientName);
private: private:
void addClient(const QString &clientName); void addClient(const QString &clientName);
void configureClient(const QString &clientName);
bool checkMainWindow(); 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 * synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2021 Symless Ltd. * Copyright (C) 2021 Symless Ltd.
* Copyright (C) 2008 Volker Lanz (vl@fidra.de)
* *
* This package is free software; you can redistribute it and/or * This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * 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 * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "ServerMessage.h" #include "ServerMessage.h"
namespace synergy::gui {
ServerMessage::ServerMessage(const QString &message) ServerMessage::ServerMessage(const QString &message)
: m_message(message), : m_message(message),
m_clienName(parseClientName(message)) {} m_clienName(parseClientName(message)) {}
@ -50,3 +52,5 @@ QString ServerMessage::parseClientName(const QString &line) const {
return clientName; return clientName;
} }
} // namespace synergy::gui

View file

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

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

View file

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

View file

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

View file

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

View file

@ -17,75 +17,100 @@
#include "messages.h" #include "messages.h"
#include "Logger.h"
#include "constants.h" #include "constants.h"
#include "styles.h" #include "styles.h"
#include <QAction>
#include <QDateTime> #include <QDateTime>
#include <QDir>
#include <QMessageBox> #include <QMessageBox>
#include <QTime> #include <QTime>
#include <memory>
namespace synergy::gui::messages { 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( void messageHandler(
QtMsgType type, const QMessageLogContext &context, const QString &message) { QtMsgType type, const QMessageLogContext &context, const QString &message) {
auto datetime = QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm:ss"); Logger::instance().handleMessage(type, context, message);
const auto filename = QString(context.file).split("/").last();
auto function = context.function ? context.function : "";
QString typeString; if (type == QtFatalMsg || type == QtCriticalMsg) {
auto out = stdout; showErrorDialog(message, context, type);
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;
} }
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) { if (type == QtFatalMsg) {
auto contextString = // developers: if you hit this line in your debugger, traverse the stack
QString("%1:%2\n%3").arg(filename).arg(context.line).arg(function); // 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.
QMessageBox::critical( //
nullptr, "Fatal error", // hint: if you don't want to crash, but still want to show an error
QString("<p>Sorry, a fatal error has occurred " // message, use `qCritical()` instead of `qFatal()`. you should use
"and the application must now exit.</p>" // fatal errors when the app is in an unrecoverable state; i.e. it cannot
"<p>Please " // function correctly in it's current state and must be restarted.
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.
abort(); abort();
} }
} }
@ -143,16 +168,16 @@ void showDevThanks(QWidget *parent, const QString &productName) {
QMessageBox::information( QMessageBox::information(
parent, "Thank you!", parent, "Thank you!",
QString( QString("<p>Thanks for using %1.</p>"
"<p>Thanks for using %1.</p>" "<p>If you enjoy using this app, you can support the <br />"
"<p>If you enjoy using this app, you can support the developers by " "developers by "
R"(<a href="%1" style="color: %2")>purchasing a license</a>)" R"(<a href="%2" style="color: %3")>purchasing a license</a>)"
" or " " or "
R"(<a href="%3" style="color: %4")>contributing code</a>.)" R"(<a href="%4" style="color: %5")>contributing code</a>.)"
"</p>" "</p>"
"<p>This message will only appear once.</p>") "<p>This message will only appear once.</p>")
.arg( .arg(
productName, kUrlPurchase, kColorSecondary, kUrlContribute, productName, kUrlPurchase, kColorSecondary, kUrlGitHub,
kColorSecondary)); 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> #include <QString>
namespace synergy::gui {
const auto kColorWhite = "#ffffff"; const auto kColorWhite = "#ffffff";
const auto kColorPrimary = "#ff7c00"; const auto kColorPrimary = "#ff7c00";
const auto kColorSecondary = "#4285f4"; const auto kColorSecondary = "#4285f4";
@ -47,3 +49,5 @@ const auto kStyleNoticeLabel = //
QString("padding: 3px 5px; border-radius: 3px;" QString("padding: 3px 5px; border-radius: 3px;"
"background-color: %1; color: %2") "background-color: %1; color: %2")
.arg(kColorNotice, kColorWhite); .arg(kColorNotice, kColorWhite);
} // namespace synergy::gui

View file

@ -22,13 +22,10 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QDir> #include <QDir>
#include <QProcess> #include <QProcess>
#include <optional>
// RSA Bit length (e.g. 1024/2048/4096)
static const char *const kCertificateKeyLength = "rsa:"; static const char *const kCertificateKeyLength = "rsa:";
// fingerprint hashing algorithm
static const char *const kCertificateHashAlgorithm = "-sha256"; static const char *const kCertificateHashAlgorithm = "-sha256";
static const char *const kCertificateLifetime = "365"; static const char *const kCertificateLifetime = "365";
static const char *const kCertificateSubjectInfo = "/CN=Synergy"; static const char *const kCertificateSubjectInfo = "/CN=Synergy";
static const char *const kCertificateFilename = "Synergy.pem"; static const char *const kCertificateFilename = "Synergy.pem";
@ -42,9 +39,10 @@ static const char *const kConfigFile = "synergy.conf";
static const char *const kUnixOpenSslCommand = "openssl"; static const char *const kUnixOpenSslCommand = "openssl";
#endif #endif
namespace synergy::gui {
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
namespace synergy::gui {
QString openSslWindowsDir() { QString openSslWindowsDir() {
auto appDir = QDir(QCoreApplication::applicationDirPath()); auto appDir = QDir(QCoreApplication::applicationDirPath());
@ -83,16 +81,16 @@ QString openSslWindowsBinary() {
return path; return path;
} }
#endif
} // namespace synergy::gui } // namespace synergy::gui
using namespace synergy::gui; using namespace synergy::gui;
#endif
TlsCertificate::TlsCertificate(QObject *parent) : QObject(parent) { TlsCertificate::TlsCertificate(QObject *parent) : QObject(parent) {
m_ProfileDir = m_CoreInterface.getProfileDir(); m_profileDir = m_coreTool.getProfileDir();
if (m_ProfileDir.isEmpty()) { if (m_profileDir.isEmpty()) {
emit error(tr("Failed to get profile directory.")); qCritical("empty profile directory result");
} }
} }
@ -108,92 +106,118 @@ bool TlsCertificate::runTool(const QStringList &args) {
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
auto openSslDir = QDir(openSslWindowsDir()); auto openSslDir = QDir(openSslWindowsDir());
auto config = QDir::cleanPath(openSslDir.filePath(kConfigFile)); 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); environment << QString("OPENSSL_CONF=%1").arg(config);
#endif #endif
qDebug(
"running: %s %s", qUtf8Printable(program),
qUtf8Printable(args.join(" ")));
QProcess process; QProcess process;
for (const auto &envVar : environment) {
qDebug("setting env var %s", qUtf8Printable(envVar));
}
process.setEnvironment(environment); process.setEnvironment(environment);
process.start(program, args); process.start(program, args);
bool success = process.waitForStarted(); bool success = process.waitForStarted();
QString standardError; QString stderrOutput;
if (success && process.waitForFinished()) { if (success && process.waitForFinished()) {
m_ToolOutput = process.readAllStandardOutput().trimmed(); m_toolStdout = process.readAllStandardOutput().trimmed();
standardError = process.readAllStandardError().trimmed(); stderrOutput = process.readAllStandardError().trimmed();
} }
if (int code = process.exitCode(); !success || code != 0) { if (int code = process.exitCode(); !success || code != 0) {
emit error(QString("SSL tool failed: %1\n\nCode: %2\nError: %3") qDebug(
.arg(program) "openssl failed with code %d: %s", code, qUtf8Printable(stderrOutput));
.arg(process.exitCode())
.arg(standardError.isEmpty() ? "Unknown" : standardError)); qCritical(
"failed to generate TLS certificate:\n\n%s",
qUtf8Printable(stderrOutput));
return false; return false;
} }
return true; return true;
} }
void TlsCertificate::generateCertificate( bool TlsCertificate::generateCertificate(const QString &path, int keyLength) {
const QString &path, const QString &keyLength, bool forceGen) {
QString sslDirPath = 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") QString defaultPath = QString("%1%2%3")
.arg(sslDirPath) .arg(sslDirPath)
.arg(QDir::separator()) .arg(QDir::separator())
.arg(kCertificateFilename); .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) { qDebug("generating tls certificate: %s", qUtf8Printable(pathToUse));
QStringList arguments;
// self signed certificate QStringList arguments;
arguments.append("req");
arguments.append("-x509");
arguments.append("-nodes");
// valide duration // self signed certificate
arguments.append("-days"); arguments.append("req");
arguments.append(kCertificateLifetime); arguments.append("-x509");
arguments.append("-nodes");
// subject information // valide duration
arguments.append("-subj"); arguments.append("-days");
arguments.append(kCertificateLifetime);
QString subInfo(kCertificateSubjectInfo); // subject information
arguments.append(subInfo); arguments.append("-subj");
// private key QString subInfo(kCertificateSubjectInfo);
arguments.append("-newkey"); arguments.append(subInfo);
arguments.append(keySize);
if (QDir sslDir(sslDirPath); !sslDir.exists()) { // private key
sslDir.mkpath("."); arguments.append("-newkey");
} arguments.append(keySize);
// key output filename if (QDir sslDir(sslDirPath); !sslDir.exists()) {
arguments.append("-keyout"); sslDir.mkpath(".");
arguments.append(pathToUse);
// certificate output filename
arguments.append("-out");
arguments.append(pathToUse);
if (!runTool(arguments)) {
return;
}
generateFingerprint(pathToUse);
emit info(tr("SSL certificate generated."));
} }
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; QStringList arguments;
arguments.append("x509"); arguments.append("x509");
arguments.append("-fingerprint"); arguments.append("-fingerprint");
@ -203,23 +227,26 @@ void TlsCertificate::generateFingerprint(const QString &certificateFilename) {
arguments.append(certificateFilename); arguments.append(certificateFilename);
if (!runTool(arguments)) { if (!runTool(arguments)) {
return; qCritical("failed to generate tls fingerprint");
return false;
} }
// find the fingerprint from the tool output // find the fingerprint from the tool output
auto i = m_ToolOutput.indexOf("="); auto i = m_toolStdout.indexOf("=");
if (i != -1) { if (i != -1) {
i++; 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); TlsFingerprint::local().trust(fingerprint, false);
emit info(tr("SSL fingerprint generated.")); qDebug("tls fingerprint generated");
return true;
} else { } 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; QStringList arguments;
arguments.append("rsa"); arguments.append("rsa");
@ -229,17 +256,19 @@ QString TlsCertificate::getCertKeyLength(const QString &path) {
arguments.append("-noout"); arguments.append("-noout");
if (!runTool(arguments)) { if (!runTool(arguments)) {
return QString(); qFatal("failed to get key length from certificate");
return 0;
} }
const QString searchStart("Private-Key: ("); const QString searchStart("Private-Key: (");
const QString searchEnd(" bit"); const QString searchEnd(" bit");
// Get the line that contains the key length from the output // Get the line that contains the key length from the output
const auto indexStart = m_ToolOutput.indexOf(searchStart); const auto indexStart = m_toolStdout.indexOf(searchStart);
const auto indexEnd = m_ToolOutput.indexOf(searchEnd, indexStart); const auto indexEnd = m_toolStdout.indexOf(searchEnd, indexStart);
const auto start = indexStart + searchStart.length(); const auto start = indexStart + searchStart.length();
const auto end = indexEnd - (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 "TlsFingerprint.h"
#include "gui/CoreInterface.h" #include "gui/core/CoreTool.h"
#include <QDir> #include <QDir>
#include <QTextStream> #include <QTextStream>
@ -27,11 +27,10 @@ static const char kLocalFilename[] = "Local.txt";
static const char kTrustedServersFilename[] = "TrustedServers.txt"; static const char kTrustedServersFilename[] = "TrustedServers.txt";
static const char kTrustedClientsFilename[] = "TrustedClients.txt"; static const char kTrustedClientsFilename[] = "TrustedClients.txt";
TlsFingerprint::TlsFingerprint(const QString &filename) { TlsFingerprint::TlsFingerprint(const QString &filename)
m_Filename = filename; : m_Filename(filename) {}
}
void TlsFingerprint::trust(const QString &fingerprintText, bool append) { void TlsFingerprint::trust(const QString &fingerprintText, bool append) const {
TlsFingerprint::persistDirectory(); TlsFingerprint::persistDirectory();
QIODevice::OpenMode openMode; QIODevice::OpenMode openMode;
@ -59,7 +58,7 @@ bool TlsFingerprint::fileExists() const {
return file.exists(); return file.exists();
} }
bool TlsFingerprint::isTrusted(const QString &fingerprintText) { bool TlsFingerprint::isTrusted(const QString &fingerprintText) const {
QStringList list = readList(); QStringList list = readList();
foreach (QString trusted, list) { foreach (QString trusted, list) {
if (trusted == fingerprintText) { if (trusted == fingerprintText) {
@ -69,7 +68,7 @@ bool TlsFingerprint::isTrusted(const QString &fingerprintText) {
return false; return false;
} }
QStringList TlsFingerprint::readList(const int readTo) { QStringList TlsFingerprint::readList(const int readTo) const {
QStringList list; QStringList list;
QString dirName = TlsFingerprint::directoryPath(); QString dirName = TlsFingerprint::directoryPath();
@ -93,7 +92,7 @@ QStringList TlsFingerprint::readList(const int readTo) {
return list; return list;
} }
QString TlsFingerprint::readFirst() { QString TlsFingerprint::readFirst() const {
QStringList list = readList(1); QStringList list = readList(1);
return list.at(0); return list.at(0);
} }
@ -111,8 +110,8 @@ void TlsFingerprint::persistDirectory() {
} }
QString TlsFingerprint::directoryPath() { QString TlsFingerprint::directoryPath() {
CoreInterface coreInterface; CoreTool coreTool;
QString profileDir = coreInterface.getProfileDir(); QString profileDir = coreTool.getProfileDir();
return QString("%1/%2").arg(profileDir).arg(kDirName); return QString("%1/%2").arg(profileDir).arg(kDirName);
} }

View file

@ -21,15 +21,7 @@
class TlsFingerprint { class TlsFingerprint {
private: private:
TlsFingerprint(const QString &filename); explicit 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;
public: public:
static TlsFingerprint local(); static TlsFingerprint local();
@ -40,6 +32,13 @@ public:
static bool localFingerprintExists(); static bool localFingerprintExists();
static void persistDirectory(); 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: private:
QString m_Filename; QString m_Filename;
}; };

View file

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

View file

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

View file

@ -56,7 +56,11 @@ Screen::~Screen() {
disable(); disable();
} }
assert(!m_enabled); 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); assert(m_entered == m_isPrimary);
delete m_screen; delete m_screen;
LOG((CLOG_DEBUG "closed display")); LOG((CLOG_DEBUG "closed display"));
} }

View file

@ -15,14 +15,23 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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> #include <gtest/gtest.h>
TEST(CommandProcessTests, run_commandSucceeds_returnsOutput) { 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 QString command = "echo";
const QStringList arguments = {"Hello, World!"}; const QStringList arguments = {"Hello, World!"};
const QString input = ""; const QString input = "";
#endif // Q_OS_WIN
CommandProcess commandProcess(command, arguments, input); CommandProcess commandProcess(command, arguments, input);

View file

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

View file

@ -33,7 +33,7 @@ void TestEventQueue::initQuitTimeout(double timeout) {
void TestEventQueue::cleanupQuitTimeout() { void TestEventQueue::cleanupQuitTimeout() {
removeHandler(Event::kTimer, m_pQuitTimeoutTimer); removeHandler(Event::kTimer, m_pQuitTimeoutTimer);
delete m_pQuitTimeoutTimer; deleteTimer(m_pQuitTimeoutTimer);
m_pQuitTimeoutTimer = nullptr; 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/>. * 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"
#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/>. * 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 <gmock/gmock.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -25,7 +25,7 @@ using testing::StrEq;
class MockStream : public QDataStreamProxy { class MockStream : public QDataStreamProxy {
public: public:
MOCK_METHOD(qint64, writeRawData, (const char *, qint64), (override)); MOCK_METHOD(qint64, writeRawData, (const char *, int), (override));
}; };
TEST(QIpcClientTests, sendCommand_anyCommand_commandSent) { TEST(QIpcClientTests, sendCommand_anyCommand_commandSent) {
@ -38,5 +38,5 @@ TEST(QIpcClientTests, sendCommand_anyCommand_commandSent) {
EXPECT_CALL(*mockStream, writeRawData(StrEq("test"), 4)).Times(1); EXPECT_CALL(*mockStream, writeRawData(StrEq("test"), 4)).Times(1);
QIpcClient ipcClient(streamProvider); 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "gui/LicenseHandler.h" #include "gui/license/LicenseHandler.h"
#include <chrono> #include <chrono>
#include <gtest/gtest.h> #include <gtest/gtest.h>

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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 <chrono>
#include <gmock/gmock.h> #include <gmock/gmock.h>