Add reset settings menu action and env var (#7435)

* Add reset settings menu action and env var

* Delete profile and config dirs, reduce duplication with TLS dir and various other paths, fixed bug in new client dialog, move TLS arg to server

* Better filename

* Make method const

* Update ChangeLog

* Remove include

* Make param const

* Stop core process on reset

* Fixed: reset menu action causes config to be pulled from app/server config

* Center main window in middle of screen
This commit is contained in:
Nick Bolton 2024-08-11 00:58:24 +01:00 committed by GitHub
parent c4510bf093
commit 3e4cb4a1c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 559 additions and 228 deletions

View file

@ -21,6 +21,9 @@
# Enable verbose logging in the GUI (always off by default) # Enable verbose logging in the GUI (always off by default)
# SYNERGY_GUI_VERBOSE=true # SYNERGY_GUI_VERBOSE=true
# Reset all settings and delete all data on startup
# SYNERGY_RESET_ALL=true
# #
# Build # Build
# #

View file

@ -77,6 +77,7 @@ Enhancements:
- #7431 Improve handling of Qt-related warnings and errors - #7431 Improve handling of Qt-related warnings and errors
- #7432 Only show close to tray reminder when not quitting the app - #7432 Only show close to tray reminder when not quitting the app
- #7434 Show dark logo in dark mode and improve .env loader - #7434 Show dark logo in dark mode and improve .env loader
- #7435 Add reset settings menu action and env var
# 1.14.6 # 1.14.6

View file

@ -21,12 +21,14 @@
#include "AboutDialog.h" #include "AboutDialog.h"
#include "ActivationDialog.h" #include "ActivationDialog.h"
#include "ServerConfigDialog.h" #include "ServerConfigDialog.h"
#include "common/constants.h"
#include "gui/Logger.h" #include "gui/Logger.h"
#include "gui/TrayIcon.h" #include "gui/TrayIcon.h"
#include "gui/VersionChecker.h" #include "gui/VersionChecker.h"
#include "gui/config/ConfigScopes.h" #include "gui/config/ConfigScopes.h"
#include "gui/constants.h" #include "gui/constants.h"
#include "gui/core/CoreProcess.h" #include "gui/core/CoreProcess.h"
#include "gui/diagnostic.h"
#include "gui/dialogs/SettingsDialog.h" #include "gui/dialogs/SettingsDialog.h"
#include "gui/license/LicenseHandler.h" #include "gui/license/LicenseHandler.h"
#include "gui/license/license_notices.h" #include "gui/license/license_notices.h"
@ -76,13 +78,15 @@ MainWindow::MainWindow(ConfigScopes &configScopes, AppConfig &appConfig)
: m_ConfigScopes(configScopes), : m_ConfigScopes(configScopes),
m_AppConfig(appConfig), m_AppConfig(appConfig),
m_ServerConfig(appConfig, *this), m_ServerConfig(appConfig, *this),
m_CoreProcess(appConfig, m_ServerConfig), m_CoreProcess(appConfig, m_ServerConfig, m_LicenseHandler.license()),
m_ServerConnection(this, appConfig, m_ServerConfig), m_ServerConnection(
this, appConfig, m_ServerConfig, m_ServerConfigDialogState),
m_ClientConnection(this, appConfig), m_ClientConnection(this, appConfig),
m_TlsUtility(appConfig, m_LicenseHandler.license()), m_TlsUtility(appConfig, m_LicenseHandler.license()),
m_WindowSaveTimer(this) { m_WindowSaveTimer(this) {
setupUi(this); setupUi(this);
createMenuBar();
setupControls(); setupControls();
connectSlots(); connectSlots();
@ -90,15 +94,7 @@ MainWindow::MainWindow(ConfigScopes &configScopes, AppConfig &appConfig)
emit created(); emit created();
} }
MainWindow::~MainWindow() { MainWindow::~MainWindow() { m_CoreProcess.cleanup(); }
try {
saveWindow();
} catch (const std::exception &e) {
qFatal("failed to save window on main window close: %s", e.what());
}
m_CoreProcess.cleanup();
}
void MainWindow::restoreWindow() { void MainWindow::restoreWindow() {
@ -112,6 +108,11 @@ void MainWindow::restoreWindow() {
if (windowPosition.has_value()) { if (windowPosition.has_value()) {
qDebug("restoring main window position"); qDebug("restoring main window position");
move(windowPosition.value()); move(windowPosition.value());
} else {
// center main window in middle of screen
const auto screen = QGuiApplication::primaryScreen();
QRect screenGeometry = screen->geometry();
move(screenGeometry.center() - rect().center());
} }
// give the window chance to restore its size and position before the window // give the window chance to restore its size and position before the window
@ -144,7 +145,6 @@ void MainWindow::setupControls() {
m_pActionActivate->setVisible(false); m_pActionActivate->setVisible(false);
} }
createMenuBar();
secureSocket(false); secureSocket(false);
updateLocalFingerprint(); updateLocalFingerprint();
@ -273,7 +273,11 @@ void MainWindow::connectSlots() {
[this]() { showAndActivate(); }); [this]() { showAndActivate(); });
} }
void MainWindow::onAppAboutToQuit() { m_ConfigScopes.save(); } void MainWindow::onAppAboutToQuit() {
if (m_SaveOnExit) {
m_ConfigScopes.save();
}
}
void MainWindow::onCreated() { void MainWindow::onCreated() {
@ -398,6 +402,19 @@ void MainWindow::on_m_pActionTestCriticalError_triggered() const {
qCritical("test critical error"); qCritical("test critical error");
} }
void MainWindow::on_m_pActionClearSettings_triggered() {
if (!messages::showClearSettings(this)) {
qDebug("clear settings cancelled");
return;
}
m_CoreProcess.stop();
m_Quitting = true;
m_SaveOnExit = false;
diagnostic::clearSettings(m_ConfigScopes, true);
}
bool MainWindow::on_m_pActionSave_triggered() { bool MainWindow::on_m_pActionSave_triggered() {
QString fileName = QString fileName =
QFileDialog::getSaveFileName(this, QString("Save configuration as...")); QFileDialog::getSaveFileName(this, QString("Save configuration as..."));
@ -505,10 +522,12 @@ void MainWindow::on_m_pButtonConnectToClient_clicked() {
void MainWindow::onWindowSaveTimerTimeout() { saveWindow(); } void MainWindow::onWindowSaveTimerTimeout() { saveWindow(); }
void MainWindow::onServerConnectionConfigureClient(const QString &clientName) { void MainWindow::onServerConnectionConfigureClient(const QString &clientName) {
m_ServerConfigDialogState.setVisible(true);
ServerConfigDialog dialog(this, m_ServerConfig, m_AppConfig); ServerConfigDialog dialog(this, m_ServerConfig, m_AppConfig);
if (dialog.addClient(clientName) && dialog.exec() == QDialog::Accepted) { if (dialog.addClient(clientName) && dialog.exec() == QDialog::Accepted) {
m_CoreProcess.restart(); m_CoreProcess.restart();
} }
m_ServerConfigDialogState.setVisible(false);
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@ -592,22 +611,21 @@ void MainWindow::createMenuBar() {
m_pMenuFile->addAction(m_pActionSave); m_pMenuFile->addAction(m_pActionSave);
m_pMenuFile->addSeparator(); m_pMenuFile->addSeparator();
m_pMenuFile->addAction(m_pActionQuit); m_pMenuFile->addAction(m_pActionQuit);
m_pMenuEdit->addAction(m_pActionSettings); m_pMenuEdit->addAction(m_pActionSettings);
m_pMenuWindow->addAction(m_pActionMinimize); m_pMenuWindow->addAction(m_pActionMinimize);
m_pMenuWindow->addAction(m_pActionRestore); m_pMenuWindow->addAction(m_pActionRestore);
m_pMenuHelp->addAction(m_pActionAbout); m_pMenuHelp->addAction(m_pActionAbout);
m_pMenuHelp->addAction(m_pActionHelp); m_pMenuHelp->addAction(m_pActionHelp);
m_pMenuFile->addSeparator();
m_pMenuHelp->addAction(m_pActionClearSettings);
#ifndef NDEBUG
// always enable test menu in debug mode.
const auto enableTestMenu = true;
#else
// only enable test menu in release build if env var is true.
const auto enableTestMenu = const auto enableTestMenu =
strToTrue(qEnvironmentVariable("SYNERGY_TEST_MENU")); strToTrue(qEnvironmentVariable("SYNERGY_TEST_MENU"));
#endif
if (enableTestMenu) { if (enableTestMenu || kDebugBuild) {
auto testMenu = new QMenu("Test", m_pMenuBar); auto testMenu = new QMenu("Test", m_pMenuBar);
m_pMenuBar->addMenu(testMenu); m_pMenuBar->addMenu(testMenu);
testMenu->addAction(m_pActionTestFatalError); testMenu->addAction(m_pActionTestFatalError);

View file

@ -32,6 +32,7 @@
#include "gui/VersionChecker.h" #include "gui/VersionChecker.h"
#include "gui/config/AppConfig.h" #include "gui/config/AppConfig.h"
#include "gui/config/ConfigScopes.h" #include "gui/config/ConfigScopes.h"
#include "gui/config/ServerConfigDialogState.h"
#include "gui/core/ClientConnection.h" #include "gui/core/ClientConnection.h"
#include "gui/core/CoreProcess.h" #include "gui/core/CoreProcess.h"
#include "gui/core/ServerConnection.h" #include "gui/core/ServerConnection.h"
@ -130,6 +131,7 @@ private slots:
void on_m_pActionStopCore_triggered(); void on_m_pActionStopCore_triggered();
void on_m_pActionTestFatalError_triggered() const; void on_m_pActionTestFatalError_triggered() const;
void on_m_pActionTestCriticalError_triggered() const; void on_m_pActionTestCriticalError_triggered() const;
void on_m_pActionClearSettings_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_pLineEditHostname_textChanged(const QString &text);
@ -194,6 +196,8 @@ private:
bool m_SaveWindow = false; bool m_SaveWindow = false;
LicenseHandler m_LicenseHandler; LicenseHandler m_LicenseHandler;
bool m_Quitting = false; bool m_Quitting = false;
synergy::gui::config::ServerConfigDialogState m_ServerConfigDialogState;
bool m_SaveOnExit = true;
synergy::gui::ConfigScopes &m_ConfigScopes; synergy::gui::ConfigScopes &m_ConfigScopes;
AppConfig &m_AppConfig; AppConfig &m_AppConfig;

View file

@ -667,6 +667,14 @@
<enum>QAction::TextHeuristicRole</enum> <enum>QAction::TextHeuristicRole</enum>
</property> </property>
</action> </action>
<action name="m_pActionClearSettings">
<property name="text">
<string>Clear settings</string>
</property>
<property name="menuRole">
<enum>QAction::TextHeuristicRole</enum>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View file

@ -25,8 +25,10 @@
#include "gui/Logger.h" #include "gui/Logger.h"
#include "gui/config/AppConfig.h" #include "gui/config/AppConfig.h"
#include "gui/config/ConfigScopes.h" #include "gui/config/ConfigScopes.h"
#include "gui/diagnostic.h"
#include "gui/dotenv.h" #include "gui/dotenv.h"
#include "gui/messages.h" #include "gui/messages.h"
#include "gui/string_utils.h"
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
@ -34,6 +36,7 @@
#include <QObject> #include <QObject>
#include <QtCore> #include <QtCore>
#include <QtGui> #include <QtGui>
#include <qglobal.h>
#if defined(Q_OS_MAC) #if defined(Q_OS_MAC)
#include <Carbon/Carbon.h> #include <Carbon/Carbon.h>
@ -53,6 +56,11 @@ QString getSystemSettingPath();
bool checkMacAssistiveDevices(); bool checkMacAssistiveDevices();
#endif #endif
bool hasArg(const QString &arg, const QStringList &args) {
return std::ranges::any_of(
args, [&arg](const QString &a) { return a == arg; });
}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
#if defined(Q_OS_MAC) #if defined(Q_OS_MAC)
@ -96,6 +104,15 @@ int main(int argc, char *argv[]) {
qRegisterMetaType<Edition>("Edition"); qRegisterMetaType<Edition>("Edition");
ConfigScopes configScopes; ConfigScopes configScopes;
// --no-reset
QStringList arguments = QCoreApplication::arguments();
const auto noReset = hasArg("--no-reset", arguments);
const auto resetEnvVar = strToTrue(qEnvironmentVariable("SYNERGY_RESET_ALL"));
if (resetEnvVar && !noReset) {
diagnostic::clearSettings(configScopes, false);
}
AppConfig appConfig(configScopes); AppConfig appConfig(configScopes);
QObject::connect( QObject::connect(

View file

@ -30,3 +30,9 @@ const auto kVersionGitSha = GIT_SHA_SHORT;
#else #else
const auto kVersionGitSha = ""; const auto kVersionGitSha = "";
#endif #endif
#ifndef NDEBUG
const auto kDebugBuild = true;
#else
const auto kDebugBuild = false;
#endif

View file

@ -100,7 +100,8 @@ 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()),
m_TlsCertPath(deps->defaultTlsCertPath()) {
determineScope(); determineScope();
recall(); recall();
} }
@ -314,13 +315,6 @@ void AppConfig::recallElevateMode() {
m_ElevateMode = static_cast<ElevateMode>(elevateMode.toInt()); m_ElevateMode = static_cast<ElevateMode>(elevateMode.toInt());
} }
QString AppConfig::defaultTlsCertPath() const {
QDir path(m_pDeps->profileDir());
path = path.filePath("SSL");
path = path.filePath("Synergy.pem");
return path.absolutePath();
}
QString AppConfig::settingName(Setting name) { QString AppConfig::settingName(Setting name) {
auto index = static_cast<int>(name); auto index = static_cast<int>(name);
return m_SettingsName[index]; return m_SettingsName[index];

View file

@ -21,7 +21,7 @@
#include "ElevateMode.h" #include "ElevateMode.h"
#include "IAppConfig.h" #include "IAppConfig.h"
#include "IConfigScopes.h" #include "IConfigScopes.h"
#include "gui/core/CoreTool.h" #include "gui/paths.h"
#include <QDir> #include <QDir>
#include <QHostInfo> #include <QHostInfo>
@ -116,11 +116,10 @@ private:
public: public:
struct Deps { struct Deps {
virtual ~Deps() = default; virtual ~Deps() = default;
virtual QString profileDir() const { return m_coreTool.getProfileDir(); } virtual QString defaultTlsCertPath() const {
return synergy::gui::paths::defaultTlsCertPath();
}
virtual QString hostname() const { return QHostInfo::localHostName(); } virtual QString hostname() const { return QHostInfo::localHostName(); }
private:
[[no_unique_address]] CoreTool m_coreTool;
}; };
explicit AppConfig( explicit AppConfig(
@ -293,9 +292,16 @@ private:
*/ */
QString defaultTlsCertPath() const; QString defaultTlsCertPath() const;
synergy::gui::IConfigScopes &m_Scopes; static const char m_CoreServerName[];
std::shared_ptr<Deps> m_pDeps; static const char m_CoreClientName[];
QString m_ScreenName; static const char m_LogDir[];
/// @brief Contains the string values of the settings names that will be saved
static const char *const m_SettingsName[];
/// @brief Core config filename (not the Qt settings filename)
static const char m_ConfigFilename[];
int m_Port = 24800; int m_Port = 24800;
QString m_Interface = ""; QString m_Interface = "";
int m_LogLevel = 0; int m_LogLevel = 0;
@ -324,7 +330,6 @@ private:
bool m_EnableService = bool m_EnableService =
synergy::gui::kDefaultProcessMode == ProcessMode::kService; synergy::gui::kDefaultProcessMode == ProcessMode::kService;
bool m_CloseToTray = true; bool m_CloseToTray = true;
QString m_TlsCertPath = defaultTlsCertPath();
int m_TlsKeyLength = synergy::gui::kDefaultTlsKeyLength; 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;
@ -338,15 +343,10 @@ private:
*/ */
bool m_TlsChanged = false; bool m_TlsChanged = false;
static const char m_CoreServerName[]; synergy::gui::IConfigScopes &m_Scopes;
static const char m_CoreClientName[]; std::shared_ptr<Deps> m_pDeps;
static const char m_LogDir[]; QString m_ScreenName;
QString m_TlsCertPath;
/// @brief Contains the string values of the settings names that will be saved
static const char *const m_SettingsName[];
/// @brief Core config filename (not the Qt settings filename)
static const char m_ConfigFilename[];
signals: signals:
void tlsChanged(); void tlsChanged();

View file

@ -54,11 +54,18 @@ ConfigScopes::ConfigScopes(std::shared_ptr<Deps> deps)
m_pSystemSettingsProxy->loadSystem(); m_pSystemSettingsProxy->loadSystem();
} }
void ConfigScopes::clear() const {
m_pUserSettingsProxy->clear();
m_pSystemSettingsProxy->clear();
}
void ConfigScopes::signalReady() { emit ready(); } void ConfigScopes::signalReady() { emit ready(); }
void ConfigScopes::save() { void ConfigScopes::save(bool emitSaving) {
qDebug("emitting config saving signal"); if (emitSaving) {
emit saving(); qDebug("emitting config saving signal");
emit saving();
}
qDebug("writing config to filesystem"); qDebug("writing config to filesystem");
m_pUserSettingsProxy->sync(); m_pUserSettingsProxy->sync();

View file

@ -42,8 +42,10 @@ public:
explicit ConfigScopes(std::shared_ptr<Deps> deps = std::make_shared<Deps>()); explicit ConfigScopes(std::shared_ptr<Deps> deps = std::make_shared<Deps>());
~ConfigScopes() override = default; ~ConfigScopes() override = default;
void clear() const;
void signalReady() override; void signalReady() override;
void save() override; void save(bool emitSaving = true) override;
bool scopeContains( bool scopeContains(
const QString &name, Scope scope = Scope::Current) const override; const QString &name, Scope scope = Scope::Current) const override;
bool isActiveScopeWritable() const override; bool isActiveScopeWritable() const override;

View file

@ -48,8 +48,10 @@ public:
* @brief Signals to listeners to save and calls `sync` on underlying Qt * @brief Signals to listeners to save and calls `sync` on underlying Qt
* config. * config.
* *
* @param emitSaving Whether to emit the saving signal which typically
* triggers listeners to write their current state to the config.
*/ */
virtual void save() = 0; virtual void save(bool emitSaving = true) = 0;
/** /**
* @brief Check a scope for a config value (default is current scope). * @brief Check a scope for a config value (default is current scope).

View file

@ -0,0 +1,38 @@
/*
* 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
namespace synergy::gui::config {
/**
* @brief Represents the state of the server config dialog.
*
* Future design consideration: Once moving the server config dialog to the GUI
* lib, we can probably just pass a reference to that rather than needing an
* object to track it's state.
*/
class ServerConfigDialogState {
public:
bool isVisible() const { return m_isVisible; }
void setVisible(bool isVisible) { m_isVisible = isVisible; }
private:
bool m_isVisible = false;
};
} // namespace synergy::gui::config

View file

@ -20,6 +20,8 @@
#include "constants.h" #include "constants.h"
#include "gui/config/IAppConfig.h" #include "gui/config/IAppConfig.h"
#include "gui/core/CoreTool.h" #include "gui/core/CoreTool.h"
#include "gui/paths.h"
#include "tls/TlsUtility.h"
#if defined(Q_OS_MAC) #if defined(Q_OS_MAC)
#include "OSXHelpers.h" #include "OSXHelpers.h"
@ -37,7 +39,7 @@
namespace synergy::gui { namespace synergy::gui {
const int kRetryDelay = 1000; const int kRetryDelay = 1000;
const auto kLastConfigFilename = "LastConfig.cfg"; const auto kServerConfigFilename = "synergy-server.conf";
const auto kLineSplitRegex = QRegularExpression("\r|\n|\r\n"); const auto kLineSplitRegex = QRegularExpression("\r|\n|\r\n");
// //
@ -135,9 +137,10 @@ QString CoreProcess::Deps::getProfileRoot() const {
CoreProcess::CoreProcess( CoreProcess::CoreProcess(
IAppConfig &appConfig, IServerConfig &serverConfig, IAppConfig &appConfig, IServerConfig &serverConfig,
std::shared_ptr<Deps> deps) const license::ILicense &license, std::shared_ptr<Deps> deps)
: m_appConfig(appConfig), : m_appConfig(appConfig),
m_serverConfig(serverConfig), m_serverConfig(serverConfig),
m_license(license),
m_pDeps(deps) { m_pDeps(deps) {
connect( connect(
@ -339,65 +342,19 @@ void CoreProcess::start(std::optional<ProcessMode> processModeOption) {
setConnectionState(ConnectionState::Connecting); setConnectionState(ConnectionState::Connecting);
QString app;
QStringList args;
args << "-f"
<< "--no-tray"
<< "--debug" << m_appConfig.logLevelText();
args << "--name" << m_appConfig.screenName();
if (processMode == ProcessMode::kDesktop) { if (processMode == ProcessMode::kDesktop) {
m_pDeps->process().create(); 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 QString app;
QStringList args;
addGenericArgs(args, processMode);
if (m_serverConfig.enableDragAndDrop()) { if (mode() == Mode::Server && !addServerArgs(args, app)) {
args << "--enable-drag-drop"; qDebug("failed to add server args for core process, aborting start");
} return;
} else if (mode() == Mode::Client && !addClientArgs(args, app)) {
#endif qDebug("failed to add client args for core process, aborting start");
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; return;
} }
@ -483,7 +440,62 @@ void CoreProcess::cleanup() {
m_pDeps->ipcClient().disconnectFromHost(); m_pDeps->ipcClient().disconnectFromHost();
} }
bool CoreProcess::serverArgs(QStringList &args, QString &app) { bool CoreProcess::addGenericArgs(
QStringList &args, const ProcessMode processMode) const {
args << "-f"
<< "--no-tray"
<< "--debug" << m_appConfig.logLevelText();
args << "--name" << m_appConfig.screenName();
if (processMode != ProcessMode::kDesktop) {
// 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";
}
#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";
}
return true;
}
bool CoreProcess::addServerArgs(QStringList &args, QString &app) {
app = m_pDeps->appPath(m_appConfig.coreServerName()); app = m_pDeps->appPath(m_appConfig.coreServerName());
if (!m_pDeps->fileExists(app)) { if (!m_pDeps->fileExists(app)) {
@ -497,7 +509,7 @@ bool CoreProcess::serverArgs(QStringList &args, QString &app) {
args << "--log" << m_appConfig.logFilename(); args << "--log" << m_appConfig.logFilename();
} }
QString configFilename = persistConfig(); QString configFilename = persistServerConfig();
if (configFilename.isEmpty()) { if (configFilename.isEmpty()) {
qFatal("config file name empty for server args"); qFatal("config file name empty for server args");
return false; return false;
@ -526,10 +538,22 @@ bool CoreProcess::serverArgs(QStringList &args, QString &app) {
args << "--serial-key" << m_appConfig.serialKey(); args << "--serial-key" << m_appConfig.serialKey();
} }
// bizarrely, the tls cert path arg was being given to the core client.
// since it's not clear why (it is only needed for the server), this has now
// been moved to server args.
if (m_appConfig.tlsEnabled()) {
TlsUtility tlsUtility(m_appConfig, m_license);
if (!tlsUtility.persistCertificate()) {
qCritical("failed to persist tls certificate");
return false;
}
args << "--tls-cert" << m_appConfig.tlsCertPath();
}
return true; return true;
} }
bool CoreProcess::clientArgs(QStringList &args, QString &app) { bool CoreProcess::addClientArgs(QStringList &args, QString &app) {
app = m_pDeps->appPath(m_appConfig.coreClientName()); app = m_pDeps->appPath(m_appConfig.coreClientName());
if (!m_pDeps->fileExists(app)) { if (!m_pDeps->fileExists(app)) {
@ -568,35 +592,25 @@ bool CoreProcess::clientArgs(QStringList &args, QString &app) {
return true; return true;
} }
QString CoreProcess::persistConfig() const { QString CoreProcess::persistServerConfig() const {
QString configFullPath; QString configFullPath;
if (m_appConfig.useExternalConfig()) { if (m_appConfig.useExternalConfig()) {
return m_appConfig.configFile(); return m_appConfig.configFile();
} }
for (auto path : const auto configDir = paths::configDir(true);
{QStandardPaths::AppDataLocation, QStandardPaths::AppConfigLocation}) { const auto configDirPath = configDir.absolutePath();
auto configDirPath = QStandardPaths::writableLocation(path);
if (!QDir().mkpath(configDirPath)) {
qWarning("failed to create config folder: %s", qPrintable(configDirPath));
continue;
}
QFile configFile(configDirPath + "/" + kLastConfigFilename); QFile configFile(configDirPath + "/" + kServerConfigFilename);
if (!configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { if (!configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qWarning( qFatal(
"failed to open core config file: %s", "failed to open core config file for write: %s",
qPrintable(configFile.fileName())); qPrintable(configFile.fileName()));
continue;
}
m_serverConfig.save(configFile);
configFile.close();
return configFile.fileName();
} }
qFatal("failed to persist config file"); m_serverConfig.save(configFile);
return ""; configFile.close();
return configFile.fileName();
} }
QString CoreProcess::modeString() const { QString CoreProcess::modeString() const {

View file

@ -21,6 +21,7 @@
#include "gui/config/IServerConfig.h" #include "gui/config/IServerConfig.h"
#include "gui/ipc/QIpcClient.h" #include "gui/ipc/QIpcClient.h"
#include "gui/proxy/QProcessProxy.h" #include "gui/proxy/QProcessProxy.h"
#include "license/License.h"
#include <QMutex> #include <QMutex>
#include <QObject> #include <QObject>
@ -58,6 +59,7 @@ public:
explicit CoreProcess( explicit CoreProcess(
IAppConfig &appConfig, IServerConfig &serverConfig, IAppConfig &appConfig, IServerConfig &serverConfig,
const license::ILicense &license,
std::shared_ptr<Deps> deps = std::make_shared<Deps>()); std::shared_ptr<Deps> deps = std::make_shared<Deps>());
void extracted(QString &app, QStringList &args); void extracted(QString &app, QStringList &args);
@ -98,9 +100,10 @@ private:
void startService(const QString &app, const QStringList &args); void startService(const QString &app, const QStringList &args);
void stopDesktop() const; void stopDesktop() const;
void stopService(); void stopService();
bool serverArgs(QStringList &args, QString &app); bool addGenericArgs(QStringList &args, const ProcessMode processMode) const;
bool clientArgs(QStringList &args, QString &app); bool addServerArgs(QStringList &args, QString &app);
QString persistConfig() const; bool addClientArgs(QStringList &args, QString &app);
QString persistServerConfig() const;
QString modeString() const; QString modeString() const;
QString processModeString() const; QString processModeString() const;
void setConnectionState(ConnectionState state); void setConnectionState(ConnectionState state);
@ -117,6 +120,7 @@ private:
IAppConfig &m_appConfig; IAppConfig &m_appConfig;
IServerConfig &m_serverConfig; IServerConfig &m_serverConfig;
const license::ILicense &m_license;
std::shared_ptr<Deps> m_pDeps; std::shared_ptr<Deps> m_pDeps;
QString m_address; QString m_address;
ProcessState m_processState = ProcessState::Stopped; ProcessState m_processState = ProcessState::Stopped;

View file

@ -18,6 +18,7 @@
#include "ServerConnection.h" #include "ServerConnection.h"
#include "ServerMessage.h" #include "ServerMessage.h"
#include "gui/config/ServerConfigDialogState.h"
#include "messages.h" #include "messages.h"
#include <QMessageBox> #include <QMessageBox>
@ -40,10 +41,12 @@ messages::NewClientPromptResult ServerConnection::Deps::showNewClientPrompt(
ServerConnection::ServerConnection( ServerConnection::ServerConnection(
QWidget *parent, IAppConfig &appConfig, IServerConfig &serverConfig, QWidget *parent, IAppConfig &appConfig, IServerConfig &serverConfig,
const config::ServerConfigDialogState &serverConfigDialogState,
std::shared_ptr<Deps> deps) std::shared_ptr<Deps> deps)
: m_pParent(parent), : m_pParent(parent),
m_appConfig(appConfig), m_appConfig(appConfig),
m_serverConfig(serverConfig), m_serverConfig(serverConfig),
m_serverConfigDialogState(serverConfigDialogState),
m_pDeps(deps) {} m_pDeps(deps) {}
void ServerConnection::handleLogLine(const QString &logLine) { void ServerConnection::handleLogLine(const QString &logLine) {
@ -53,6 +56,16 @@ void ServerConnection::handleLogLine(const QString &logLine) {
return; return;
} }
if (m_messageShowing) {
qDebug("new client message already shown, skipping for now");
return;
}
if (m_serverConfigDialogState.isVisible()) {
qDebug("server config dialog visible, skipping new client prompt");
return;
}
if (m_appConfig.useExternalConfig()) { if (m_appConfig.useExternalConfig()) {
qDebug("external config enabled, skipping new client prompt"); qDebug("external config enabled, skipping new client prompt");
return; return;
@ -75,11 +88,6 @@ void ServerConnection::handleNewClient(const QString &clientName) {
m_receivedClients.append(clientName); m_receivedClients.append(clientName);
if (m_messageShowing) {
qDebug("new client message already shown, skipping");
return;
}
if (m_serverConfig.isFull()) { if (m_serverConfig.isFull()) {
qDebug( qDebug(
"server config full, skipping new client prompt for: %s", "server config full, skipping new client prompt for: %s",

View file

@ -22,6 +22,7 @@
#include "gui/config/IAppConfig.h" #include "gui/config/IAppConfig.h"
#include "gui/config/IServerConfig.h" #include "gui/config/IServerConfig.h"
#include "gui/config/ServerConfigDialogState.h"
#include "gui/messages.h" #include "gui/messages.h"
namespace synergy::gui { namespace synergy::gui {
@ -39,6 +40,7 @@ public:
explicit ServerConnection( explicit ServerConnection(
QWidget *parent, IAppConfig &appConfig, IServerConfig &serverConfig, QWidget *parent, IAppConfig &appConfig, IServerConfig &serverConfig,
const config::ServerConfigDialogState &serverConfigDialogState,
std::shared_ptr<Deps> deps = std::make_shared<Deps>()); std::shared_ptr<Deps> deps = std::make_shared<Deps>());
void handleLogLine(const QString &logLine); void handleLogLine(const QString &logLine);
@ -52,6 +54,7 @@ private:
QWidget *m_pParent; QWidget *m_pParent;
IAppConfig &m_appConfig; IAppConfig &m_appConfig;
IServerConfig &m_serverConfig; IServerConfig &m_serverConfig;
const config::ServerConfigDialogState &m_serverConfigDialogState;
std::shared_ptr<Deps> m_pDeps; std::shared_ptr<Deps> m_pDeps;
QStringList m_receivedClients; QStringList m_receivedClients;
bool m_messageShowing = false; bool m_messageShowing = false;

View file

@ -0,0 +1,67 @@
/*
* 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 "diagnostic.h"
#include "config/ConfigScopes.h"
#include "paths.h"
#include <QApplication>
#include <QDir>
#include <QProcess>
namespace synergy::gui::diagnostic {
void restart() {
QString program = QCoreApplication::applicationFilePath();
QStringList arguments = QCoreApplication::arguments();
// prevent infinite reset loop when env var set.
arguments << "--no-reset";
qInfo("launching new process: %s", qPrintable(program));
QProcess::startDetached(program, arguments);
qDebug("exiting current process");
QApplication::exit();
}
void clearSettings(ConfigScopes &scopes, bool enableRestart) {
qDebug("clearing settings");
scopes.clear();
// save but do not emit saving signal which will prevent the current state of
// the app config and server configs from being applied.
scopes.save(false);
auto configDir = paths::configDir();
qDebug("removing config dir: %s", qPrintable(configDir.absolutePath()));
configDir.removeRecursively();
auto profileDir = paths::coreProfileDir();
qDebug("removing profile dir: %s", qPrintable(profileDir.absolutePath()));
profileDir.removeRecursively();
if (enableRestart) {
qDebug("restarting");
restart();
} else {
qDebug("skipping restart");
}
}
} // namespace synergy::gui::diagnostic

26
src/lib/gui/diagnostic.h Normal file
View file

@ -0,0 +1,26 @@
/*
* 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/ConfigScopes.h"
namespace synergy::gui::diagnostic {
void clearSettings(ConfigScopes &scopes, bool enableRestart);
}

View file

@ -17,6 +17,8 @@
#include "dotenv.h" #include "dotenv.h"
#include "paths.h"
#include <QCoreApplication> #include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
@ -55,17 +57,10 @@ void dotenv(const QString &filename) {
"no %s file in dir: %s", qPrintable(filename), "no %s file in dir: %s", qPrintable(filename),
qPrintable(fileInfo.absolutePath())); qPrintable(fileInfo.absolutePath()));
// if nothing in current dir, then try the app data dir. // if nothing in current dir, then try the config dir.
// this makes it a bit easier for engineers in the field to have an easily // this makes it a bit easier for engineers in the field to have an easily
// predictable location for the .env file. // predictable location for the .env file.
QDir configDir( const auto orgDir = paths::configDir();
QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
// HACK: since we have the org name set to the app name, the config dir is
// confusing. make this simple by using the org dir instead.
// use `filePath("..")` instead of `cdUp` to avoid the existence check.
QDir orgDir = configDir.filePath("..");
orgDir = orgDir.absolutePath();
filePath = orgDir.filePath(filename); filePath = orgDir.filePath(filename);
if (!open(file, filePath)) { if (!open(file, filePath)) {

View file

@ -249,4 +249,17 @@ showNewClientPrompt(QWidget *parent, const QString &clientName) {
} }
} }
bool showClearSettings(QWidget *parent) {
QMessageBox message(parent);
message.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
const auto clear =
message.addButton(QObject::tr("Clear settings"), QMessageBox::AcceptRole);
message.setText(
"<p>Are you sure you want to clear all settings and restart Synergy?</p>"
"<p>This action cannot be undone.</p>");
message.exec();
return message.clickedButton() == clear;
}
} // namespace synergy::gui::messages } // namespace synergy::gui::messages

View file

@ -46,4 +46,6 @@ void showClientConnectError(
NewClientPromptResult NewClientPromptResult
showNewClientPrompt(QWidget *parent, const QString &clientName); showNewClientPrompt(QWidget *parent, const QString &clientName);
bool showClearSettings(QWidget *parent);
} // namespace synergy::gui::messages } // namespace synergy::gui::messages

67
src/lib/gui/paths.h Normal file
View file

@ -0,0 +1,67 @@
/*
* 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 "core/CoreTool.h"
#include <QDir>
#include <QStandardPaths>
#include <QString>
const auto kCertificateFilename = "Synergy.pem";
const auto kSslDir = "SSL";
namespace synergy::gui::paths {
/**
* @brief Gets the org config dir (parent of app config dir).
*/
inline QDir configDir(const bool persist = false) {
const QDir configDir(
QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
// HACK: since we have the org name set to the app name, the config dir is
// confusing. make this simple by using the org dir instead.
// use `filePath("..")` instead of `cdUp` to avoid the existence check.
const QDir orgDir = configDir.filePath("..");
if (persist) {
const auto orgDirPath = orgDir.absolutePath();
if (!QDir().mkpath(orgDirPath)) {
qFatal("failed to persist config dir: %s", qPrintable(orgDirPath));
}
}
return orgDir.absolutePath();
}
/**
* @brief Uses the Core tool to get the profile dir.
*/
inline QDir coreProfileDir() {
CoreTool coreTool;
return QDir(coreTool.getProfileDir());
}
inline QString defaultTlsCertPath() {
const auto root = coreProfileDir();
const auto sslDirPath = QDir(root.filePath(kSslDir));
return sslDirPath.filePath(kCertificateFilename);
}
} // namespace synergy::gui::paths

View file

@ -29,7 +29,7 @@ public:
virtual void loadUser(); virtual void loadUser();
virtual void loadSystem(); virtual void loadSystem();
virtual void clear() { m_pSettings->clear(); }
virtual void sync() { m_pSettings->sync(); } virtual void sync() { m_pSettings->sync(); }
virtual int beginReadArray(const QString &prefix); virtual int beginReadArray(const QString &prefix);
virtual void beginWriteArray(const QString &prefix); virtual void beginWriteArray(const QString &prefix);

View file

@ -18,18 +18,16 @@
#include "TlsCertificate.h" #include "TlsCertificate.h"
#include "TlsFingerprint.h" #include "TlsFingerprint.h"
#include "gui/paths.h"
#include <QCoreApplication> #include <QCoreApplication>
#include <QDir> #include <QDir>
#include <QProcess> #include <QProcess>
#include <optional>
static const char *const kCertificateKeyLength = "rsa:"; static const char *const kCertificateKeyLength = "rsa:";
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 kSslDir = "SSL";
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
static const char *const kWinOpenSslDir = "OpenSSL"; static const char *const kWinOpenSslDir = "OpenSSL";
@ -39,6 +37,8 @@ static const char *const kConfigFile = "synergy.conf";
static const char *const kUnixOpenSslCommand = "openssl"; static const char *const kUnixOpenSslCommand = "openssl";
#endif #endif
using namespace synergy::gui;
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
namespace synergy::gui { namespace synergy::gui {
@ -87,12 +87,7 @@ using namespace synergy::gui;
#endif #endif
TlsCertificate::TlsCertificate(QObject *parent) : QObject(parent) { TlsCertificate::TlsCertificate(QObject *parent) : QObject(parent) {}
m_profileDir = m_coreTool.getProfileDir();
if (m_profileDir.isEmpty()) {
qCritical("empty profile directory result");
}
}
bool TlsCertificate::runTool(const QStringList &args) { bool TlsCertificate::runTool(const QStringList &args) {
QString program; QString program;
@ -157,21 +152,17 @@ bool TlsCertificate::runTool(const QStringList &args) {
} }
bool TlsCertificate::generateCertificate(const QString &path, int keyLength) { bool TlsCertificate::generateCertificate(const QString &path, int keyLength) {
QString sslDirPath = qDebug("generating tls certificate: %s", qUtf8Printable(path));
QString("%1%2%3").arg(m_profileDir).arg(QDir::separator()).arg(kSslDir);
QString defaultPath = QString("%1%2%3") QFileInfo info(path);
.arg(sslDirPath) QDir dir(info.absolutePath());
.arg(QDir::separator()) if (!dir.exists() && !dir.mkpath(".")) {
.arg(kCertificateFilename); qCritical("failed to create directory for tls certificate");
return false;
}
QString keySize = kCertificateKeyLength + QString::number(keyLength); QString keySize = kCertificateKeyLength + QString::number(keyLength);
const QString pathToUse =
QDir::cleanPath(path.isEmpty() ? defaultPath : path);
qDebug("generating tls certificate: %s", qUtf8Printable(pathToUse));
QStringList arguments; QStringList arguments;
// self signed certificate // self signed certificate
@ -179,7 +170,7 @@ bool TlsCertificate::generateCertificate(const QString &path, int keyLength) {
arguments.append("-x509"); arguments.append("-x509");
arguments.append("-nodes"); arguments.append("-nodes");
// valide duration // valid duration
arguments.append("-days"); arguments.append("-days");
arguments.append(kCertificateLifetime); arguments.append(kCertificateLifetime);
@ -193,22 +184,18 @@ bool TlsCertificate::generateCertificate(const QString &path, int keyLength) {
arguments.append("-newkey"); arguments.append("-newkey");
arguments.append(keySize); arguments.append(keySize);
if (QDir sslDir(sslDirPath); !sslDir.exists()) {
sslDir.mkpath(".");
}
// key output filename // key output filename
arguments.append("-keyout"); arguments.append("-keyout");
arguments.append(pathToUse); arguments.append(path);
// certificate output filename // certificate output filename
arguments.append("-out"); arguments.append("-out");
arguments.append(pathToUse); arguments.append(path);
if (runTool(arguments)) { if (runTool(arguments)) {
qDebug("tls certificate generated"); qDebug("tls certificate generated");
return generateFingerprint(pathToUse); return generateFingerprint(path);
} else { } else {
qCritical("failed to generate tls certificate"); qCritical("failed to generate tls certificate");
return false; return false;

View file

@ -35,7 +35,6 @@ private:
bool generateFingerprint(const QString &certificateFilename); bool generateFingerprint(const QString &certificateFilename);
private: private:
QString m_profileDir;
QString m_toolStdout; QString m_toolStdout;
[[no_unique_address]] CoreTool m_coreTool; [[no_unique_address]] CoreTool m_coreTool;
}; };

View file

@ -20,12 +20,13 @@
#include "TlsCertificate.h" #include "TlsCertificate.h"
#include "constants.h" #include "constants.h"
#include <QFile>
#include <QString> #include <QString>
namespace synergy::gui { namespace synergy::gui {
TlsUtility::TlsUtility( TlsUtility::TlsUtility(
const IAppConfig &appConfig, const license::License &license) const IAppConfig &appConfig, const license::ILicense &license)
: m_appConfig(appConfig), : m_appConfig(appConfig),
m_license(license) {} m_license(license) {}
@ -48,10 +49,20 @@ bool TlsUtility::generateCertificate() {
return false; return false;
} }
auto path = m_appConfig.tlsCertPath();
auto length = m_appConfig.tlsKeyLength(); auto length = m_appConfig.tlsKeyLength();
return m_certificate.generateCertificate(path, length); return m_certificate.generateCertificate(m_appConfig.tlsCertPath(), length);
}
bool TlsUtility::persistCertificate() {
qDebug("persisting tls certificate");
if (QFile::exists(m_appConfig.tlsCertPath())) {
qDebug("tls certificate already exists");
return true;
}
return generateCertificate();
} }
} // namespace synergy::gui } // namespace synergy::gui

View file

@ -31,9 +31,10 @@ class TlsUtility : public QObject {
public: public:
explicit TlsUtility( explicit TlsUtility(
const IAppConfig &appConfig, const license::License &license); const IAppConfig &appConfig, const license::ILicense &license);
bool generateCertificate(); bool generateCertificate();
bool persistCertificate();
/** /**
* @brief Combines the availability and the enabled status of TLS. * @brief Combines the availability and the enabled status of TLS.
@ -51,7 +52,7 @@ public:
private: private:
const IAppConfig &m_appConfig; const IAppConfig &m_appConfig;
const license::License &m_license; const license::ILicense &m_license;
TlsCertificate m_certificate; TlsCertificate m_certificate;
}; };

View file

@ -0,0 +1,32 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2016 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
class Server;
class LicenseHandler;
class LicenseTests;
namespace synergy::license {
class ILicense {
public:
virtual ~ILicense() = default;
virtual bool isTlsAvailable() const = 0;
};
} // namespace synergy::license

View file

@ -17,6 +17,7 @@
#pragma once #pragma once
#include "ILicense.h"
#include "SerialKey.h" #include "SerialKey.h"
#include <chrono> #include <chrono>
@ -30,7 +31,7 @@ class LicenseTests;
namespace synergy::license { namespace synergy::license {
class License { class License : public ILicense {
friend class ::Server; friend class ::Server;
friend class ::LicenseHandler; friend class ::LicenseHandler;
friend class ::LicenseTests; friend class ::LicenseTests;
@ -44,18 +45,20 @@ class License {
public: public:
explicit License(const SerialKey &serialKey); explicit License(const SerialKey &serialKey);
explicit License(const std::string &hexString); explicit License(const std::string &hexString);
~License() override = default;
friend bool operator==(License const &lhs, License const &rhs) { friend bool operator==(License const &lhs, License const &rhs) {
return lhs.m_serialKey == rhs.m_serialKey; return lhs.m_serialKey == rhs.m_serialKey;
} }
bool isTlsAvailable() const override;
bool isValid() const { return m_serialKey.isValid; } bool isValid() const { return m_serialKey.isValid; }
bool isExpiringSoon() const; bool isExpiringSoon() const;
bool isExpired() const; bool isExpired() const;
bool isTrial() const; bool isTrial() const;
bool isSubscription() const; bool isSubscription() const;
bool isTimeLimited() const; bool isTimeLimited() const;
bool isTlsAvailable() const;
days daysLeft() const; days daysLeft() const;
Edition productEdition() const; Edition productEdition() const;
std::string productName() const; std::string productName() const;

View file

@ -40,7 +40,7 @@ IDataSocket *SecureServerSocket::accept() {
socket->initSsl(true); socket->initSsl(true);
setListeningJob(); setListeningJob();
auto certificateFilename = getCertifcateFileName(); auto certificateFilename = getCertificateFileName();
if (socket->loadCertificates(certificateFilename)) { if (socket->loadCertificates(certificateFilename)) {
socket->secureAccept(); socket->secureAccept();
} else { } else {
@ -64,7 +64,7 @@ IDataSocket *SecureServerSocket::accept() {
return dynamic_cast<IDataSocket *>(socket); return dynamic_cast<IDataSocket *>(socket);
} }
std::string SecureServerSocket::getCertifcateFileName() const { std::string SecureServerSocket::getCertificateFileName() const {
// if the tls cert option is set use that for the certificate file // if the tls cert option is set use that for the certificate file
auto certificateFilename = ArgParser::argsBase().m_tlsCertFile; auto certificateFilename = ArgParser::argsBase().m_tlsCertFile;

View file

@ -27,5 +27,5 @@ public:
IDataSocket *accept() override; IDataSocket *accept() override;
private: private:
std::string getCertifcateFileName() const; std::string getCertificateFileName() const;
}; };

View file

@ -48,12 +48,12 @@ public:
MOCK_METHOD(bool, isActiveScopeWritable, (), (const, override)); MOCK_METHOD(bool, isActiveScopeWritable, (), (const, override));
MOCK_METHOD(const QSettingsProxy &, activeSettings, (), (const, override)); MOCK_METHOD(const QSettingsProxy &, activeSettings, (), (const, override));
MOCK_METHOD(QSettingsProxy &, activeSettings, (), (override)); MOCK_METHOD(QSettingsProxy &, activeSettings, (), (override));
MOCK_METHOD(void, save, (), (override)); MOCK_METHOD(void, save, (bool), (override));
}; };
struct DepsMock : public AppConfig::Deps { struct DepsMock : public AppConfig::Deps {
DepsMock() { DepsMock() {
ON_CALL(*this, profileDir()).WillByDefault(Return("stub")); ON_CALL(*this, defaultTlsCertPath()).WillByDefault(Return("stub"));
ON_CALL(*this, hostname()).WillByDefault(Return("stub")); ON_CALL(*this, hostname()).WillByDefault(Return("stub"));
} }
@ -61,7 +61,7 @@ struct DepsMock : public AppConfig::Deps {
return std::make_shared<NiceMock<DepsMock>>(); return std::make_shared<NiceMock<DepsMock>>();
} }
MOCK_METHOD(QString, profileDir, (), (const, override)); MOCK_METHOD(QString, defaultTlsCertPath, (), (const, override));
MOCK_METHOD(QString, hostname, (), (const, override)); MOCK_METHOD(QString, hostname, (), (const, override));
}; };

View file

@ -63,8 +63,7 @@ struct DepsMock : public ConfigScopes::Deps {
} // namespace } // namespace
TEST(ConfigScopesTests, ctor_callsMakeUserSettings) { TEST(ConfigScopesTests, ctor_callsMakeUserSettings) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
EXPECT_CALL(*deps, makeUserSettings()).Times(1); EXPECT_CALL(*deps, makeUserSettings()).Times(1);
@ -72,8 +71,7 @@ TEST(ConfigScopesTests, ctor_callsMakeUserSettings) {
} }
TEST(ConfigScopesTests, ctor_callsMakeSystemSettings) { TEST(ConfigScopesTests, ctor_callsMakeSystemSettings) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
EXPECT_CALL(*deps, makeSystemSettings()).Times(1); EXPECT_CALL(*deps, makeSystemSettings()).Times(1);
@ -81,8 +79,7 @@ TEST(ConfigScopesTests, ctor_callsMakeSystemSettings) {
} }
TEST(ConfigScopesTests, save_syncsBothScopes) { TEST(ConfigScopesTests, save_syncsBothScopes) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
ConfigScopes configScopes(deps); ConfigScopes configScopes(deps);
@ -93,8 +90,7 @@ TEST(ConfigScopesTests, save_syncsBothScopes) {
} }
TEST(ConfigScopesTests, activeSettings_returnsUserSettingsByDefault) { TEST(ConfigScopesTests, activeSettings_returnsUserSettingsByDefault) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
ConfigScopes configScopes(deps); ConfigScopes configScopes(deps);
@ -102,8 +98,7 @@ TEST(ConfigScopesTests, activeSettings_returnsUserSettingsByDefault) {
} }
TEST(ConfigScopesTests, activeSettings_returnsSystemSettingsWhenSystemScope) { TEST(ConfigScopesTests, activeSettings_returnsSystemSettingsWhenSystemScope) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
ConfigScopes configScopes(deps); ConfigScopes configScopes(deps);
configScopes.setActiveScope(ConfigScopes::Scope::System); configScopes.setActiveScope(ConfigScopes::Scope::System);
@ -112,8 +107,7 @@ TEST(ConfigScopesTests, activeSettings_returnsSystemSettingsWhenSystemScope) {
} }
TEST(ConfigScopesTests, setActiveScope_setsCurrentScope) { TEST(ConfigScopesTests, setActiveScope_setsCurrentScope) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
ConfigScopes configScopes(deps); ConfigScopes configScopes(deps);
@ -125,8 +119,7 @@ TEST(ConfigScopesTests, setActiveScope_setsCurrentScope) {
TEST( TEST(
ConfigScopesTests, ConfigScopesTests,
isActiveScopeWritable_returnsTrueWhenUserSettingsWritable) { isActiveScopeWritable_returnsTrueWhenUserSettingsWritable) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
ConfigScopes configScopes(deps); ConfigScopes configScopes(deps);
@ -138,8 +131,7 @@ TEST(
TEST( TEST(
ConfigScopesTests, ConfigScopesTests,
scopeContains_byDefault_returnsTrueWhenUserSettingsContainsKey) { scopeContains_byDefault_returnsTrueWhenUserSettingsContainsKey) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
ON_CALL(*deps->m_pUserSettings, contains(_)).WillByDefault(Return(true)); ON_CALL(*deps->m_pUserSettings, contains(_)).WillByDefault(Return(true));
@ -151,8 +143,7 @@ TEST(
TEST( TEST(
ConfigScopesTests, ConfigScopesTests,
scopeContains_userScope_returnsTrueWhenUserSettingsContainsKey) { scopeContains_userScope_returnsTrueWhenUserSettingsContainsKey) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
ON_CALL(*deps->m_pUserSettings, contains(_)).WillByDefault(Return(true)); ON_CALL(*deps->m_pUserSettings, contains(_)).WillByDefault(Return(true));
@ -164,8 +155,7 @@ TEST(
TEST( TEST(
ConfigScopesTests, ConfigScopesTests,
scopeContains_systemScope_returnsTrueWhenSystemSettingsContainsKey) { scopeContains_systemScope_returnsTrueWhenSystemSettingsContainsKey) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
ON_CALL(*deps->m_pSystemSettings, contains(_)).WillByDefault(Return(true)); ON_CALL(*deps->m_pSystemSettings, contains(_)).WillByDefault(Return(true));
@ -175,8 +165,7 @@ TEST(
} }
TEST(ConfigScopesTests, activeFilePath_returnsUserSettingsFileNameByDefault) { TEST(ConfigScopesTests, activeFilePath_returnsUserSettingsFileNameByDefault) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
ON_CALL(*deps->m_pUserSettings, fileName()).WillByDefault(Return("test")); ON_CALL(*deps->m_pUserSettings, fileName()).WillByDefault(Return("test"));
ConfigScopes configScopes(deps); ConfigScopes configScopes(deps);
@ -185,8 +174,7 @@ TEST(ConfigScopesTests, activeFilePath_returnsUserSettingsFileNameByDefault) {
} }
TEST(ConfigScopesTests, getFromScope_byDefault_returnsValueFromActiveSettings) { TEST(ConfigScopesTests, getFromScope_byDefault_returnsValueFromActiveSettings) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
ON_CALL(*deps->m_pUserSettings, value(_, _)).WillByDefault(Return("test")); ON_CALL(*deps->m_pUserSettings, value(_, _)).WillByDefault(Return("test"));
ConfigScopes configScopes(deps); ConfigScopes configScopes(deps);
@ -195,8 +183,7 @@ TEST(ConfigScopesTests, getFromScope_byDefault_returnsValueFromActiveSettings) {
} }
TEST(ConfigScopesTests, setInScope_byDefault_setsValueInActiveSettings) { TEST(ConfigScopesTests, setInScope_byDefault_setsValueInActiveSettings) {
std::shared_ptr<NiceMock<DepsMock>> deps = auto deps = std::make_shared<NiceMock<DepsMock>>();
std::make_shared<NiceMock<DepsMock>>();
ConfigScopes configScopes(deps); ConfigScopes configScopes(deps);

View file

@ -19,6 +19,7 @@
#include "gui/core/CoreProcess.h" #include "gui/core/CoreProcess.h"
#include "gui/ipc/IQIpcClient.h" #include "gui/ipc/IQIpcClient.h"
#include "gui/proxy/QProcessProxy.h" #include "gui/proxy/QProcessProxy.h"
#include "license/ILicense.h"
#include "shared/gui/mocks/AppConfigMock.h" #include "shared/gui/mocks/AppConfigMock.h"
#include "shared/gui/mocks/ServerConfigMock.h" #include "shared/gui/mocks/ServerConfigMock.h"
@ -27,10 +28,8 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
using namespace synergy::gui; using namespace synergy::gui;
using ::testing::_; using namespace synergy::license;
using ::testing::NiceMock; using namespace testing;
using ::testing::Return;
using ::testing::ReturnRef;
namespace { namespace {
@ -92,12 +91,23 @@ public:
NiceMock<QIpcClientMock> m_ipcClient; NiceMock<QIpcClientMock> m_ipcClient;
}; };
class CoreProcessTests : public ::testing::Test { class LicenseMock : public ILicense {
public: public:
CoreProcessTests() : m_coreProcess(m_appConfig, m_serverConfig, m_pDeps) {} LicenseMock() {
ON_CALL(*this, isTlsAvailable()).WillByDefault(Return(true));
}
MOCK_METHOD(bool, isTlsAvailable, (), (const, override));
};
class CoreProcessTests : public Test {
public:
CoreProcessTests()
: m_coreProcess(m_appConfig, m_serverConfig, m_license, m_pDeps) {}
NiceMock<AppConfigMock> m_appConfig; NiceMock<AppConfigMock> m_appConfig;
NiceMock<ServerConfigMock> m_serverConfig; NiceMock<ServerConfigMock> m_serverConfig;
NiceMock<LicenseMock> m_license;
std::shared_ptr<NiceMock<DepsMock>> m_pDeps = std::shared_ptr<NiceMock<DepsMock>> m_pDeps =
std::make_shared<NiceMock<DepsMock>>(); std::make_shared<NiceMock<DepsMock>>();
CoreProcess m_coreProcess; CoreProcess m_coreProcess;

View file

@ -15,6 +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/config/ServerConfigDialogState.h"
#include "gui/core/ServerConnection.h" #include "gui/core/ServerConnection.h"
#include "shared/gui/mocks/AppConfigMock.h" #include "shared/gui/mocks/AppConfigMock.h"
@ -45,11 +46,12 @@ public:
std::shared_ptr<DepsMock> m_pDeps = std::make_shared<NiceMock<DepsMock>>(); std::shared_ptr<DepsMock> m_pDeps = std::make_shared<NiceMock<DepsMock>>();
NiceMock<AppConfigMock> m_appConfig; NiceMock<AppConfigMock> m_appConfig;
NiceMock<ServerConfigMock> m_serverConfig; NiceMock<ServerConfigMock> m_serverConfig;
config::ServerConfigDialogState m_serverConfigDialogState;
}; };
TEST_F(ServerConnectionTests, handleLogLine_newClient_shouldShowPrompt) { TEST_F(ServerConnectionTests, handleLogLine_newClient_shouldShowPrompt) {
ServerConnection serverConnection( ServerConnection serverConnection(
nullptr, m_appConfig, m_serverConfig, m_pDeps); nullptr, m_appConfig, m_serverConfig, m_serverConfigDialogState, m_pDeps);
QString clientName = "test client"; QString clientName = "test client";
EXPECT_CALL(*m_pDeps, showNewClientPrompt(_, clientName)); EXPECT_CALL(*m_pDeps, showNewClientPrompt(_, clientName));
@ -59,7 +61,7 @@ TEST_F(ServerConnectionTests, handleLogLine_newClient_shouldShowPrompt) {
TEST_F(ServerConnectionTests, handleLogLine_ignoredClient_shouldNotShowPrompt) { TEST_F(ServerConnectionTests, handleLogLine_ignoredClient_shouldNotShowPrompt) {
ServerConnection serverConnection( ServerConnection serverConnection(
nullptr, m_appConfig, m_serverConfig, m_pDeps); nullptr, m_appConfig, m_serverConfig, m_serverConfigDialogState, m_pDeps);
ON_CALL(*m_pDeps, showNewClientPrompt(_, _)) ON_CALL(*m_pDeps, showNewClientPrompt(_, _))
.WillByDefault(testing::Return(messages::NewClientPromptResult::Ignore)); .WillByDefault(testing::Return(messages::NewClientPromptResult::Ignore));
serverConnection.handleLogLine(R"(unrecognised client name "stub")"); serverConnection.handleLogLine(R"(unrecognised client name "stub")");
@ -72,7 +74,7 @@ TEST_F(ServerConnectionTests, handleLogLine_ignoredClient_shouldNotShowPrompt) {
TEST_F( TEST_F(
ServerConnectionTests, handleLogLine_serverConfigFull_shouldNotShowPrompt) { ServerConnectionTests, handleLogLine_serverConfigFull_shouldNotShowPrompt) {
ServerConnection serverConnection( ServerConnection serverConnection(
nullptr, m_appConfig, m_serverConfig, m_pDeps); nullptr, m_appConfig, m_serverConfig, m_serverConfigDialogState, m_pDeps);
ON_CALL(m_serverConfig, isFull()).WillByDefault(testing::Return(true)); ON_CALL(m_serverConfig, isFull()).WillByDefault(testing::Return(true));
EXPECT_CALL(*m_pDeps, showNewClientPrompt(_, _)).Times(0); EXPECT_CALL(*m_pDeps, showNewClientPrompt(_, _)).Times(0);
@ -82,7 +84,7 @@ TEST_F(
TEST_F(ServerConnectionTests, handleLogLine_screenExists_shouldNotShowPrompt) { TEST_F(ServerConnectionTests, handleLogLine_screenExists_shouldNotShowPrompt) {
ServerConnection serverConnection( ServerConnection serverConnection(
nullptr, m_appConfig, m_serverConfig, m_pDeps); nullptr, m_appConfig, m_serverConfig, m_serverConfigDialogState, m_pDeps);
ON_CALL(m_serverConfig, screenExists(_)).WillByDefault(testing::Return(true)); ON_CALL(m_serverConfig, screenExists(_)).WillByDefault(testing::Return(true));
EXPECT_CALL(*m_pDeps, showNewClientPrompt(_, _)).Times(0); EXPECT_CALL(*m_pDeps, showNewClientPrompt(_, _)).Times(0);

View file

@ -39,7 +39,7 @@ TEST(SettingsDialogTests, ctor_getsScreenName) {
serialKey.isValid = true; serialKey.isValid = true;
License license(serialKey); License license(serialKey);
auto cpDeps = std::make_shared<CoreProcess::Deps>(); auto cpDeps = std::make_shared<CoreProcess::Deps>();
CoreProcess coreProcess(appConfig, serverConfig, cpDeps); CoreProcess coreProcess(appConfig, serverConfig, license, cpDeps);
EXPECT_CALL(appConfig, screenName()).Times(1); EXPECT_CALL(appConfig, screenName()).Times(1);