From 157fe818d8cf5a2bfee4f01c68d5d7e8cf03a916 Mon Sep 17 00:00:00 2001 From: Nick Bolton Date: Sat, 3 Aug 2024 01:17:29 +0100 Subject: [PATCH] Refactor local/global save/load and show dev thanks message (#7423) * WIP: Show dev thanks message * Improve error messages and guard license code * Print core command * Allow license tests when licensing disabled by default * Don't check invalid serial key * Fixed typo * Add error checking * Use shared ptr instead of static deps * Actually save and load the setting * Add dev thanks vars to CI * Show thanks message after connected * Refactor hack-job of local/global save/load to remove coupling and use Qt events system * Simplify config loading * Overloads for optionals * Fixed global config scope not loaded * Save message state before showing to avoid multiple messages * Update ChangeLog * Fixed lint warnings * Make function const * Reduce verbosity * Remove dead code, show version on start, tidy up lint warnings * Make product name code safer * Connect on enter press * Only show server specific wording when on server * Add emit commands * Log info and error * Remove deaad code * Remove test member * Disable start context menu action unless usable * Show more relvant connect message * Fixed close to tray not applied, and simplified restart core logic * Remove redundant include --- .github/workflows/ci.yml | 1 + ChangeLog | 1 + cmake/Definitions.cmake | 13 + src/gui/src/AboutDialog.cpp | 15 +- src/gui/src/AboutDialog.h | 2 +- src/gui/src/ActivationDialog.cpp | 3 +- src/gui/src/MainWindow.cpp | 379 +++++++------ src/gui/src/MainWindow.h | 23 +- src/gui/src/MainWindowBase.ui | 7 +- src/gui/src/QSynergyApplication.cpp | 16 +- src/gui/src/QSynergyApplication.h | 2 - src/gui/src/ScreenSettingsDialog.cpp | 2 +- src/gui/src/ServerConfig.cpp | 45 +- src/gui/src/ServerConfig.h | 35 +- src/gui/src/ServerConfigDialogBase.ui | 503 +++++++++--------- src/gui/src/ServerConnection.cpp | 4 +- src/gui/src/SettingsDialog.cpp | 26 +- src/gui/src/SettingsDialog.h | 2 +- src/gui/src/SetupWizard.cpp | 19 +- src/gui/src/SetupWizard.h | 6 +- src/gui/src/SetupWizardBase.ui | 25 +- src/gui/src/SetupWizardBlocker.cpp | 6 +- src/gui/src/SetupWizardBlocker.h | 11 +- src/gui/src/main.cpp | 67 ++- src/gui/src/validators/LineEditValidator.cpp | 2 +- src/gui/src/validators/ValidationError.cpp | 2 +- src/lib/arch/unix/ArchNetworkBSD.cpp | 12 +- src/lib/arch/unix/ArchNetworkBSD.h | 9 +- src/lib/gui/AppConfig.cpp | 483 +++++++++-------- src/lib/gui/AppConfig.h | 127 ++--- src/lib/gui/ConfigScopes.cpp | 210 ++++---- src/lib/gui/ConfigScopes.h | 88 +-- src/lib/gui/IConfigScopes.h | 43 +- src/lib/gui/LicenseHandler.cpp | 5 + src/lib/gui/LicenseHandler.h | 3 + src/lib/gui/QIpcClient.cpp | 12 +- src/lib/gui/TlsUtility.cpp | 3 + src/lib/gui/constants.h | 40 +- src/lib/gui/license_notices.cpp | 1 + src/lib/gui/messages.cpp | 159 ++++++ src/lib/gui/messages.h | 36 ++ src/lib/gui/styles.h | 49 ++ src/lib/gui/version.cpp | 38 ++ src/lib/gui/{CommonConfig.h => version.h} | 18 +- src/lib/server/Server.cpp | 12 +- src/test/shared/gui/QtCoreTest.h | 3 + src/test/shared/gui/QtTest.h | 3 + .../arch/unix/ArchNetworkBSDTests.cpp | 58 +- src/test/unittests/gui/AppConfigTests.cpp | 81 ++- .../unittests/gui/LicenseHandlerTests.cpp | 1 + 50 files changed, 1525 insertions(+), 1186 deletions(-) create mode 100644 src/lib/gui/messages.cpp create mode 100644 src/lib/gui/messages.h create mode 100644 src/lib/gui/styles.h create mode 100644 src/lib/gui/version.cpp rename src/lib/gui/{CommonConfig.h => version.h} (65%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e72d748c..f3e2c193b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,7 @@ env: SYNERGY_PRODUCT_NAME: ${{ vars.SYNERGY_PRODUCT_NAME }} SYNERGY_PACKAGE_PREFIX: ${{ vars.SYNERGY_PACKAGE_PREFIX }} SYNERGY_ENABLE_LICENSING: ${{ vars.SYNERGY_ENABLE_LICENSING }} + SYNERGY_SHOW_DEV_THANKS: ${{ vars.SYNERGY_SHOW_DEV_THANKS }} PACKAGE_BUILD: ${{ !github.event.pull_request.draft }} PACKAGE_UPLOAD: ${{ !github.event.pull_request.draft && github.event_name != 'schedule' }} UPLOAD_TO_GITHUB: ${{ github.event_name == 'pull_request' && !github.event.pull_request.draft }} diff --git a/ChangeLog b/ChangeLog index e1306b617..687c98780 100644 --- a/ChangeLog +++ b/ChangeLog @@ -66,6 +66,7 @@ Enhancements: - #7421 Only load core after settings have fully loaded - #7419 Introduce 'Advanced' tab to Preferences window - #7422 Handle empty value for computer name setting +- #7423 Refactor local/global save/load and show dev thanks message # 1.14.6 diff --git a/cmake/Definitions.cmake b/cmake/Definitions.cmake index ebb2d524a..abdb659ef 100644 --- a/cmake/Definitions.cmake +++ b/cmake/Definitions.cmake @@ -60,6 +60,11 @@ macro(configure_definitions) add_definitions(-DSYNERGY_PRODUCT_NAME="${PRODUCT_NAME}") endif() + if(SHOW_DEV_THANKS) + message(STATUS "Enabling dev thanks message") + add_definitions(-DSYNERGY_SHOW_DEV_THANKS=1) + endif() + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") message(STATUS "Disabling debug build") add_definitions(-DNDEBUG) @@ -108,6 +113,9 @@ macro(configure_options) # licensing is off by default to make life easier for contributors. set(DEFAULT_ENABLE_LICENSING OFF) + # by default, show the dev thanks message. + set(DEFAULT_SHOW_DEV_THANKS ON) + if("$ENV{SYNERGY_BUILD_MINIMAL}" STREQUAL "true") set(DEFAULT_BUILD_GUI OFF) set(DEFAULT_BUILD_INSTALLER OFF) @@ -129,11 +137,16 @@ macro(configure_options) set(DEFAULT_ENABLE_COVERAGE ON) endif() + if("$ENV{SYNERGY_SHOW_DEV_THANKS}" STREQUAL "false") + set(DEFAULT_SHOW_DEV_THANKS OFF) + endif() + option(BUILD_GUI "Build GUI" ${DEFAULT_BUILD_GUI}) option(BUILD_INSTALLER "Build installer" ${DEFAULT_BUILD_INSTALLER}) option(BUILD_TESTS "Build tests" ${DEFAULT_BUILD_TESTS}) option(BUILD_UNIFIED "Build unified binary" ${DEFAULT_BUILD_UNIFIED}) option(ENABLE_LICENSING "Enable licensing" ${DEFAULT_ENABLE_LICENSING}) option(ENABLE_COVERAGE "Enable test coverage" ${DEFAULT_ENABLE_COVERAGE}) + option(SHOW_DEV_THANKS "Show dev thanks message" ${DEFAULT_SHOW_DEV_THANKS}) endmacro() diff --git a/src/gui/src/AboutDialog.cpp b/src/gui/src/AboutDialog.cpp index 538e7fe7e..9b28eaea2 100644 --- a/src/gui/src/AboutDialog.cpp +++ b/src/gui/src/AboutDialog.cpp @@ -24,23 +24,14 @@ #include "OSXHelpers.h" #endif -#ifdef GIT_SHA_SHORT -const QString kVersionAppend = GIT_SHA_SHORT; -#else -const QString kVersionAppend; -#endif +#include "gui/version.h" -AboutDialog::AboutDialog(MainWindow *parent, const AppConfig &config) +AboutDialog::AboutDialog(MainWindow *parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint), Ui::AboutDialogBase() { setupUi(this); - QString version(SYNERGY_VERSION); - if (!kVersionAppend.isEmpty()) { - version.append(QString(" (%1)").arg(kVersionAppend)); - } - - m_pLabelSynergyVersion->setText(version); + m_pLabelSynergyVersion->setText(synergy::gui::version()); QString buildDateString = QString::fromLocal8Bit(__DATE__).simplified(); QDate buildDate = QLocale("en_US").toDate(buildDateString, "MMM d yyyy"); diff --git a/src/gui/src/AboutDialog.h b/src/gui/src/AboutDialog.h index 7bcbda5ea..78a52f46d 100644 --- a/src/gui/src/AboutDialog.h +++ b/src/gui/src/AboutDialog.h @@ -30,7 +30,7 @@ class QString; class AboutDialog : public QDialog, public Ui::AboutDialogBase { Q_OBJECT public: - AboutDialog(MainWindow *parent, const AppConfig &config); + explicit AboutDialog(MainWindow *parent); int exec() override; private: diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index e1cd4ea59..24c3a409a 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -23,6 +23,7 @@ #include "gui/LicenseHandler.h" #include "gui/constants.h" #include "gui/license_notices.h" +#include "gui/styles.h" #include "license/ProductEdition.h" #include "license/parse_serial_key.h" #include "ui_ActivationDialog.h" @@ -107,7 +108,6 @@ void ActivationDialog::accept() { } showSuccessDialog(); - m_pAppConfig->setActivationHasRun(true); QDialog::accept(); } @@ -122,6 +122,7 @@ void ActivationDialog::showResultDialog( QMessageBox::information( this, title, "Heads up, the serial key you entered was the same as last time."); + QDialog::accept(); break; case kInvalid: diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 6c640eced..021e9c13c 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -22,11 +22,14 @@ #include "ActivationDialog.h" #include "ServerConfigDialog.h" #include "SettingsDialog.h" +#include "gui/ConfigScopes.h" #include "gui/LicenseHandler.h" #include "gui/TlsFingerprint.h" #include "gui/VersionChecker.h" #include "gui/constants.h" #include "gui/license_notices.h" +#include "gui/messages.h" +#include "gui/styles.h" #include "license/License.h" #if defined(Q_OS_MAC) @@ -58,12 +61,6 @@ using namespace synergy::license; static const int kRetryDelay = 1000; static const int kDebugLogLevel = 1; -#ifdef SYNERGY_PRODUCT_NAME -const QString kProductName = SYNERGY_PRODUCT_NAME; -#else -const QString kProductName; -#endif - #if defined(Q_OS_MAC) static const char *const kLightIconFiles[] = { @@ -89,9 +86,10 @@ static const char *const kDefaultIconFiles[] = { ":/res/icons/16x16/synergy-transfering.png", ":/res/icons/16x16/synergy-disconnected.png"}; -MainWindow::MainWindow(AppConfig &appConfig) - : m_AppConfig(appConfig), - m_ServerConfig(5, 3, &m_AppConfig, this), +MainWindow::MainWindow(ConfigScopes &configScopes, AppConfig &appConfig) + : m_ConfigScopes(configScopes), + m_AppConfig(appConfig), + m_ServerConfig(appConfig, *this), m_ServerConnection(*this), m_ClientConnection(*this), m_TlsUtility(appConfig, m_LicenseHandler.license()), @@ -101,21 +99,13 @@ MainWindow::MainWindow(AppConfig &appConfig) setupControls(); connectSlots(); -#if defined(Q_OS_WIN) - - // TODO: only connect permenantly to ipc when switching to service mode. - // if switching from service to desktop, connect only to stop the service - // and don't retry. - m_IpcClient.connectToHost(); - -#endif - + // handled by `onCreated` emit created(); } MainWindow::~MainWindow() { try { - if (appConfig().processMode() == ProcessMode::kDesktop) { + if (m_AppConfig.processMode() == ProcessMode::kDesktop) { m_ExpectedRunningState = RuningState::Stopped; stopDesktop(); } @@ -132,18 +122,16 @@ MainWindow::~MainWindow() { void MainWindow::restoreWindow() { - const auto &config = appConfig(); - - const auto &size = config.mainWindowSize(); - if (size.has_value()) { + const auto &windowSize = m_AppConfig.mainWindowSize(); + if (windowSize.has_value()) { qDebug("restoring main window size"); - resize(size.value()); + resize(windowSize.value()); } - const auto &position = config.mainWindowPosition(); - if (position.has_value()) { + const auto &windowPosition = m_AppConfig.mainWindowPosition(); + if (windowPosition.has_value()) { qDebug("restoring main window position"); - move(position.value()); + move(windowPosition.value()); } // give the window chance to restore its size and position before the window @@ -159,10 +147,9 @@ void MainWindow::saveWindow() { } qDebug("saving window size and position"); - auto &config = appConfig(); - config.setMainWindowSize(size()); - config.setMainWindowPosition(pos()); - config.saveSettings(); + m_AppConfig.setMainWindowSize(size()); + m_AppConfig.setMainWindowPosition(pos()); + m_ConfigScopes.save(); } void MainWindow::setupControls() { @@ -210,7 +197,8 @@ void MainWindow::connectSlots() const { Qt::QueuedConnection); connect( - &m_AppConfig, &AppConfig::loaded, this, &MainWindow::onAppConfigLoaded); + &m_ConfigScopes, &ConfigScopes::saving, this, + &MainWindow::onConfigScopesSaving, Qt::DirectConnection); connect( &m_AppConfig, &AppConfig::tlsChanged, this, @@ -267,20 +255,30 @@ void MainWindow::connectSlots() const { &MainWindow::onWindowSaveTimerTimeout); } -void MainWindow::onAppAboutToQuit() { m_AppConfig.saveSettings(); } +void MainWindow::onAppAboutToQuit() { m_ConfigScopes.save(); } void MainWindow::onCreated() { - // it may seem a bit over-complicated to load the app config through a - // signal/slot, but there is a good reason: intentionality; we want to - // ensure that the ctor is fully executed before we start loading the - // app config. a signal is the clearest way to communicate this. - m_AppConfig.loadAllScopes(); - const auto serverEnabled = appConfig().serverGroupChecked(); - const auto clientEnabled = appConfig().clientGroupChecked(); - if (serverEnabled || clientEnabled) { - startCore(); +#if defined(Q_OS_WIN) + + // TODO: only connect permenantly to ipc when switching to service mode. + // if switching from service to desktop, connect only to stop the service + // and don't retry. + m_IpcClient.connectToHost(); + +#endif + + m_ConfigScopes.signalReady(); + + applyCloseToTray(); + + if (kLicensingEnabled && !m_AppConfig.serialKey().isEmpty()) { + m_LicenseHandler.changeSerialKey(m_AppConfig.serialKey()); } + + updateScreenName(); + applyConfig(); + restoreWindow(); } void MainWindow::onShown() { @@ -299,7 +297,7 @@ void MainWindow::onLicenseHandlerSerialKeyChanged(const QString &serialKey) { if (m_AppConfig.serialKey() != serialKey) { m_AppConfig.setSerialKey(serialKey); - m_AppConfig.saveSettings(); + m_ConfigScopes.save(); } } @@ -308,16 +306,7 @@ void MainWindow::onLicenseHandlerInvalidLicense() { showActivationDialog(); } -void MainWindow::onAppConfigLoaded() { - QApplication::setQuitOnLastWindowClosed(!m_AppConfig.closeToTray()); - if (!m_AppConfig.serialKey().isEmpty()) { - m_LicenseHandler.changeSerialKey(m_AppConfig.serialKey()); - } - - updateScreenName(); - applyConfig(); - restoreWindow(); -} +void MainWindow::onConfigScopesSaving() { m_ServerConfig.commit(); } void MainWindow::onAppConfigTlsChanged() { updateLocalFingerprint(); @@ -421,7 +410,7 @@ bool MainWindow::on_m_pActionSave_triggered() { QString fileName = QFileDialog::getSaveFileName(this, QString("Save configuration as...")); - if (!fileName.isEmpty() && !serverConfig().save(fileName)) { + if (!fileName.isEmpty() && !m_ServerConfig.save(fileName)) { QMessageBox::warning( this, QString("Save failed"), QString("Could not save configuration to file.")); @@ -432,8 +421,8 @@ bool MainWindow::on_m_pActionSave_triggered() { } void MainWindow::on_m_pActionAbout_triggered() { - AboutDialog dlg(this, appConfig()); - dlg.exec(); + AboutDialog about(this); + about.exec(); } void MainWindow::on_m_pActionHelp_triggered() { @@ -442,13 +431,14 @@ void MainWindow::on_m_pActionHelp_triggered() { void MainWindow::on_m_pActionSettings_triggered() { auto result = - SettingsDialog(this, appConfig(), m_LicenseHandler.license()).exec(); + SettingsDialog(this, m_AppConfig, m_LicenseHandler.license()).exec(); if (result == QDialog::Accepted) { - enableServer(appConfig().serverGroupChecked()); - enableClient(appConfig().clientGroupChecked()); - auto state = m_CoreState; - if ((state == CoreState::Connected) || (state == CoreState::Connecting) || - (state == CoreState::Listening)) { + m_ConfigScopes.save(); + + applyConfig(); + applyCloseToTray(); + + if (isCoreActive()) { restartCore(); } } @@ -460,6 +450,14 @@ void MainWindow::on_m_pButtonConfigureServer_clicked() { void MainWindow::on_m_pActivate_triggered() { showActivationDialog(); } +void MainWindow::on_m_pLineEditHostname_returnPressed() { + m_pButtonConnect->click(); +} + +void MainWindow::on_m_pLineEditClientIp_returnPressed() { + m_pButtonConnectToClient->click(); +} + void MainWindow::on_m_pButtonApply_clicked() { m_ClientConnection.setCheckConnection(true); restartCore(); @@ -477,13 +475,13 @@ void MainWindow::on_m_pLabelFingerprint_linkActivated(const QString &) { void MainWindow::on_m_pRadioGroupServer_clicked(bool) { enableServer(true); enableClient(false); - m_AppConfig.saveSettings(); + m_ConfigScopes.save(); } void MainWindow::on_m_pRadioGroupClient_clicked(bool) { enableClient(true); enableServer(false); - m_AppConfig.saveSettings(); + m_ConfigScopes.save(); } void MainWindow::on_m_pButtonConnect_clicked() { on_m_pButtonApply_clicked(); } @@ -527,7 +525,7 @@ void MainWindow::open() { setIcon(CoreState::Disconnected); }); - if (appConfig().autoHide()) { + if (m_AppConfig.autoHide()) { hide(); } else { showNormal(); @@ -538,8 +536,8 @@ void MainWindow::open() { // only start if user has previously started. this stops the gui from // auto hiding before the user has configured synergy (which of course // confuses first time users, who think synergy has crashed). - if (appConfig().startedBefore() && - appConfig().processMode() == ProcessMode::kDesktop) { + if (m_AppConfig.startedBefore() && + m_AppConfig.processMode() == ProcessMode::kDesktop) { startCore(); } } @@ -580,20 +578,24 @@ void MainWindow::createMenuBar() { } void MainWindow::applyConfig() { - enableServer(appConfig().serverGroupChecked()); - enableClient(appConfig().clientGroupChecked()); + enableServer(m_AppConfig.serverGroupChecked()); + enableClient(m_AppConfig.clientGroupChecked()); - m_pLineEditHostname->setText(appConfig().serverHostname()); - m_pLineEditClientIp->setText(serverConfig().getClientAddress()); + m_pLineEditHostname->setText(m_AppConfig.serverHostname()); + m_pLineEditClientIp->setText(m_ServerConfig.getClientAddress()); +} + +void MainWindow::applyCloseToTray() const { + QApplication::setQuitOnLastWindowClosed(!m_AppConfig.closeToTray()); } void MainWindow::saveSettings() { - appConfig().setServerGroupChecked(m_pRadioGroupServer->isChecked()); - appConfig().setClientGroupChecked(m_pRadioGroupClient->isChecked()); - appConfig().setServerHostname(m_pLineEditHostname->text()); - serverConfig().setClientAddress(m_pLineEditClientIp->text()); + m_AppConfig.setServerGroupChecked(m_pRadioGroupServer->isChecked()); + m_AppConfig.setClientGroupChecked(m_pRadioGroupClient->isChecked()); + m_AppConfig.setServerHostname(m_pLineEditHostname->text()); + m_ServerConfig.setClientAddress(m_pLineEditClientIp->text()); - appConfig().scopes().saveAll(); + m_ConfigScopes.save(); } void MainWindow::setIcon(CoreState state) const { @@ -622,19 +624,22 @@ void MainWindow::setIcon(CoreState state) const { } void MainWindow::appendLogInfo(const QString &text) { + qInfo("%s", text.toStdString().c_str()); + processCoreLogLine(getTimeStamp() + " INFO: " + text); } void MainWindow::appendLogDebug(const QString &text) { qDebug("%s", text.toStdString().c_str()); - if (appConfig().logLevel() >= kDebugLogLevel) { + if (m_AppConfig.logLevel() >= kDebugLogLevel) { processCoreLogLine(getTimeStamp() + " DEBUG: " + text); } } void MainWindow::appendLogError(const QString &text) { - onIpcClientReadLogLine(getTimeStamp() + " ERROR: " + text); + qCritical("%s", text.toStdString().c_str()); + processCoreLogLine(getTimeStamp() + " ERROR: " + text); } void MainWindow::processCoreLogLine(const QString &text) { @@ -644,7 +649,7 @@ void MainWindow::processCoreLogLine(const QString &text) { } // only start if there is no active service running - if (line.contains("service status: idle") && appConfig().startedBefore()) { + if (line.contains("service status: idle") && m_AppConfig.startedBefore()) { startCore(); } @@ -689,15 +694,11 @@ void MainWindow::checkConnected(const QString &line) { if (line.contains("connected to server") || line.contains("has connected")) { setCoreState(CoreState::Connected); - if (!appConfig().startedBefore() && isVisible()) { - QMessageBox::information( - this, "Synergy", - QString("Synergy is now connected. You can close the " - "config window and Synergy will remain connected in " - "the background.")); - - appConfig().setStartedBefore(true); + if (isVisible()) { + showFirstRunMessage(); + showDevThanksMessage(); } + } else if (line.contains("started server")) { setCoreState(CoreState::Listening); } else if ( @@ -803,8 +804,53 @@ void MainWindow::showEvent(QShowEvent *event) { emit shown(); } +void MainWindow::closeEvent(QCloseEvent *event) { + if (!m_AppConfig.closeToTray()) { + return; + } + + qDebug("window will hide to tray"); + if (!m_AppConfig.showCloseReminder()) { + return; + } + + messages::showCloseReminder(this); + m_AppConfig.setShowCloseReminder(false); + m_ConfigScopes.save(); +} + +void MainWindow::showFirstRunMessage() { + if (m_AppConfig.startedBefore()) { + return; + } + + m_AppConfig.setStartedBefore(true); + m_ConfigScopes.save(); + + const auto isServer = coreMode() == CoreMode::Server; + messages::showFirstRunMessage( + this, m_AppConfig.closeToTray(), m_AppConfig.enableService(), isServer); +} + +void MainWindow::showDevThanksMessage() { + if (!m_AppConfig.showDevThanks()) { + qDebug("skipping dev thanks message, disabled in settings"); + return; + } + + if (kLicensingEnabled) { + qDebug("licensing enabled, skipping dev thanks message"); + return; + } + + m_AppConfig.setShowDevThanks(false); + m_ConfigScopes.save(); + + messages::showDevThanks(this, kProductName); +} + void MainWindow::startCore() { - appendLogDebug("starting core process"); + appendLogInfo(QString("starting core %1 process").arg(coreModeString())); saveSettings(); @@ -828,11 +874,11 @@ void MainWindow::startCore() { args << "-f" << "--no-tray" - << "--debug" << appConfig().logLevelText(); + << "--debug" << m_AppConfig.logLevelText(); - args << "--name" << appConfig().screenName(); + args << "--name" << m_AppConfig.screenName(); - ProcessMode mode = appConfig().processMode(); + ProcessMode mode = m_AppConfig.processMode(); if (mode == ProcessMode::kDesktop) { m_pCoreProcess = std::make_unique(this); @@ -850,7 +896,7 @@ void MainWindow::startCore() { // unnecessary restarts when synergy was started elevated or // when it is not allowed to elevate. In these cases restarting // the server is fruitless. - if (appConfig().elevateMode() == ElevateAsNeeded) { + if (m_AppConfig.elevateMode() == ElevateAsNeeded) { args << "--stop-on-desk-switch"; } #endif @@ -896,10 +942,6 @@ void MainWindow::startCore() { if (!m_pLogOutput->toPlainText().isEmpty()) onIpcClientReadLogLine(""); - appendLogInfo( - "starting " + - QString(coreMode() == CoreMode::Server ? "server" : "client")); - if ((coreMode() == CoreMode::Client && !clientArgs(args, app)) || (coreMode() == CoreMode::Server && !serverArgs(args, app))) { onActionStopCoreTriggered(); @@ -918,15 +960,14 @@ void MainWindow::startCore() { &MainWindow::onCoreProcessReadyReadStandardError); } - // show command if debug log level... - if (appConfig().logLevel() >= 4) { + if (m_AppConfig.logLevel() >= kDebugLogLevel) { appendLogInfo(QString("command: %1 %2").arg(app, args.join(" "))); } - appendLogInfo("log level: " + appConfig().logLevelText()); + appendLogInfo("log level: " + m_AppConfig.logLevelText()); - if (appConfig().logToFile()) - appendLogInfo("log file: " + appConfig().logFilename()); + if (m_AppConfig.logToFile()) + appendLogInfo("log file: " + m_AppConfig.logFilename()); if (mode == ProcessMode::kDesktop) { m_pCoreProcess->start(app, args); @@ -944,12 +985,12 @@ void MainWindow::startCore() { } } else if (mode == ProcessMode::kService) { QString command(app + " " + args.join(" ")); - m_IpcClient.sendCommand(command, appConfig().elevateMode()); + m_IpcClient.sendCommand(command, m_AppConfig.elevateMode()); } } bool MainWindow::clientArgs(QStringList &args, QString &app) { - app = appPath(appConfig().coreClientName()); + app = appPath(m_AppConfig.coreClientName()); if (!QFile::exists(app)) { show(); @@ -959,22 +1000,22 @@ bool MainWindow::clientArgs(QStringList &args, QString &app) { return false; } - if (appConfig().logToFile()) { - appConfig().persistLogDir(); - args << "--log" << appConfig().logFilename(); + if (m_AppConfig.logToFile()) { + m_AppConfig.persistLogDir(); + args << "--log" << m_AppConfig.logFilename(); } - if (appConfig().languageSync()) { + if (m_AppConfig.languageSync()) { args << "--sync-language"; } - if (appConfig().invertScrollDirection()) { + if (m_AppConfig.invertScrollDirection()) { args << "--invert-scroll"; } - if (appConfig().invertConnection()) { + if (m_AppConfig.invertConnection()) { args << "--host"; - args << ":" + QString::number(appConfig().port()); + args << ":" + QString::number(m_AppConfig.port()); } else { if (m_pLineEditHostname->text().isEmpty()) { show(); @@ -994,7 +1035,7 @@ bool MainWindow::clientArgs(QStringList &args, QString &app) { hostName.push_back(']'); } } - args << hostName + ":" + QString::number(appConfig().port()); + args << hostName + ":" + QString::number(m_AppConfig.port()); } return true; @@ -1002,8 +1043,8 @@ bool MainWindow::clientArgs(QStringList &args, QString &app) { QString MainWindow::configFilename() { QString configFullPath; - if (appConfig().useExternalConfig()) { - configFullPath = appConfig().configFile(); + if (m_AppConfig.useExternalConfig()) { + configFullPath = m_AppConfig.configFile(); } else { QStringList errors; for (auto path : @@ -1023,7 +1064,7 @@ QString MainWindow::configFilename() { continue; } - serverConfig().save(configFile); + m_ServerConfig.save(configFile); configFile.close(); configFullPath = configFile.fileName(); @@ -1040,7 +1081,7 @@ QString MainWindow::configFilename() { } QString MainWindow::address() const { - QString i = appConfig().networkInterface(); + QString i = m_AppConfig.networkInterface(); // if interface is IPv6 - ensure that ip is in square brackets if (i.count(':') > 1) { if (i[0] != '[') { @@ -1050,7 +1091,7 @@ QString MainWindow::address() const { i.push_back(']'); } } - return (!i.isEmpty() ? i : "") + ":" + QString::number(appConfig().port()); + return (!i.isEmpty() ? i : "") + ":" + QString::number(m_AppConfig.port()); } QString MainWindow::appPath(const QString &name) const { @@ -1059,7 +1100,7 @@ QString MainWindow::appPath(const QString &name) const { } bool MainWindow::serverArgs(QStringList &args, QString &app) { - app = appPath(appConfig().coreServerName()); + app = appPath(m_AppConfig.coreServerName()); if (!QFile::exists(app)) { QMessageBox::warning( @@ -1068,17 +1109,17 @@ bool MainWindow::serverArgs(QStringList &args, QString &app) { return false; } - if (appConfig().invertConnection() && m_pLineEditClientIp->text().isEmpty()) { + if (m_AppConfig.invertConnection() && m_pLineEditClientIp->text().isEmpty()) { QMessageBox::warning( this, QString("Client IP address or name is empty"), QString("Please fill in a client IP address or name.")); return false; } - if (appConfig().logToFile()) { - appConfig().persistLogDir(); + if (m_AppConfig.logToFile()) { + m_AppConfig.persistLogDir(); - args << "--log" << appConfig().logFilename(); + args << "--log" << m_AppConfig.logFilename(); } QString configFilename = this->configFilename(); @@ -1089,8 +1130,8 @@ bool MainWindow::serverArgs(QStringList &args, QString &app) { args << "-c" << configFilename << "--address" << address(); appendLogInfo("config file: " + configFilename); - if (kLicensingEnabled && !appConfig().serialKey().isEmpty()) { - args << "--serial-key" << appConfig().serialKey(); + if (kLicensingEnabled && !m_AppConfig.serialKey().isEmpty()) { + args << "--serial-key" << m_AppConfig.serialKey(); } return true; @@ -1101,9 +1142,9 @@ void MainWindow::stopCore() { m_ExpectedRunningState = RuningState::Stopped; - if (appConfig().processMode() == ProcessMode::kService) { + if (m_AppConfig.processMode() == ProcessMode::kService) { stopService(); - } else if (appConfig().processMode() == ProcessMode::kDesktop) { + } else if (m_AppConfig.processMode() == ProcessMode::kDesktop) { stopDesktop(); } @@ -1115,7 +1156,7 @@ void MainWindow::stopCore() { void MainWindow::stopService() { // send empty command to stop service from laucning anything. - m_IpcClient.sendCommand("", appConfig().elevateMode()); + m_IpcClient.sendCommand("", m_AppConfig.elevateMode()); } void MainWindow::stopDesktop() { @@ -1229,8 +1270,31 @@ void MainWindow::setVisible(bool visible) { } MainWindow::CoreMode MainWindow::coreMode() const { - auto isClient = m_pRadioGroupClient->isChecked(); - return isClient ? CoreMode::Client : CoreMode::Server; + using enum CoreMode; + + auto serverChecked = m_pRadioGroupServer->isChecked(); + auto clientChecked = m_pRadioGroupClient->isChecked(); + + if (serverChecked) { + return Server; + } else if (clientChecked) { + return Client; + } else { + return None; + } +} + +QString MainWindow::coreModeString() const { + using enum CoreMode; + + switch (coreMode()) { + case Server: + return "server"; + case Client: + return "client"; + default: + qFatal("invalid core mode"); + } } QString MainWindow::getIPAddresses() const { @@ -1291,22 +1355,22 @@ void MainWindow::updateLocalFingerprint() { } } -LicenseHandler &MainWindow::licenseHandler() { return m_LicenseHandler; } - -void MainWindow::updateWindowTitle() { +QString MainWindow::productName() const { if (kLicensingEnabled) { - setWindowTitle(m_LicenseHandler.productName()); + return m_LicenseHandler.productName(); + } else if (!kProductName.isEmpty()) { + return kProductName; } else { - setWindowTitle(kProductName); + qFatal("product name not set"); } } +void MainWindow::updateWindowTitle() { setWindowTitle(productName()); } + void MainWindow::autoAddScreen(const QString name) { if (m_ActivationDialogRunning) { - // TODO: refactor this code - // add this screen to the pending list and check this list until - // users finish activation dialog + // add this screen to the pending list if the activation dialog is running. m_PendingClientNames.append(name); return; } @@ -1316,7 +1380,7 @@ void MainWindow::autoAddScreen(const QString name) { switch (r) { case kAutoAddScreenManualServer: showConfigureServer(QString("Please add the server (%1) to the grid.") - .arg(appConfig().screenName())); + .arg(m_AppConfig.screenName())); break; case kAutoAddScreenManualClient: @@ -1328,18 +1392,18 @@ void MainWindow::autoAddScreen(const QString name) { } } -void MainWindow::showConfigureServer(const QString &message) { +bool MainWindow::isCoreActive() const { using enum CoreState; - ServerConfigDialog dlg(this, serverConfig(), appConfig()); - dlg.message(message); - auto result = dlg.exec(); + auto state = m_CoreState; + return (state == Connected) || (state == Connecting) || (state == Listening); +} - if (result == QDialog::Accepted) { - auto state = m_CoreState; - if ((state == Connected) || (state == Connecting) || (state == Listening)) { - restartCore(); - } +void MainWindow::showConfigureServer(const QString &message) { + ServerConfigDialog dialog(this, serverConfig(), m_AppConfig); + dialog.message(message); + if ((dialog.exec() == QDialog::Accepted) && isCoreActive()) { + restartCore(); } } @@ -1354,6 +1418,11 @@ int MainWindow::showActivationDialog() { int result = activationDialog.exec(); m_ActivationDialogRunning = false; + if (result == QDialog::Accepted) { + m_AppConfig.setActivationHasRun(true); + m_ConfigScopes.save(); + } + if (!m_PendingClientNames.empty()) { foreach (const QString &name, m_PendingClientNames) { autoAddScreen(name); @@ -1391,23 +1460,33 @@ void MainWindow::updateScreenName() { m_pLabelComputerName->setText( QString("This computer's name: %1 " R"((change))") - .arg(appConfig().screenName()) + .arg(m_AppConfig.screenName()) .arg(kColorSecondary)); - serverConfig().updateServerName(); + m_ServerConfig.updateServerName(); } void MainWindow::enableServer(bool enable) { + qDebug(enable ? "server enabled" : "server disabled"); m_AppConfig.setServerGroupChecked(enable); m_pRadioGroupServer->setChecked(enable); m_pWidgetServer->setEnabled(enable); m_pWidgetServerInverse->setVisible(m_AppConfig.invertConnection()); - m_pButtonToggleStart->setEnabled(true); + + if (enable) { + m_pButtonToggleStart->setEnabled(true); + m_pActionStartCore->setEnabled(true); + } } void MainWindow::enableClient(bool enable) { + qDebug(enable ? "client enabled" : "client disabled"); m_AppConfig.setClientGroupChecked(enable); m_pRadioGroupClient->setChecked(enable); m_pWidgetClient->setEnabled(enable); m_pWidgetClient->setVisible(!m_AppConfig.invertConnection()); - m_pButtonToggleStart->setEnabled(true); + + if (enable) { + m_pButtonToggleStart->setEnabled(true); + m_pActionStartCore->setEnabled(true); + } } diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index 10ddcf4b4..f995e7020 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -79,22 +79,23 @@ public: PendingRetry }; - enum class CoreMode { Client, Server }; + enum class CoreMode { None, Client, Server }; enum class LogLevel { Error, Info }; enum class RuningState { Started, Stopped }; public: - explicit MainWindow(AppConfig &appConfig); + explicit MainWindow( + synergy::gui::ConfigScopes &configScopes, AppConfig &appConfig); ~MainWindow() override; void setVisible(bool visible) override; CoreMode coreMode() const; + QString coreModeString() const; QString address() const; QString appPath(const QString &name) const; void open(); ServerConfig &serverConfig() { return m_ServerConfig; } void autoAddScreen(const QString name); - LicenseHandler &licenseHandler(); int showActivationDialog(); void appendLogInfo(const QString &text); void appendLogDebug(const QString &text); @@ -110,7 +111,7 @@ public slots: private slots: void onCreated(); void onShown(); - void onAppConfigLoaded(); + void onConfigScopesSaving(); void onAppConfigTlsChanged(); void onAppConfigScreenNameChanged(); void onAppConfigInvertConnection(); @@ -142,15 +143,17 @@ private slots: void on_m_pActionHelp_triggered(); void on_m_pActionSettings_triggered(); void on_m_pActivate_triggered(); + void on_m_pLineEditHostname_returnPressed(); + void on_m_pLineEditClientIp_returnPressed(); private: - QSettings &settings() { return *appConfig().scopes().currentSettings(); } AppConfig &appConfig() { return m_AppConfig; } AppConfig const &appConfig() const { return m_AppConfig; } void createMenuBar(); void createStatusBar(); void createTrayIcon(); void applyConfig(); + void applyCloseToTray() const; void setIcon(CoreState state) const; void setCoreState(CoreState state); bool checkForApp(int which, QString &app); @@ -172,6 +175,7 @@ private: QString getTimeStamp() const; void restartCore(); void showEvent(QShowEvent *) override; + void closeEvent(QCloseEvent *event) override; void secureSocket(bool secureSocket); void windowStateChanged(); void connectSlots() const; @@ -183,6 +187,7 @@ private: void updateScreenName(); void saveSettings(); QString configFilename(); + bool isCoreActive() const; void showConfigureServer(const QString &message); void showConfigureServer() { showConfigureServer(""); } void showLicenseNotice(); @@ -192,6 +197,9 @@ private: void setupControls(); void resizeEvent(QResizeEvent *event) override; void moveEvent(QMoveEvent *event) override; + void showFirstRunMessage(); + void showDevThanksMessage(); + QString productName() const; #ifdef Q_OS_MAC void checkOSXNotification(const QString &line); @@ -216,9 +224,10 @@ private: bool m_SecureSocket = false; QString m_SecureSocketVersion = ""; bool m_SaveWindow = false; - - AppConfig &m_AppConfig; LicenseHandler m_LicenseHandler; + + synergy::gui::ConfigScopes &m_ConfigScopes; + AppConfig &m_AppConfig; ServerConfig m_ServerConfig; ServerConnection m_ServerConnection; ClientConnection m_ClientConnection; diff --git a/src/gui/src/MainWindowBase.ui b/src/gui/src/MainWindowBase.ui index 12fa3eb35..0f61c718a 100644 --- a/src/gui/src/MainWindowBase.ui +++ b/src/gui/src/MainWindowBase.ui @@ -103,7 +103,7 @@ - + 0 @@ -268,7 +268,7 @@ - + 0 @@ -599,6 +599,9 @@ + + false + &Start diff --git a/src/gui/src/QSynergyApplication.cpp b/src/gui/src/QSynergyApplication.cpp index 76e9d5695..4b693471e 100644 --- a/src/gui/src/QSynergyApplication.cpp +++ b/src/gui/src/QSynergyApplication.cpp @@ -24,18 +24,4 @@ #include QSynergyApplication::QSynergyApplication(int &argc, char **argv) - : QApplication(argc, argv) { - - // Setting the style to 'Fusion' seems to fix issues such as text being - // rendered as black on black. This may not be the style we want long-term - // but it does fix the style issues for now. - setStyle("Fusion"); -} - -void QSynergyApplication::commitData(const QSessionManager &) const { - foreach (QWidget *widget, topLevelWidgets()) { - MainWindow *mainWindow = qobject_cast(widget); - if (mainWindow) - mainWindow->saveSettings(); - } -} + : QApplication(argc, argv) {} diff --git a/src/gui/src/QSynergyApplication.h b/src/gui/src/QSynergyApplication.h index be4a47dea..73bfd8599 100644 --- a/src/gui/src/QSynergyApplication.h +++ b/src/gui/src/QSynergyApplication.h @@ -26,6 +26,4 @@ class QSynergyApplication : public QApplication { public: QSynergyApplication(int &argc, char **argv); ~QSynergyApplication() override = default; - - void commitData(const QSessionManager &manager) const; }; diff --git a/src/gui/src/ScreenSettingsDialog.cpp b/src/gui/src/ScreenSettingsDialog.cpp index 9dbb7b581..0f5cde0a6 100644 --- a/src/gui/src/ScreenSettingsDialog.cpp +++ b/src/gui/src/ScreenSettingsDialog.cpp @@ -18,7 +18,7 @@ #include "ScreenSettingsDialog.h" #include "Screen.h" -#include "gui/constants.h" +#include "gui/styles.h" #include "validators/AliasValidator.h" #include "validators/ScreenNameValidator.h" #include "validators/ValidationError.h" diff --git a/src/gui/src/ServerConfig.cpp b/src/gui/src/ServerConfig.cpp index a2abe10f4..188ea1bbf 100644 --- a/src/gui/src/ServerConfig.cpp +++ b/src/gui/src/ServerConfig.cpp @@ -21,8 +21,6 @@ #include "AddClientDialog.h" #include "Hotkey.h" #include "MainWindow.h" -#include "gui/ConfigScopes.h" -#include "gui/constants.h" #include #include @@ -44,23 +42,14 @@ static const struct { const int serverDefaultIndex = 7; ServerConfig::ServerConfig( - int numColumns, int numRows, AppConfig *appConfig, MainWindow *mainWindow) - : m_pAppConfig(appConfig), - m_pMainWindow(mainWindow), - m_Screens(numColumns), - m_NumColumns(numColumns), - m_NumRows(numRows), + AppConfig &appConfig, MainWindow &mainWindow, int columns, int rows) + : m_pAppConfig(&appConfig), + m_pMainWindow(&mainWindow), + m_Screens(columns), + m_Columns(columns), + m_Rows(rows), m_ClipboardSharingSize(defaultClipboardSharingSize()) { - appConfig->scopes().registerReceiver(this); -} - -ServerConfig::~ServerConfig() { - try { - ServerConfig::saveSettings(); - } catch (const std::exception &e) { - qDebug() << e.what(); - m_pMainWindow->appendLogError(e.what()); - } + recall(); } bool ServerConfig::save(const QString &fileName) const { @@ -75,8 +64,8 @@ bool ServerConfig::save(const QString &fileName) const { } bool ServerConfig::operator==(const ServerConfig &sc) const { - return m_Screens == sc.m_Screens && m_NumColumns == sc.m_NumColumns && - m_NumRows == sc.m_NumRows && m_HasHeartbeat == sc.m_HasHeartbeat && + return m_Screens == sc.m_Screens && m_Columns == sc.m_Columns && + m_Rows == sc.m_Rows && m_HasHeartbeat == sc.m_HasHeartbeat && m_Heartbeat == sc.m_Heartbeat && m_RelativeMouseMoves == sc.m_RelativeMouseMoves && m_Win32KeepForeground == sc.m_Win32KeepForeground && @@ -99,7 +88,7 @@ void ServerConfig::save(QFile &file) const { outStream << *this; } -void ServerConfig::init() { +void ServerConfig::setupScreens() { switchCorners().clear(); screens().clear(); hotkeys().clear(); @@ -114,7 +103,9 @@ void ServerConfig::init() { addScreen(Screen()); } -void ServerConfig::saveSettings() { +void ServerConfig::commit() { + qDebug("committing server config"); + settings().beginGroup("internalConfig"); settings().remove(""); @@ -163,7 +154,9 @@ void ServerConfig::saveSettings() { settings().endGroup(); } -void ServerConfig::loadSettings() { +void ServerConfig::recall() { + qDebug("recalling server config"); + settings().beginGroup("internalConfig"); setNumColumns(settings().value("numColumns", 5).toInt()); @@ -171,7 +164,7 @@ void ServerConfig::loadSettings() { // we need to know the number of columns and rows before we can set up // ourselves - init(); + setupScreens(); haveHeartbeat(settings().value("hasHeartbeat", false).toBool()); setHeartbeat(settings().value("heartbeat", 5000).toInt()); @@ -398,7 +391,6 @@ int ServerConfig::autoAddScreen(const QString name) { return kAutoAddScreenManualClient; } - saveSettings(); return kAutoAddScreenOk; } @@ -410,7 +402,6 @@ void ServerConfig::updateServerName() { for (auto &screen : screens()) { if (screen.isServer()) { screen.setName(m_pAppConfig->screenName()); - saveSettings(); break; } } @@ -553,5 +544,5 @@ QString ServerConfig::getClientAddress() const { } QSettings &ServerConfig::settings() { - return *m_pAppConfig->scopes().currentSettings(); + return *m_pAppConfig->scopes().activeSettings(); } diff --git a/src/gui/src/ServerConfig.h b/src/gui/src/ServerConfig.h index 320c6ddd5..bb8dd1ca3 100644 --- a/src/gui/src/ServerConfig.h +++ b/src/gui/src/ServerConfig.h @@ -21,10 +21,12 @@ #include "Hotkey.h" #include "ScreenConfig.h" #include "ScreenList.h" -#include "gui/CommonConfig.h" #include +const auto kDefaultColumns = 5; +const auto kDefaultRows = 3; + class QTextStream; class QSettings; class QString; @@ -33,7 +35,7 @@ class ServerConfigDialog; class MainWindow; class AppConfig; -class ServerConfig : public ScreenConfig, public synergy::gui::CommonConfig { +class ServerConfig : public ScreenConfig { friend class ServerConfigDialog; friend class ServerConnection; friend QTextStream & @@ -41,15 +43,15 @@ class ServerConfig : public ScreenConfig, public synergy::gui::CommonConfig { public: ServerConfig( - int numColumns, int numRows, AppConfig *appConfig, - MainWindow *mainWindow); - ~ServerConfig() override; + AppConfig &appConfig, MainWindow &mainWindow, + int columns = kDefaultColumns, int rows = kDefaultRows); + ~ServerConfig() = default; bool operator==(const ServerConfig &sc) const; const ScreenList &screens() const { return m_Screens; } - int numColumns() const { return m_NumColumns; } - int numRows() const { return m_NumRows; } + int numColumns() const { return m_Columns; } + int numRows() const { return m_Rows; } bool hasHeartbeat() const { return m_HasHeartbeat; } int heartbeat() const { return m_Heartbeat; } bool relativeMouseMoves() const { return m_RelativeMouseMoves; } @@ -68,8 +70,7 @@ public: size_t clipboardSharingSize() const { return m_ClipboardSharingSize; } static size_t defaultClipboardSharingSize(); - void saveSettings() override; - void loadSettings() override; + void commit(); bool save(const QString &fileName) const; void save(QFile &file) const; int numScreens() const; @@ -84,13 +85,15 @@ public: QString getClientAddress() const; void setClientAddress(const QString &address); -protected: +private: + void recall(); + void setupScreens(); QSettings &settings(); ScreenList &screens() { return m_Screens; } void setScreens(const ScreenList &screens) { m_Screens = screens; } void addScreen(const Screen &screen) { m_Screens.append(screen); } - void setNumColumns(int n) { m_NumColumns = n; } - void setNumRows(int n) { m_NumRows = n; } + void setNumColumns(int n) { m_Columns = n; } + void setNumRows(int n) { m_Rows = n; } void haveHeartbeat(bool on) { m_HasHeartbeat = on; } void setHeartbeat(int val) { m_Heartbeat = val; } void setRelativeMouseMoves(bool on) { m_RelativeMouseMoves = on; } @@ -109,11 +112,7 @@ protected: size_t setClipboardSharingSize(size_t size); QList &switchCorners() { return m_SwitchCorners; } HotkeyList &hotkeys() { return m_Hotkeys; } - - void init(); int adjacentScreenIndex(int idx, int deltaColumn, int deltaRow) const; - -private: bool findScreenName(const QString &name, int &index); bool fixNoServer(const QString &name, int &index); int showAddClientDialog(const QString &clientName); @@ -139,8 +138,8 @@ private: AppConfig *m_pAppConfig; MainWindow *m_pMainWindow; ScreenList m_Screens; - int m_NumColumns; - int m_NumRows; + int m_Columns; + int m_Rows; size_t m_ClipboardSharingSize; }; diff --git a/src/gui/src/ServerConfigDialogBase.ui b/src/gui/src/ServerConfigDialogBase.ui index c987799c9..e8eb23513 100644 --- a/src/gui/src/ServerConfigDialogBase.ui +++ b/src/gui/src/ServerConfigDialogBase.ui @@ -332,7 +332,7 @@ - Advanced server settings + Advanced @@ -839,289 +839,284 @@ - Advanced config + Config file + + 15 + + + 120 + + + 30 + + + 120 + + + + + + 18 + true + + + + Core server config file + + + Qt::PlainText + + + - + + + Use a Core server config file to create complex screen layouts that are not possible with the simple grid-based screen layout editor. + +[How to write a config file](https://symless.com/synergy/help/creating-text-config-files) + +Enabling this setting will disable the server config GUI. + + + Qt::MarkdownText + + + Qt::AlignCenter + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Maximum + + + + 20 + 30 + + + + + + + + 0 + + + 0 + - + - Qt::Vertical + Qt::Horizontal + + + QSizePolicy::Expanding - 20 - 40 + 40 + 20 - - - <html><head/><body><p style="text-align: center; font-family: Arial; font-size: 18px; line-height: 25px;">Config override</p></body></html> + + + + 0 + 0 + - - Qt::RichText + + + 300 + 0 + + + + Use Core server config file - + - Qt::Vertical + Qt::Horizontal - QSizePolicy::Minimum + QSizePolicy::Expanding - 20 - 5 - - - - - - - - <html><head/><body><p style="text-align: center; font-size: 13px; font-family: Arial; line-hegiht: 18px;">If you have a config file and want to override all setttings with this file.</p></body></html> - - - Qt::RichText - - - - - - - Qt::Vertical - - - QSizePolicy::Maximum - - - - 20 - 30 - - - - - - - - 0 - - - 0 - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 300 - 0 - - - - Use configuration file - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 40 - 20 - - - - - - - - - - 40 - - - 0 - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 40 - 20 - - - - - - - - false - - - - 0 - 0 - - - - - 96 - 0 - - - - Config file path - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - - - - - false - - - - 0 - 0 - - - - - 180 - 0 - - - - - - - - false - - - - 0 - 0 - - - - - 0 - 0 - - - - PointingHandCursor - - - - - - - :/res/icons/64x64/folder.png:/res/icons/64x64/folder.png - - - - 20 - 13 - - - - true - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 + 40 + 20 + + + + 40 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + false + + + + 0 + 0 + + + + + 96 + 0 + + + + Config file path + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + false + + + + 0 + 0 + + + + + 180 + 0 + + + + + + + + false + + + + 0 + 0 + + + + + 0 + 0 + + + + PointingHandCursor + + + + + + + :/res/icons/64x64/folder.png:/res/icons/64x64/folder.png + + + + 20 + 13 + + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/src/gui/src/ServerConnection.cpp b/src/gui/src/ServerConnection.cpp index 7d769bd02..54342e623 100644 --- a/src/gui/src/ServerConnection.cpp +++ b/src/gui/src/ServerConnection.cpp @@ -69,10 +69,10 @@ void ServerConnection::addClient(const QString &clientName) { } void ServerConnection::configureClient(const QString &clientName) { - ServerConfigDialog dlg( + ServerConfigDialog dialog( &m_parent, m_parent.serverConfig(), m_parent.appConfig()); - if (dlg.addClient(clientName) && dlg.exec() == QDialog::Accepted) { + if (dialog.addClient(clientName) && dialog.exec() == QDialog::Accepted) { m_parent.restartCore(); } } diff --git a/src/gui/src/SettingsDialog.cpp b/src/gui/src/SettingsDialog.cpp index e12cb05ce..19e8ed889 100644 --- a/src/gui/src/SettingsDialog.cpp +++ b/src/gui/src/SettingsDialog.cpp @@ -53,7 +53,7 @@ SettingsDialog::SettingsDialog( m_pMainWindow = dynamic_cast(parent); loadFromConfig(); - m_isSystemAtStart = appConfig().isSystemScoped(); + m_wasOriginallySystemScope = appConfig().isActiveScopeSystem(); updateControlsEnabled(); const auto &serveConfig = m_pMainWindow->serverConfig(); @@ -86,18 +86,17 @@ void SettingsDialog::accept() { appConfig().setTlsEnabled(m_pCheckBoxEnableCrypto->isChecked()); appConfig().setLanguageSync(m_pCheckBoxLanguageSync->isChecked()); appConfig().setInvertScrollDirection(m_pCheckBoxScrollDirection->isChecked()); - appConfig().setServiceEnabled(m_pCheckBoxServiceEnabled->isChecked()); + appConfig().setEnableService(m_pCheckBoxServiceEnabled->isChecked()); appConfig().setCloseToTray(m_pCheckBoxCloseToTray->isChecked()); appConfig().setInvertConnection(m_pInvertConnection->isChecked()); - appConfig().saveSettings(); QDialog::accept(); } void SettingsDialog::reject() { - // We should restore scope at start if the user rejects changes. - if (appConfig().isSystemScoped() != m_isSystemAtStart) { - appConfig().setLoadFromSystemScope(m_isSystemAtStart); + // restore original system scope value on reject. + if (appConfig().isActiveScopeSystem() != m_wasOriginallySystemScope) { + appConfig().setLoadFromSystemScope(m_wasOriginallySystemScope); } QDialog::reject(); @@ -117,10 +116,10 @@ void SettingsDialog::loadFromConfig() { m_pCheckBoxEnableCrypto->setChecked(m_appConfig.tlsEnabled()); m_pCheckBoxLanguageSync->setChecked(m_appConfig.languageSync()); m_pCheckBoxScrollDirection->setChecked(m_appConfig.invertScrollDirection()); - m_pCheckBoxServiceEnabled->setChecked(m_appConfig.serviceEnabled()); + m_pCheckBoxServiceEnabled->setChecked(m_appConfig.enableService()); m_pCheckBoxCloseToTray->setChecked(m_appConfig.closeToTray()); - if (m_appConfig.isSystemScoped()) { + if (m_appConfig.isActiveScopeSystem()) { m_pRadioSystemScope->setChecked(true); } else { m_pRadioUserScope->setChecked(true); @@ -147,14 +146,15 @@ void SettingsDialog::updateTlsControls() { } void SettingsDialog::updateTlsControlsEnabled() { + bool writable = appConfig().isActiveScopeWritable(); auto clientMode = appConfig().clientGroupChecked(); auto tlsAvailable = m_tlsUtility.isAvailableAndEnabled(); auto tlsChecked = m_pCheckBoxEnableCrypto->isChecked(); - auto enabled = !clientMode && tlsAvailable && tlsChecked; + auto enabled = writable && !clientMode && tlsAvailable && tlsChecked; qDebug( - "tls enabled=%d, client=%d, available=%d, checked=%d", enabled, - clientMode, tlsAvailable, tlsChecked); + "tls enabled=%d, writable=%d, client=%d, available=%d, checked=%d", + enabled, writable, clientMode, tlsAvailable, tlsChecked); m_pLabelKeyLength->setEnabled(enabled); m_pComboBoxKeyLength->setEnabled(enabled); @@ -165,7 +165,7 @@ void SettingsDialog::updateTlsControlsEnabled() { } bool SettingsDialog::isClientMode() const { - return (m_pMainWindow->coreMode() == MainWindow::CoreMode::Client); + return m_pMainWindow->coreMode() == MainWindow::CoreMode::Client; } void SettingsDialog::on_m_pCheckBoxLogToFile_stateChanged(int i) { @@ -254,7 +254,7 @@ void SettingsDialog::updateKeyLengthOnFile(const QString &path) { } void SettingsDialog::updateControlsEnabled() { - bool writable = appConfig().isWritable(); + bool writable = appConfig().isActiveScopeWritable(); m_pLineEditScreenName->setEnabled(writable); m_pSpinBoxPort->setEnabled(writable); diff --git a/src/gui/src/SettingsDialog.h b/src/gui/src/SettingsDialog.h index ca9b44d71..9613b4da9 100644 --- a/src/gui/src/SettingsDialog.h +++ b/src/gui/src/SettingsDialog.h @@ -76,7 +76,7 @@ private: /// @brief Stores settings scope at start of settings dialog /// This is neccessary to restore state if user changes /// the scope and doesn't save changes - bool m_isSystemAtStart = false; + bool m_wasOriginallySystemScope = false; private slots: void on_m_pCheckBoxEnableCrypto_clicked(bool checked); diff --git a/src/gui/src/SetupWizard.cpp b/src/gui/src/SetupWizard.cpp index ae6bded58..d871c8a93 100644 --- a/src/gui/src/SetupWizard.cpp +++ b/src/gui/src/SetupWizard.cpp @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2016 Symless Ltd. + * 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 @@ -17,18 +17,16 @@ #include "SetupWizard.h" -#include "MainWindow.h" -#include "gui/constants.h" +#include "gui/styles.h" #include "validators/ScreenNameValidator.h" #include "validators/ValidationError.h" -SetupWizard::SetupWizard(MainWindow &mainWindow) : m_mainWindow(mainWindow) { - +SetupWizard::SetupWizard(AppConfig &appConfig) : m_appConfig(appConfig) { setupUi(this); m_pLabelError->setStyleSheet(kStyleErrorActiveLabel); - m_pLineEditName->setText(m_mainWindow.appConfig().screenName()); + m_pLineEditName->setText(appConfig.screenName()); m_pLineEditName->setValidator(new validators::ScreenNameValidator( m_pLineEditName, new validators::ValidationError(this, m_pLabelError))); @@ -39,13 +37,8 @@ SetupWizard::SetupWizard(MainWindow &mainWindow) : m_mainWindow(mainWindow) { } void SetupWizard::accept() { - AppConfig &appConfig = m_mainWindow.appConfig(); - - appConfig.setWizardHasRun(); - appConfig.setScreenName(m_pLineEditName->text()); - appConfig.saveSettings(); - - m_mainWindow.open(); + m_appConfig.setWizardHasRun(); + m_appConfig.setScreenName(m_pLineEditName->text()); QDialog::accept(); } diff --git a/src/gui/src/SetupWizard.h b/src/gui/src/SetupWizard.h index f55f2e149..01487e2e0 100644 --- a/src/gui/src/SetupWizard.h +++ b/src/gui/src/SetupWizard.h @@ -19,6 +19,8 @@ #include "ui_SetupWizardBase.h" +#include "gui/AppConfig.h" + #include #include @@ -28,14 +30,14 @@ class SetupWizard : public QDialog, public Ui::SetupWizardBase { Q_OBJECT public: - explicit SetupWizard(MainWindow &mainWindow); + explicit SetupWizard(AppConfig &appConfig); protected: void accept(); void reject(); private: - MainWindow &m_mainWindow; + AppConfig &m_appConfig; public slots: void onLineEditNameChanged(const QString &error); diff --git a/src/gui/src/SetupWizardBase.ui b/src/gui/src/SetupWizardBase.ui index 728d16909..2a209c80d 100644 --- a/src/gui/src/SetupWizardBase.ui +++ b/src/gui/src/SetupWizardBase.ui @@ -6,8 +6,8 @@ 0 0 - 772 - 634 + 764 + 612 @@ -36,10 +36,10 @@ 20 - 100 + 150 - 100 + 150 @@ -54,7 +54,7 @@ - + @@ -63,11 +63,11 @@ :/res/image/welcome.png - 30 + 1 - + @@ -86,6 +86,15 @@ + + 50 + + + 30 + + + 50 + @@ -121,7 +130,7 @@ - + diff --git a/src/gui/src/SetupWizardBlocker.cpp b/src/gui/src/SetupWizardBlocker.cpp index 210f8f5e1..eee36a6bc 100644 --- a/src/gui/src/SetupWizardBlocker.cpp +++ b/src/gui/src/SetupWizardBlocker.cpp @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2016 Symless Ltd. + * 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 @@ -33,9 +33,7 @@ static const std::vector blockerText = { "Please switch to Xorg if you wish to continue using Synergy today.", }; -SetupWizardBlocker::SetupWizardBlocker( - MainWindow &mainWindow, qBlockerType type) - : m_MainWindow(mainWindow) { +SetupWizardBlocker::SetupWizardBlocker(BlockerType type) { setupUi(this); m_pLabelTitle->setText(blockerTitels[static_cast(type)]); diff --git a/src/gui/src/SetupWizardBlocker.h b/src/gui/src/SetupWizardBlocker.h index 62ca9b611..36be88be4 100644 --- a/src/gui/src/SetupWizardBlocker.h +++ b/src/gui/src/SetupWizardBlocker.h @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2021 Symless Ltd. * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -16,7 +16,9 @@ */ #pragma once + #include "ui_SetupWizardBlocker.h" + #include class MainWindow; @@ -25,13 +27,10 @@ class SetupWizardBlocker : public QDialog, public Ui::SetupWizardBlocker { Q_OBJECT public: - enum class qBlockerType { waylandDetected }; - explicit SetupWizardBlocker(MainWindow &mainWindow, qBlockerType type); + enum class BlockerType { Wayland }; + explicit SetupWizardBlocker(BlockerType type); protected: void onlineSupport(); void cancel(); - -private: - MainWindow &m_MainWindow; }; diff --git a/src/gui/src/main.cpp b/src/gui/src/main.cpp index cacbd51b2..c2952d168 100644 --- a/src/gui/src/main.cpp +++ b/src/gui/src/main.cpp @@ -21,9 +21,16 @@ #include "SetupWizard.h" #include "SetupWizardBlocker.h" #include "gui/AppConfig.h" +#include "gui/ConfigScopes.h" +#include "gui/constants.h" #include "gui/dotenv.h" +#include "gui/messages.h" +#include "gui/version.h" +#include +#include #include +#include #include #include @@ -46,19 +53,23 @@ bool checkMacAssistiveDevices(); #endif int main(int argc, char *argv[]) { + #ifdef Q_OS_DARWIN /* Workaround for QTBUG-40332 - "High ping when QNetworkAccessManager is * instantiated" */ ::setenv("QT_BEARER_POLL_TIMEOUT", "-1", 1); #endif - dotenv(".env"); - - QCoreApplication::setOrganizationName("Synergy"); - QCoreApplication::setOrganizationDomain("http://symless.com/"); - QCoreApplication::setApplicationName("Synergy"); + QCoreApplication::setOrganizationName(kAppName); + QCoreApplication::setApplicationName(kAppName); + QCoreApplication::setOrganizationDomain(kAppDomain); QSynergyApplication app(argc, argv); + qInstallMessageHandler(synergy::gui::messages::messageHandler); + + qInfo("Synergy v%s", synergy::gui::version().toUtf8().constData()); + + dotenv(".env"); #if defined(Q_OS_MAC) @@ -75,31 +86,41 @@ int main(int argc, char *argv[]) { } #endif - AppConfig appConfig; qRegisterMetaType("Edition"); - MainWindow mainWindow(appConfig); + ConfigScopes configScopes; + AppConfig appConfig(configScopes); + + QObject::connect( + &configScopes, &ConfigScopes::saving, &appConfig, + [&appConfig]() { appConfig.commit(); }, Qt::DirectConnection); + + std::unique_ptr setupBlocker; + if (qgetenv("XDG_SESSION_TYPE") == "wayland") { + SetupWizardBlocker blocked(SetupWizardBlocker::BlockerType::Wayland); + blocked.exec(); + qInfo("wayland detected, exiting"); + return 0; + } + + if (appConfig.wizardShouldRun()) { + SetupWizard wizard(appConfig); + auto result = wizard.exec(); + if (result != QDialog::Accepted) { + qInfo("wizard cancelled, exiting"); + return 0; + } + + configScopes.save(); + } + + MainWindow mainWindow(configScopes, appConfig); QObject::connect( &app, &QSynergyApplication::aboutToQuit, &mainWindow, &MainWindow::onAppAboutToQuit); - std::unique_ptr setupBlocker; - if (qgetenv("XDG_SESSION_TYPE") == "wayland") { - setupBlocker.reset(new SetupWizardBlocker( - mainWindow, SetupWizardBlocker::qBlockerType::waylandDetected)); - setupBlocker->show(); - return QApplication::exec(); - } - - std::unique_ptr setupWizard; - if (appConfig.wizardShouldRun()) { - setupWizard.reset(new SetupWizard(mainWindow)); - setupWizard->show(); - } else { - mainWindow.open(); - } - + mainWindow.open(); return QSynergyApplication::exec(); } diff --git a/src/gui/src/validators/LineEditValidator.cpp b/src/gui/src/validators/LineEditValidator.cpp index 609d2c796..7bf9bec5c 100644 --- a/src/gui/src/validators/LineEditValidator.cpp +++ b/src/gui/src/validators/LineEditValidator.cpp @@ -17,7 +17,7 @@ #include "LineEditValidator.h" -#include "gui/constants.h" +#include "gui/styles.h" #include namespace validators { diff --git a/src/gui/src/validators/ValidationError.cpp b/src/gui/src/validators/ValidationError.cpp index fbf2a2eb3..6ff872000 100644 --- a/src/gui/src/validators/ValidationError.cpp +++ b/src/gui/src/validators/ValidationError.cpp @@ -17,7 +17,7 @@ #include "ValidationError.h" -#include "gui/constants.h" +#include "gui/styles.h" namespace validators { diff --git a/src/lib/arch/unix/ArchNetworkBSD.cpp b/src/lib/arch/unix/ArchNetworkBSD.cpp index e69ae05dc..36ec26dd9 100644 --- a/src/lib/arch/unix/ArchNetworkBSD.cpp +++ b/src/lib/arch/unix/ArchNetworkBSD.cpp @@ -49,8 +49,6 @@ static const int s_family[] = { static const int s_type[] = {SOCK_DGRAM, SOCK_STREAM}; -ArchNetworkBSD::Deps ArchNetworkBSD::s_deps; - #if !HAVE_INET_ATON // parse dotted quad addresses. we don't bother with the weird BSD'ism // of handling octal and hex and partial forms. @@ -274,13 +272,13 @@ int ArchNetworkBSD::pollSocket(PollEntry pe[], int num, double timeout) { // return if nothing to do if (num == 0) { if (timeout > 0.0) { - m_deps.sleep(timeout); + m_pDeps->sleep(timeout); } return 0; } // allocate space for translated query - auto pfdPtr = m_deps.makePollFD(1 + num); + auto pfdPtr = m_pDeps->makePollFD(1 + num); auto *pfd = pfdPtr.get(); // translate query @@ -308,14 +306,14 @@ int ArchNetworkBSD::pollSocket(PollEntry pe[], int num, double timeout) { int t = (timeout < 0.0) ? -1 : static_cast(1000.0 * timeout); // do the poll - n = m_deps.poll(pfd, n, t); + n = m_pDeps->poll(pfd, n, t); // reset the unblock pipe if (n > 0 && unblockPipe != nullptr && (pfd[num].revents & POLLIN) != 0) { // the unblock event was signalled. flush the pipe. char dummy[100]; do { - m_deps.read(unblockPipe[0], dummy, sizeof(dummy)); + m_pDeps->read(unblockPipe[0], dummy, sizeof(dummy)); } while (errno != EAGAIN); // don't count this unblock pipe in return value @@ -326,7 +324,7 @@ int ArchNetworkBSD::pollSocket(PollEntry pe[], int num, double timeout) { if (n == -1) { if (errno == EINTR) { // interrupted system call - m_deps.testCancelThread(); + m_pDeps->testCancelThread(); return 0; } throwError(errno); diff --git a/src/lib/arch/unix/ArchNetworkBSD.h b/src/lib/arch/unix/ArchNetworkBSD.h index 5de0991bf..14f4642c6 100644 --- a/src/lib/arch/unix/ArchNetworkBSD.h +++ b/src/lib/arch/unix/ArchNetworkBSD.h @@ -20,6 +20,7 @@ #include "arch/IArchMultithread.h" #include "arch/IArchNetwork.h" + #include #if HAVE_SYS_TYPES_H @@ -69,6 +70,7 @@ public: //! Berkeley (BSD) sockets implementation of IArchNetwork class ArchNetworkBSD : public IArchNetwork { + public: struct Deps { virtual ~Deps() = default; @@ -79,8 +81,8 @@ public: virtual void testCancelThread(); }; - explicit ArchNetworkBSD() : m_deps(s_deps) {} - explicit ArchNetworkBSD(Deps &deps) : m_deps(deps) {} + explicit ArchNetworkBSD(std::shared_ptr deps = std::make_shared()) + : m_pDeps(deps) {} ArchNetworkBSD(ArchNetworkBSD const &) = delete; ArchNetworkBSD(ArchNetworkBSD &&) = delete; ~ArchNetworkBSD() override; @@ -128,7 +130,6 @@ private: void throwNameError(int); private: - static Deps s_deps; - Deps &m_deps; + std::shared_ptr m_pDeps; ArchMutex m_mutex{}; }; diff --git a/src/lib/gui/AppConfig.cpp b/src/lib/gui/AppConfig.cpp index efa5e8acb..0f3095b6f 100644 --- a/src/lib/gui/AppConfig.cpp +++ b/src/lib/gui/AppConfig.cpp @@ -86,146 +86,156 @@ const char *const AppConfig::m_SettingsName[] = { "initiateConnectionFromServer", // kInvertConnection "", // 35 = clientHostMode, obsolete "", // 36 = serverClientMode, obsolete - "serviceEnabled", + "enableService", "closeToTray", "mainWindowSize", "mainWindowPosition", + "showDevThanks", + "showCloseReminder", }; static const char *logLevelNames[] = {"INFO", "DEBUG", "DEBUG1", "DEBUG2"}; -AppConfig::Deps AppConfig::s_Deps; - -AppConfig::AppConfig(Deps &deps) : m_Deps(deps), m_ScreenName(deps.hostname()) { - - m_Deps.scopes().registerReceiver(this); +AppConfig::AppConfig( + synergy::gui::IConfigScopes &scopes, std::shared_ptr deps) + : m_scopes(scopes), + m_pDeps(deps), + m_ScreenName(deps->hostname()) { + determineScope(); + recall(); } -void AppConfig::loadAllScopes() { - m_Deps.scopes().loadAll(); - - // User settings exist and the load from system scope variable is true - if (m_Deps.scopes().hasSetting( - settingName(Setting::kLoadSystemSettings), - ConfigScopes::Scope::User)) { - setLoadFromSystemScope(m_LoadFromSystemScope); - } - // If user setting don't exist but system ones do, load the system settings - else if (m_Deps.scopes().hasSetting( - settingName(Setting::kScreenName), - ConfigScopes::Scope::System)) { - setLoadFromSystemScope(true); - } -} - -void AppConfig::loadSettings() { +void AppConfig::recall() { using enum AppConfig::Setting; - qDebug("loading settings"); + qDebug("recalling app config"); - loadCommonSettings(); - loadScopeSettings(); - loadSerialKey(); - loadElevateMode(); - - emit loaded(); + recallFromAllScopes(); + recallFromCurrentScope(); } -void AppConfig::loadCommonSettings() { +void AppConfig::recallFromAllScopes() { using enum Setting; - m_WizardLastRun = loadCommonSetting(kWizardLastRun, m_WizardLastRun).toInt(); + m_WizardLastRun = findInAllScopes(kWizardLastRun, m_WizardLastRun).toInt(); m_LoadFromSystemScope = - loadCommonSetting(kLoadSystemSettings, m_LoadFromSystemScope).toBool(); + findInAllScopes(kLoadSystemSettings, m_LoadFromSystemScope).toBool(); m_licenseNextCheck = - loadCommonSetting(kLicenseNextCheck, m_licenseNextCheck).toULongLong(); + findInAllScopes(kLicenseNextCheck, m_licenseNextCheck).toULongLong(); } -void AppConfig::loadScopeSettings() { +void AppConfig::recallFromCurrentScope() { using enum Setting; - loadScreenName(); + recallScreenName(); + recallSerialKey(); + recallElevateMode(); - m_Port = loadSetting(kPort, m_Port).toInt(); - m_Interface = loadSetting(kInterface, m_Interface).toString(); - m_LogLevel = loadSetting(kLogLevel, m_LogLevel).toInt(); - m_LogToFile = loadSetting(kLogToFile, m_LogToFile).toBool(); - m_LogFilename = loadSetting(kLogFilename, m_LogFilename).toString(); - m_StartedBefore = loadSetting(kStartedBefore, m_StartedBefore).toBool(); - m_AutoHide = loadSetting(kAutoHide, m_AutoHide).toBool(); - m_LastVersion = loadSetting(kLastVersion, m_LastVersion).toString(); + m_Port = getFromCurrentScope(kPort, m_Port).toInt(); + m_Interface = getFromCurrentScope(kInterface, m_Interface).toString(); + m_LogLevel = getFromCurrentScope(kLogLevel, m_LogLevel).toInt(); + m_LogToFile = getFromCurrentScope(kLogToFile, m_LogToFile).toBool(); + m_LogFilename = getFromCurrentScope(kLogFilename, m_LogFilename).toString(); + m_StartedBefore = + getFromCurrentScope(kStartedBefore, m_StartedBefore).toBool(); + m_AutoHide = getFromCurrentScope(kAutoHide, m_AutoHide).toBool(); + m_LastVersion = getFromCurrentScope(kLastVersion, m_LastVersion).toString(); m_ActivationHasRun = - loadSetting(kActivationHasRun, m_ActivationHasRun).toBool(); + getFromCurrentScope(kActivationHasRun, m_ActivationHasRun).toBool(); m_ServerGroupChecked = - loadSetting(kServerGroupChecked, m_ServerGroupChecked).toBool(); + getFromCurrentScope(kServerGroupChecked, m_ServerGroupChecked).toBool(); m_UseExternalConfig = - loadSetting(kUseExternalConfig, m_UseExternalConfig).toBool(); - m_ConfigFile = loadSetting(kConfigFile, m_ConfigFile).toString(); + getFromCurrentScope(kUseExternalConfig, m_UseExternalConfig).toBool(); + m_ConfigFile = getFromCurrentScope(kConfigFile, m_ConfigFile).toString(); m_UseInternalConfig = - loadSetting(kUseInternalConfig, m_UseInternalConfig).toBool(); + getFromCurrentScope(kUseInternalConfig, m_UseInternalConfig).toBool(); m_ClientGroupChecked = - loadSetting(kClientGroupChecked, m_ClientGroupChecked).toBool(); - m_ServerHostname = loadSetting(kServerHostname, m_ServerHostname).toString(); - m_PreventSleep = loadSetting(kPreventSleep, m_PreventSleep).toBool(); - m_LanguageSync = loadSetting(kLanguageSync, m_LanguageSync).toBool(); + getFromCurrentScope(kClientGroupChecked, m_ClientGroupChecked).toBool(); + m_ServerHostname = + getFromCurrentScope(kServerHostname, m_ServerHostname).toString(); + m_PreventSleep = getFromCurrentScope(kPreventSleep, m_PreventSleep).toBool(); + m_LanguageSync = getFromCurrentScope(kLanguageSync, m_LanguageSync).toBool(); m_InvertScrollDirection = - loadSetting(kInvertScrollDirection, m_InvertScrollDirection).toBool(); + getFromCurrentScope(kInvertScrollDirection, m_InvertScrollDirection) + .toBool(); m_InvertConnection = - loadSetting(kInvertConnection, m_InvertConnection).toBool(); - m_ServiceEnabled = loadSetting(kServiceEnabled, m_ServiceEnabled).toBool(); - m_CloseToTray = loadSetting(kCloseToTray, m_CloseToTray).toBool(); - m_TlsEnabled = loadSetting(kTlsEnabled, m_TlsEnabled).toBool(); - m_TlsCertPath = loadSetting(kTlsCertPath, m_TlsCertPath).toString(); - m_TlsKeyLength = loadSetting(kTlsKeyLength, m_TlsKeyLength).toString(); - m_MainWindowPosition = loadOptional( - kMainWindowPosition, [](QVariant v) { return v.toPoint(); }); - m_MainWindowSize = loadOptional( - kMainWindowSize, [](QVariant v) { return v.toSize(); }); + getFromCurrentScope(kInvertConnection, m_InvertConnection).toBool(); + m_EnableService = + getFromCurrentScope(kEnableService, m_EnableService).toBool(); + m_CloseToTray = getFromCurrentScope(kCloseToTray, m_CloseToTray).toBool(); + m_TlsEnabled = getFromCurrentScope(kTlsEnabled, m_TlsEnabled).toBool(); + m_TlsCertPath = getFromCurrentScope(kTlsCertPath, m_TlsCertPath).toString(); + m_TlsKeyLength = + getFromCurrentScope(kTlsKeyLength, m_TlsKeyLength).toString(); + m_MainWindowPosition = getFromCurrentScope( + kMainWindowPosition, [](const QVariant &v) { return v.toPoint(); }); + m_MainWindowSize = getFromCurrentScope( + kMainWindowSize, [](const QVariant &v) { return v.toSize(); }); + m_ShowDevThanks = + getFromCurrentScope(kShowDevThanks, m_ShowDevThanks).toBool(); + m_ShowCloseReminder = + getFromCurrentScope(kShowCloseReminder, m_ShowCloseReminder).toBool(); } -void AppConfig::saveSettings() { +void AppConfig::recallScreenName() { using enum Setting; - qDebug("saving settings"); + const auto &screenName = + getFromCurrentScope(kScreenName, m_ScreenName).toString().trimmed(); - setCommonSetting(kWizardLastRun, m_WizardLastRun); - setCommonSetting(kLoadSystemSettings, m_LoadFromSystemScope); - setCommonSetting(kClientGroupChecked, m_ClientGroupChecked); - setCommonSetting(kServerGroupChecked, m_ServerGroupChecked); - setCommonSetting(kLicenseNextCheck, m_licenseNextCheck); - - if (isWritable()) { - setSetting(kScreenName, m_ScreenName); - setSetting(kPort, m_Port); - setSetting(kInterface, m_Interface); - setSetting(kLogLevel, m_LogLevel); - setSetting(kLogToFile, m_LogToFile); - setSetting(kLogFilename, m_LogFilename); - setSetting(kStartedBefore, m_StartedBefore); - setSetting(kElevateMode, static_cast(m_ElevateMode)); - setSetting(kElevateModeLegacy, m_ElevateMode == ElevateAlways); - setSetting(kTlsEnabled, m_TlsEnabled); - setSetting(kAutoHide, m_AutoHide); - setSetting(kSerialKey, m_SerialKey); - setSetting(kLastVersion, m_LastVersion); - setSetting(kActivationHasRun, m_ActivationHasRun); - setSetting(kUseExternalConfig, m_UseExternalConfig); - setSetting(kConfigFile, m_ConfigFile); - setSetting(kUseInternalConfig, m_UseInternalConfig); - setSetting(kServerHostname, m_ServerHostname); - setSetting(kPreventSleep, m_PreventSleep); - setSetting(kLanguageSync, m_LanguageSync); - setSetting(kInvertScrollDirection, m_InvertScrollDirection); - setSetting(kInvertConnection, m_InvertConnection); - setSetting(kServiceEnabled, m_ServiceEnabled); - setSetting(kCloseToTray, m_CloseToTray); - setOptional(kMainWindowSize, m_MainWindowSize); - setOptional(kMainWindowPosition, m_MainWindowPosition); + // for some reason, the screen name can be saved as an empty string + // in the config file. this is probably a bug. if this happens, then default + // back to the hostname. + if (screenName.isEmpty()) { + qWarning("screen name was empty in config, setting to hostname"); + m_ScreenName = m_pDeps->hostname(); + } else { + m_ScreenName = screenName; } +} - setModified(false); +void AppConfig::commit() { + using enum Setting; - emit saved(); + qDebug("comitting app config"); + + saveToAllScopes(kWizardLastRun, m_WizardLastRun); + saveToAllScopes(kLoadSystemSettings, m_LoadFromSystemScope); + saveToAllScopes(kClientGroupChecked, m_ClientGroupChecked); + saveToAllScopes(kServerGroupChecked, m_ServerGroupChecked); + saveToAllScopes(kLicenseNextCheck, m_licenseNextCheck); + + if (isActiveScopeWritable()) { + setInCurrentScope(kScreenName, m_ScreenName); + setInCurrentScope(kPort, m_Port); + setInCurrentScope(kInterface, m_Interface); + setInCurrentScope(kLogLevel, m_LogLevel); + setInCurrentScope(kLogToFile, m_LogToFile); + setInCurrentScope(kLogFilename, m_LogFilename); + setInCurrentScope(kStartedBefore, m_StartedBefore); + setInCurrentScope(kElevateMode, static_cast(m_ElevateMode)); + setInCurrentScope(kElevateModeLegacy, m_ElevateMode == ElevateAlways); + setInCurrentScope(kTlsEnabled, m_TlsEnabled); + setInCurrentScope(kAutoHide, m_AutoHide); + setInCurrentScope(kSerialKey, m_SerialKey); + setInCurrentScope(kLastVersion, m_LastVersion); + setInCurrentScope(kActivationHasRun, m_ActivationHasRun); + setInCurrentScope(kUseExternalConfig, m_UseExternalConfig); + setInCurrentScope(kConfigFile, m_ConfigFile); + setInCurrentScope(kUseInternalConfig, m_UseInternalConfig); + setInCurrentScope(kServerHostname, m_ServerHostname); + setInCurrentScope(kPreventSleep, m_PreventSleep); + setInCurrentScope(kLanguageSync, m_LanguageSync); + setInCurrentScope(kInvertScrollDirection, m_InvertScrollDirection); + setInCurrentScope(kInvertConnection, m_InvertConnection); + setInCurrentScope(kEnableService, m_EnableService); + setInCurrentScope(kCloseToTray, m_CloseToTray); + setInCurrentScope(kShowDevThanks, m_ShowDevThanks); + setInCurrentScope(kShowCloseReminder, m_ShowCloseReminder); + + setInCurrentScope(kMainWindowSize, m_MainWindowSize); + setInCurrentScope(kMainWindowPosition, m_MainWindowPosition); + } if (m_TlsChanged) { m_TlsChanged = false; @@ -233,37 +243,45 @@ void AppConfig::saveSettings() { } } -void AppConfig::loadScreenName() { - using enum Setting; +void AppConfig::determineScope() { - const auto &screenName = - loadSetting(kScreenName, m_ScreenName).toString().trimmed(); + qDebug("determining config scope"); - // for some reason, the screen name can be saved as an empty string - // in the config file. this is probably a bug. if this happens, then default - // back to the hostname. - if (screenName.isEmpty()) { - qWarning("screen name was empty in config, setting to hostname"); - m_ScreenName = m_Deps.hostname(); - } else { - m_ScreenName = screenName; + // first, try to determine if the system scope should be used according to the + // user scope... + if (m_scopes.scopeContains( + settingName(Setting::kLoadSystemSettings), + ConfigScopes::Scope::User)) { + auto loadFromSystemScope = + m_scopes + .getFromScope( + settingName(Setting::kLoadSystemSettings), + m_LoadFromSystemScope, ConfigScopes::Scope::User) + .toBool(); + + setLoadFromSystemScope(loadFromSystemScope); + } + + // ...failing that, check the system scope instead to see if an arbitrary + // required setting is present. if it is, then we can assume that the system + // scope should be used. + else if (m_scopes.scopeContains( + settingName(Setting::kScreenName), + ConfigScopes::Scope::System)) { + setLoadFromSystemScope(true); } } -void AppConfig::loadSerialKey() { +void AppConfig::recallSerialKey() { using enum Setting; - // only set the serial key if the current settings scope has they key. - bool shouldLoad = m_Deps.scopes().hasSetting( - settingName(kLoadSystemSettings), ConfigScopes::Scope::Current); - - if (!shouldLoad) { + if (!m_scopes.scopeContains(settingName(kLoadSystemSettings))) { qDebug("no serial key in current scope, skipping"); return; } const auto &serialKey = - loadSetting(kSerialKey, m_SerialKey).toString().trimmed(); + getFromCurrentScope(kSerialKey, m_SerialKey).toString().trimmed(); if (serialKey.isEmpty()) { qDebug("serial key is empty, skipping"); @@ -273,18 +291,18 @@ void AppConfig::loadSerialKey() { m_SerialKey = serialKey; } -void AppConfig::loadElevateMode() { +void AppConfig::recallElevateMode() { using enum Setting; - if (!m_Deps.scopes().hasSetting(settingName(kElevateMode))) { + if (!m_scopes.scopeContains(settingName(kElevateMode))) { qDebug("elevate mode not set yet, skipping"); return; } - QVariant elevateMode = loadSetting(kElevateMode); + QVariant elevateMode = getFromCurrentScope(kElevateMode); if (!elevateMode.isValid()) { qDebug("elevate mode not valid, loading legacy setting"); - elevateMode = loadSetting( + elevateMode = getFromCurrentScope( kElevateModeLegacy, QVariant(static_cast(kDefaultElevateMode))); } @@ -292,7 +310,7 @@ void AppConfig::loadElevateMode() { } QString AppConfig::defaultTlsCertPath() const { - QDir path(m_Deps.profileDir()); + QDir path(m_pDeps->profileDir()); path = path.filePath("SSL"); path = path.filePath("Synergy.pem"); return path.absolutePath(); @@ -303,103 +321,113 @@ QString AppConfig::settingName(Setting name) { return m_SettingsName[index]; } -template void AppConfig::setSetting(Setting name, T value) { - m_Deps.scopes().setSetting(settingName(name), value); +template void AppConfig::setInCurrentScope(Setting name, T value) { + m_scopes.setInScope(settingName(name), value); } -template void AppConfig::setCommonSetting(Setting name, T value) { - m_Deps.scopes().setSetting( - settingName(name), value, ConfigScopes::Scope::User); - m_Deps.scopes().setSetting( - settingName(name), value, ConfigScopes::Scope::System); +template void AppConfig::saveToAllScopes(Setting name, T value) { + m_scopes.setInScope(settingName(name), value, ConfigScopes::Scope::User); + m_scopes.setInScope(settingName(name), value, ConfigScopes::Scope::System); } -QVariant AppConfig::loadSetting(Setting name, const QVariant &defaultValue) { - return m_Deps.scopes().loadSetting(settingName(name), defaultValue); +QVariant AppConfig::getFromCurrentScope( + Setting name, const QVariant &defaultValue) const { + return m_scopes.getFromScope(settingName(name), defaultValue); } template -std::optional -AppConfig::loadOptional(Setting name, std::function toType) const { - if (m_Deps.scopes().hasSetting(settingName(name))) { - return toType(m_Deps.scopes().loadSetting(settingName(name))); +std::optional AppConfig::getFromCurrentScope( + Setting name, std::function toType) const { + if (m_scopes.scopeContains(settingName(name))) { + return toType(m_scopes.getFromScope(settingName(name))); } else { return std::nullopt; } } template -void AppConfig::setOptional(Setting name, const std::optional &value) { +void AppConfig::setInCurrentScope(Setting name, const std::optional &value) { if (value.has_value()) { - m_Deps.scopes().setSetting(settingName(name), value.value()); + m_scopes.setInScope(settingName(name), value.value()); } } QVariant -AppConfig::loadCommonSetting(Setting name, const QVariant &defaultValue) const { +AppConfig::findInAllScopes(Setting name, const QVariant &defaultValue) const { + using enum ConfigScopes::Scope; + QVariant result(defaultValue); QString setting(settingName(name)); - if (m_Deps.scopes().hasSetting(setting)) { - result = m_Deps.scopes().loadSetting(setting, defaultValue); - } else if (m_Deps.scopes().getScope() == ConfigScopes::Scope::System) { - if (m_Deps.scopes().hasSetting(setting, ConfigScopes::Scope::User)) { - result = m_Deps.scopes().loadSetting( - setting, defaultValue, ConfigScopes::Scope::User); + if (m_scopes.scopeContains(setting)) { + result = m_scopes.getFromScope(setting, defaultValue); + } else if (m_scopes.activeScope() == System) { + if (m_scopes.scopeContains(setting, User)) { + result = m_scopes.getFromScope(setting, defaultValue, User); } - } else if (m_Deps.scopes().hasSetting(setting, ConfigScopes::Scope::System)) { - result = m_Deps.scopes().loadSetting( - setting, defaultValue, ConfigScopes::Scope::System); + } else if (m_scopes.scopeContains(setting, System)) { + result = m_scopes.getFromScope(setting, defaultValue, System); } return result; } void AppConfig::loadScope(ConfigScopes::Scope scope) { + using enum ConfigScopes::Scope; - if (m_Deps.scopes().getScope() != scope) { - setDefaultValues(); - m_Deps.scopes().setScope(scope); - if (m_Deps.scopes().hasSetting( - settingName(Setting::kScreenName), m_Deps.scopes().getScope())) { - // If the user already has settings, then load them up now. - m_Deps.scopes().loadAll(); - } + switch (scope) { + case User: + qDebug("loading user settings scope"); + break; + + case System: + qDebug("loading system settings scope"); + break; + + default: + qFatal("invalid scope"); + } + + if (m_scopes.activeScope() == scope) { + qDebug("already in required scope, skipping"); + return; + } + + m_scopes.setActiveScope(scope); + + // only signal ready if there is at least one setting in the required scope. + // this prevents the current settings from being set back to default. + if (m_scopes.scopeContains( + settingName(Setting::kScreenName), m_scopes.activeScope())) { + m_scopes.signalReady(); + } else { + qDebug("no screen name in scope, skipping"); } } -void AppConfig::setDefaultValues() { m_InvertConnection = false; } - void AppConfig::setLoadFromSystemScope(bool value) { + using enum ConfigScopes::Scope; if (value) { qDebug("loading system settings scope"); - loadScope(ConfigScopes::Scope::System); + loadScope(System); } else { qDebug("loading user settings scope"); - loadScope(ConfigScopes::Scope::User); + loadScope(User); } - /* - * It's very imprortant to set this variable after loadScope - * because during scope loading this variable can be rewritten with old value - */ + // set after loading scope since it may have been overridden. m_LoadFromSystemScope = value; } -bool AppConfig::isWritable() const { return m_Deps.scopes().isWritable(); } - -bool AppConfig::isSystemScoped() const { - return m_Deps.scopes().getScope() == ConfigScopes::Scope::System; +bool AppConfig::isActiveScopeWritable() const { + return m_scopes.isActiveScopeWritable(); } -template -void AppConfig::setSettingModified(T &variable, const T &newValue) { - if (variable != newValue) { - variable = newValue; - setModified(true); - } +bool AppConfig::isActiveScopeSystem() const { + return m_scopes.activeScope() == ConfigScopes::Scope::System; } + QString AppConfig::logDir() const { // by default log to home dir return QDir::home().absolutePath() + "/"; @@ -418,7 +446,7 @@ void AppConfig::persistLogDir() const { // Begin getters /////////////////////////////////////////////////////////////////////////////// -IConfigScopes &AppConfig::scopes() { return m_Deps.scopes(); } +IConfigScopes &AppConfig::scopes() { return m_scopes; } bool AppConfig::activationHasRun() const { return m_ActivationHasRun; } @@ -439,7 +467,7 @@ const QString &AppConfig::logFilename() const { return m_LogFilename; } QString AppConfig::logLevelText() const { return logLevelNames[logLevel()]; } ProcessMode AppConfig::processMode() const { - return m_ServiceEnabled ? ProcessMode::kService : ProcessMode::kDesktop; + return m_EnableService ? ProcessMode::kService : ProcessMode::kDesktop; } bool AppConfig::wizardShouldRun() const { @@ -478,7 +506,7 @@ QString AppConfig::tlsCertPath() const { return m_TlsCertPath; } QString AppConfig::tlsKeyLength() const { return m_TlsKeyLength; } -bool AppConfig::serviceEnabled() const { return m_ServiceEnabled; } +bool AppConfig::enableService() const { return m_EnableService; } bool AppConfig::closeToTray() const { return m_CloseToTray; } @@ -504,6 +532,10 @@ std::optional AppConfig::mainWindowPosition() const { return m_MainWindowPosition; } +bool AppConfig::showDevThanks() const { return m_ShowDevThanks; } + +bool AppConfig::showCloseReminder() const { return m_ShowCloseReminder; } + /////////////////////////////////////////////////////////////////////////////// // End getters /////////////////////////////////////////////////////////////////////////////// @@ -515,109 +547,94 @@ std::optional AppConfig::mainWindowPosition() const { void AppConfig::clearSerialKey() { m_SerialKey.clear(); } void AppConfig::setTlsEnabled(bool value) { - setSettingModified(m_TlsEnabled, value); - m_TlsChanged = true; + // we purposefully do not set the 'tls changed' flag when enabling/disabling + // tls, since that would cause the certificate to regenerate, which could get + // pretty annoying. + + m_TlsEnabled = value; } void AppConfig::setTlsCertPath(const QString &value) { - setSettingModified(m_TlsCertPath, value); - m_TlsChanged = true; + m_TlsChanged = m_TlsCertPath != value; + m_TlsCertPath = value; } void AppConfig::setTlsKeyLength(const QString &value) { - setSettingModified(m_TlsKeyLength, value); - m_TlsChanged = true; + m_TlsChanged = m_TlsKeyLength != value; + m_TlsKeyLength = value; } void AppConfig::setSerialKey(const QString &serialKey) { - setSettingModified(m_SerialKey, serialKey); - setCommonSetting(Setting::kSerialKey, m_SerialKey); + m_SerialKey = serialKey; + saveToAllScopes(Setting::kSerialKey, m_SerialKey); } void AppConfig::setServerGroupChecked(bool newValue) { - setSettingModified(m_ServerGroupChecked, newValue); + m_ServerGroupChecked = newValue; } void AppConfig::setUseExternalConfig(bool newValue) { - setSettingModified(m_UseExternalConfig, newValue); + m_UseExternalConfig = newValue; } void AppConfig::setConfigFile(const QString &newValue) { - setSettingModified(m_ConfigFile, newValue); + m_ConfigFile = newValue; } void AppConfig::setUseInternalConfig(bool newValue) { - setSettingModified(m_UseInternalConfig, newValue); + m_UseInternalConfig = newValue; } void AppConfig::setClientGroupChecked(bool newValue) { - setSettingModified(m_ClientGroupChecked, newValue); + m_ClientGroupChecked = newValue; } void AppConfig::setServerHostname(const QString &newValue) { - setSettingModified(m_ServerHostname, newValue); + m_ServerHostname = newValue; } void AppConfig::setLastVersion(const QString &version) { - setSettingModified(m_LastVersion, version); + m_LastVersion = version; } void AppConfig::setScreenName(const QString &s) { - setSettingModified(m_ScreenName, s); + m_ScreenName = s; emit screenNameChanged(); } -void AppConfig::setPort(int i) { setSettingModified(m_Port, i); } +void AppConfig::setPort(int i) { m_Port = i; } -void AppConfig::setNetworkInterface(const QString &s) { - setSettingModified(m_Interface, s); -} +void AppConfig::setNetworkInterface(const QString &s) { m_Interface = s; } -void AppConfig::setLogLevel(int i) { setSettingModified(m_LogLevel, i); } +void AppConfig::setLogLevel(int i) { m_LogLevel = i; } -void AppConfig::setLogToFile(bool b) { setSettingModified(m_LogToFile, b); } +void AppConfig::setLogToFile(bool b) { m_LogToFile = b; } -void AppConfig::setLogFilename(const QString &s) { - setSettingModified(m_LogFilename, s); -} +void AppConfig::setLogFilename(const QString &s) { m_LogFilename = s; } -void AppConfig::setWizardHasRun() { - setSettingModified(m_WizardLastRun, kWizardVersion); -} +void AppConfig::setWizardHasRun() { m_WizardLastRun = kWizardVersion; } -void AppConfig::setStartedBefore(bool b) { - setSettingModified(m_StartedBefore, b); -} +void AppConfig::setStartedBefore(bool b) { m_StartedBefore = b; } -void AppConfig::setElevateMode(ElevateMode em) { - setSettingModified(m_ElevateMode, em); -} +void AppConfig::setElevateMode(ElevateMode em) { m_ElevateMode = em; } -void AppConfig::setAutoHide(bool b) { setSettingModified(m_AutoHide, b); } +void AppConfig::setAutoHide(bool b) { m_AutoHide = b; } void AppConfig::setLicenseNextCheck(unsigned long long time) { - setSettingModified(m_licenseNextCheck, time); + m_licenseNextCheck = time; } void AppConfig::setInvertScrollDirection(bool newValue) { - setSettingModified(m_InvertScrollDirection, newValue); + m_InvertScrollDirection = newValue; } -void AppConfig::setLanguageSync(bool newValue) { - setSettingModified(m_LanguageSync, newValue); -} +void AppConfig::setLanguageSync(bool newValue) { m_LanguageSync = newValue; } -void AppConfig::setPreventSleep(bool newValue) { - setSettingModified(m_PreventSleep, newValue); -} +void AppConfig::setPreventSleep(bool newValue) { m_PreventSleep = newValue; } -void AppConfig::setServiceEnabled(bool enabled) { - setSettingModified(m_ServiceEnabled, enabled); -} +void AppConfig::setEnableService(bool enabled) { m_EnableService = enabled; } -void AppConfig::setCloseToTray(bool minimize) { - setSettingModified(m_CloseToTray, minimize); -} +void AppConfig::setCloseToTray(bool minimize) { m_CloseToTray = minimize; } void AppConfig::setInvertConnection(bool value) { - setSettingModified(m_InvertConnection, value); + m_InvertConnection = value; emit invertConnectionChanged(); } @@ -629,6 +646,12 @@ void AppConfig::setMainWindowPosition(const QPoint &position) { m_MainWindowPosition = position; } +void AppConfig::setShowDevThanks(bool value) { m_ShowDevThanks = value; } + +void AppConfig::setShowCloseReminder(bool value) { + m_ShowCloseReminder = value; +} + /////////////////////////////////////////////////////////////////////////////// // End setters /////////////////////////////////////////////////////////////////////////////// diff --git a/src/lib/gui/AppConfig.h b/src/lib/gui/AppConfig.h index a0057ef7b..be0e62cf1 100644 --- a/src/lib/gui/AppConfig.h +++ b/src/lib/gui/AppConfig.h @@ -18,7 +18,6 @@ #pragma once -#include "CommonConfig.h" #include "ConfigScopes.h" #include "CoreInterface.h" #include "ElevateMode.h" @@ -31,7 +30,7 @@ #include #include #include -#include +#include enum class ProcessMode { kService, kDesktop }; @@ -42,7 +41,13 @@ const QString kDefaultLogFile = "synergy.log"; const ProcessMode kDefaultProcessMode = ProcessMode::kService; #else const ProcessMode kDefaultProcessMode = ProcessMode::kDesktop; -#endif +#endif // Q_OS_WIN + +#ifdef SYNERGY_SHOW_DEV_THANKS +const bool kDefaultShowDevThanks = true; +#else +const bool kDefaultShowDevThanks = false; +#endif // SYNERGY_SHOW_DEV_THANKS /** * @brief Simply reads and writes app settings. @@ -52,9 +57,7 @@ const ProcessMode kDefaultProcessMode = ProcessMode::kDesktop; * instance is widely accessible, but that has previously led to this class * becoming a god object. */ -class AppConfig : public QObject, - public synergy::gui::CommonConfig, - public synergy::gui::IAppConfig { +class AppConfig : public QObject, public synergy::gui::IAppConfig { Q_OBJECT private: @@ -96,10 +99,12 @@ private: kInvertConnection = 34, // 35 = client-host-mode, obsolete // 36 = server-client-mode, obsolete - kServiceEnabled = 37, + kEnableService = 37, kCloseToTray = 38, kMainWindowSize = 39, kMainWindowPosition = 40, + kShowDevThanks = 41, + kShowCloseReminder = 42, }; public: @@ -108,29 +113,33 @@ public: virtual QString profileDir() const { return m_coreInterface.getProfileDir(); } - virtual synergy::gui::IConfigScopes &scopes() { return m_Scopes; } virtual QString hostname() const { return QHostInfo::localHostName(); } private: [[no_unique_address]] CoreInterface m_coreInterface; - synergy::gui::ConfigScopes m_Scopes; }; - explicit AppConfig() : AppConfig(s_Deps) {} - explicit AppConfig(Deps &deps); + explicit AppConfig( + synergy::gui::IConfigScopes &scopes, + std::shared_ptr deps = std::make_shared()); synergy::gui::IConfigScopes &scopes(); - void saveSettings() override; - void loadAllScopes(); - void loadSettings() override; + + /** + * @brief Commits the current settings to the active scope. + * This should only be called when the settings are about to be saved. + */ + void commit(); + + void determineScope(); /** * Getters */ void setActivationHasRun(bool value); - bool isWritable() const; - bool isSystemScoped() const; + bool isActiveScopeWritable() const; + bool isActiveScopeSystem() const; const QString &screenName() const; int port() const; const QString &networkInterface() const; @@ -159,7 +168,7 @@ public: bool clientGroupChecked() const; QString serverHostname() const; QString lastVersion() const; - bool serviceEnabled() const; + bool enableService() const; bool closeToTray() const; QString serialKey() const; bool activationHasRun() const; @@ -168,6 +177,8 @@ public: QString tlsKeyLength() const override; std::optional mainWindowSize() const; std::optional mainWindowPosition() const; + bool showDevThanks() const; + bool showCloseReminder() const; /** * Setters @@ -197,13 +208,15 @@ public: void setClientGroupChecked(bool); void setServerHostname(const QString &); void setLastVersion(const QString &version); - void setServiceEnabled(bool enabled); + void setEnableService(bool enabled); void setCloseToTray(bool minimize); void setTlsCertPath(const QString &path); void setTlsKeyLength(const QString &length); void setInvertConnection(bool value); void setMainWindowSize(const QSize &size); void setMainWindowPosition(const QPoint &position); + void setShowDevThanks(bool show); + void setShowCloseReminder(bool show); /// @brief Sets the user preference to load from SystemScope. /// @param [in] value @@ -214,11 +227,13 @@ public: private: static QString settingName(AppConfig::Setting name); - void loadScreenName(); - void loadSerialKey(); - void loadElevateMode(); - void loadCommonSettings(); - void loadScopeSettings(); + + void recall(); + void recallScreenName(); + void recallSerialKey(); + void recallElevateMode(); + void recallFromAllScopes(); + void recallFromCurrentScope(); /** * @brief Loads a setting if it exists, otherwise returns `std::nullopt` @@ -226,60 +241,48 @@ private: * @param toType A function to convert the QVariant to the desired type. */ template - std::optional - loadOptional(Setting name, std::function toType) const; + std::optional getFromCurrentScope( + Setting name, std::function toType) const; /** * @brief Sets a setting if the value is not `std::nullopt`. */ template - void setOptional(Setting name, const std::optional &value); + void setInCurrentScope(Setting name, const std::optional &value); /// @brief Sets the value of a setting /// @param [in] name The Setting to be saved /// @param [in] value The Value to be saved - template void setSetting(AppConfig::Setting name, T value); + template + void setInCurrentScope(AppConfig::Setting name, T value); /// @brief Sets the value of a common setting /// which should have the same value for all scopes /// @param [in] name The Setting to be saved /// @param [in] value The Value to be saved - template void setCommonSetting(AppConfig::Setting name, T value); + template void saveToAllScopes(AppConfig::Setting name, T value); - /// @brief Loads a setting - /// @param [in] name The setting to be loaded - /// @param [in] defaultValue The default value of the setting - QVariant loadSetting( - AppConfig::Setting name, const QVariant &defaultValue = QVariant()); - - /// @brief Loads a common setting - /// @param [in] name The setting to be loaded - /// @param [in] defaultValue The default value of the setting - QVariant loadCommonSetting( + QVariant getFromCurrentScope( AppConfig::Setting name, const QVariant &defaultValue = QVariant()) const; - /// @brief Sets the setting in the config checking if it has changed and - /// flagging that settings - /// needs to be saved if the setting was different - /// @param [in] variable the setting that will be changed - /// @param [in] newValue The new value of the setting - template void setSettingModified(T &variable, const T &newValue); + /** + * @brief Finds a value by searching each scope starting with the current + * scope. + */ + QVariant findInAllScopes( + AppConfig::Setting name, const QVariant &defaultValue = QVariant()) const; /// @brief This method loads config from specified scope /// @param [in] scope which should be loaded. void loadScope(synergy::gui::ConfigScopes::Scope scope); - /// @brief This function sets default values - /// for settings that shouldn't be copied from between scopes. - void setDefaultValues(); - /** * @brief Gets a TLS certificate path based on the user's profile dir. */ QString defaultTlsCertPath() const; - static Deps s_Deps; - Deps &m_Deps; + synergy::gui::IConfigScopes &m_scopes; + std::shared_ptr m_pDeps; QString m_ScreenName; int m_Port = 24800; QString m_Interface = ""; @@ -306,12 +309,15 @@ private: bool m_UseInternalConfig = false; bool m_ClientGroupChecked = false; QString m_ServerHostname = ""; - bool m_ServiceEnabled = kDefaultProcessMode == ProcessMode::kService; - bool m_CloseToTray = false; + bool m_EnableService = kDefaultProcessMode == ProcessMode::kService; + bool m_CloseToTray = true; QString m_TlsCertPath = defaultTlsCertPath(); QString m_TlsKeyLength = "2048"; std::optional m_MainWindowSize; std::optional m_MainWindowPosition; + bool m_ShowDevThanks = kDefaultShowDevThanks; + bool m_LoadFromSystemScope = false; + bool m_ShowCloseReminder = true; /** * @brief Flag is set when any TLS is setting is changed, and is reset @@ -319,19 +325,6 @@ private: */ bool m_TlsChanged = false; - /// @brief should the setting be loaded from - /// SystemScope - /// If the user has settings but this is - /// true then system settings will be - /// loaded instead of the users - bool m_LoadFromSystemScope = false; - - /// @brief As the settings will be accessible by multiple objects this lock - /// will ensure that - /// it cant be modified by more that one object at a time if the - /// setting is being switched from system to user. - std::mutex m_settings_lock; - static const char m_CoreServerName[]; static const char m_CoreClientName[]; static const char m_LogDir[]; @@ -339,12 +332,10 @@ private: /// @brief Contains the string values of the settings names that will be saved static const char *const m_SettingsName[]; - /// @brief Contains the name of the default configuration filename + /// @brief Core config filename (not the Qt settings filename) static const char m_ConfigFilename[]; signals: - void loaded(); - void saved(); void tlsChanged(); void screenNameChanged(); void invertConnectionChanged(); diff --git a/src/lib/gui/ConfigScopes.cpp b/src/lib/gui/ConfigScopes.cpp index f159a5c0d..0eef3cce4 100644 --- a/src/lib/gui/ConfigScopes.cpp +++ b/src/lib/gui/ConfigScopes.cpp @@ -17,58 +17,81 @@ #include "ConfigScopes.h" -#include "CommonConfig.h" - #include #include #include #include +const auto kSystemConfigFilename = "SystemConfig.ini"; +const auto kUnixSystemConfigPath = "/usr/local/etc/symless/"; + namespace synergy::gui { QString getSystemSettingPath() { - const QString settingFilename("SystemConfig.ini"); - QString path; + const QString settingFilename(kSystemConfigFilename); #if defined(Q_OS_WIN) - path = QCoreApplication::applicationDirPath() + "\\"; + return QCoreApplication::applicationDirPath() + "\\"; #elif defined(Q_OS_DARWIN) - // Global preferances dir - // Would be nice to use /library, but QT has no elevate system in place - path = "/usr/local/etc/symless/"; + // it would be nice to use /Library dir, but qt has no elevate system. + return kUnixSystemConfigPath + settingFilename; #elif defined(Q_OS_LINUX) - // QT adds application and filename to the end of the path already on linux - path = "/usr/local/etc/symless/"; - return path; + // qt already adds application and filename to the end of the path on linux. + return kUnixSystemConfigPath; #else - assert("OS not supported"); + qFatal("unsupported platform"); + return ""; #endif - return path + settingFilename; } #if defined(Q_OS_WIN) void loadWindowsLegacy(QSettings &settings) { - if (!QFile(settings.fileName()).exists()) { - QSettings::setPath( - QSettings::IniFormat, QSettings::SystemScope, "SystemConfig.ini"); - QSettings oldSystemSettings( - QSettings::IniFormat, QSettings::SystemScope, - QCoreApplication::organizationName(), - QCoreApplication::applicationName()); - - if (QFile(oldSystemSettings.fileName()).exists()) { - for (const auto &key : oldSystemSettings.allKeys()) { - settings.setValue(key, oldSystemSettings.value(key)); - } - } - - // Restore system settings path - QSettings::setPath( - QSettings::IniFormat, QSettings::SystemScope, getSystemSettingPath()); + if (QFile(settings.fileName()).exists()) { + qDebug("system settings already exist, skipping legacy load"); + return; } + + QSettings::setPath( + QSettings::IniFormat, QSettings::SystemScope, kSystemConfigFilename); + QSettings oldSystemSettings( + QSettings::IniFormat, QSettings::SystemScope, + QCoreApplication::organizationName(), + QCoreApplication::applicationName()); + + if (QFile(oldSystemSettings.fileName()).exists()) { + for (const auto &key : oldSystemSettings.allKeys()) { + settings.setValue(key, oldSystemSettings.value(key)); + } + } + + QSettings::setPath( + QSettings::IniFormat, QSettings::SystemScope, getSystemSettingPath()); } #endif ConfigScopes::ConfigScopes() { + auto orgName = QCoreApplication::organizationName(); + if (orgName.isEmpty()) { + qFatal("unable to load config, organization name is empty"); + return; + } else { + qDebug() << "org name for config:" << orgName; + } + + auto appName = QCoreApplication::applicationName(); + if (appName.isEmpty()) { + qFatal("unable to load config, application name is empty"); + return; + } else { + qDebug() << "app name for config:" << appName; + } + + // default to user scope. + // if we set the scope specifically then we also have to set the application + // name and the organisation name which breaks backwards compatibility. + m_pUserSettings = std::make_unique(); + + qDebug() << "user settings path:" << m_pUserSettings->fileName(); + QSettings::setPath( QSettings::Format::IniFormat, QSettings::Scope::SystemScope, getSystemSettingPath()); @@ -76,47 +99,57 @@ ConfigScopes::ConfigScopes() { // Config will default to User settings if they exist, // otherwise it will load System setting and save them to User settings m_pSystemSettings = std::make_unique( - QSettings::Format::IniFormat, QSettings::Scope::SystemScope, - QCoreApplication::organizationName(), - QCoreApplication::applicationName()); + QSettings::Format::IniFormat, QSettings::Scope::SystemScope, orgName, + appName); - // default to user scope. - // if we set the scope specifically then we also have to set the application - // name and the organisation name which breaks backwards compatibility. - m_pUserSettings = std::make_unique(); + qDebug() << "system settings path:" << m_pSystemSettings->fileName(); - load(); -} - -ConfigScopes::~ConfigScopes() { - while (!m_pReceivers.empty()) { - m_pReceivers.pop_back(); - } -} - -void ConfigScopes::load() { #if defined(Q_OS_WIN) - // This call is needed for backwardcapability with old settings. loadWindowsLegacy(*m_pSystemSettings); #endif } -bool ConfigScopes::hasSetting(const QString &name, Scope scope) const { +void ConfigScopes::signalReady() { emit ready(); } + +void ConfigScopes::save() { + qDebug("emitting config saving signal"); + emit saving(); + + qDebug("writing config to filesystem"); + m_pUserSettings->sync(); + m_pSystemSettings->sync(); +} + +bool ConfigScopes::isActiveScopeWritable() const { + return activeSettings()->isWritable(); +} + +void ConfigScopes::setActiveScope(ConfigScopes::Scope scope) { + m_currentScope = scope; +} + +ConfigScopes::Scope ConfigScopes::activeScope() const { return m_currentScope; } + +bool ConfigScopes::scopeContains(const QString &name, Scope scope) const { switch (scope) { case Scope::User: return m_pUserSettings->contains(name); case Scope::System: return m_pSystemSettings->contains(name); default: - return currentSettings()->contains(name); + return activeSettings()->contains(name); } } -bool ConfigScopes::isWritable() const { - return currentSettings()->isWritable(); +QSettings *ConfigScopes::activeSettings() const { + if (m_currentScope == Scope::User) { + return m_pUserSettings.get(); + } else { + return m_pSystemSettings.get(); + } } -QVariant ConfigScopes::loadSetting( +QVariant ConfigScopes::getFromScope( const QString &name, const QVariant &defaultValue, Scope scope) const { switch (scope) { case Scope::User: @@ -124,68 +157,23 @@ QVariant ConfigScopes::loadSetting( case Scope::System: return m_pSystemSettings->value(name, defaultValue); default: - return currentSettings()->value(name, defaultValue); + return activeSettings()->value(name, defaultValue); } } -void ConfigScopes::setScope(ConfigScopes::Scope scope) { - m_CurrentScope = scope; -} - -ConfigScopes::Scope ConfigScopes::getScope() const { return m_CurrentScope; } - -void ConfigScopes::loadAll() { - for (auto &i : m_pReceivers) { - i->loadSettings(); - } -} - -void ConfigScopes::saveAll() { - - // Save if there are any unsaved changes otherwise skip - if (unsavedChanges()) { - for (auto &i : m_pReceivers) { - i->saveSettings(); - } - - m_pUserSettings->sync(); - m_pSystemSettings->sync(); - - m_unsavedChanges = false; - } -} - -QSettings *ConfigScopes::currentSettings() const { - if (m_CurrentScope == Scope::User) { - return m_pUserSettings.get(); - } else { - return m_pSystemSettings.get(); - } -} - -void ConfigScopes::registerReceiver(CommonConfig *receiver) { - m_pReceivers.push_back(receiver); -} - -bool ConfigScopes::unsavedChanges() const { - if (m_unsavedChanges) { - return true; - } - - for (const auto &i : m_pReceivers) { - if (i->modified()) { - return true; - } - } - return false; -} - -void ConfigScopes::markUnsaved() { m_unsavedChanges = true; } - -void ConfigScopes::setSetting( +void ConfigScopes::setInScope( const QString &name, const QVariant &value, Scope scope) { - currentSettings()->setValue(name, value); - m_unsavedChanges = true; + switch (scope) { + case Scope::User: + m_pUserSettings->setValue(name, value); + break; + case Scope::System: + m_pSystemSettings->setValue(name, value); + break; + default: + activeSettings()->setValue(name, value); + break; + } } } // namespace synergy::gui diff --git a/src/lib/gui/ConfigScopes.h b/src/lib/gui/ConfigScopes.h index ecbe9438d..e5110f1e8 100644 --- a/src/lib/gui/ConfigScopes.h +++ b/src/lib/gui/ConfigScopes.h @@ -19,94 +19,44 @@ #include "IConfigScopes.h" +#include #include #include #include namespace synergy::gui { -class CommonConfig; - -/// @brief A general config reader and writer for user and gloabl settings -class ConfigScopes : public IConfigScopes { +/// @brief Encapsulates Qt config for both user and global scopes. +class ConfigScopes : public QObject, public IConfigScopes { + Q_OBJECT public: explicit ConfigScopes(); - virtual ~ConfigScopes(); + virtual ~ConfigScopes() = default; - /// @brief Checks if the setting exists - /// @param [in] name The name of the setting to check - /// @param [in] scope The scope to search in - /// @return bool True if the current scope has the named setting - bool - hasSetting(const QString &name, Scope scope = Scope::Current) const override; - - /// @brief Checks if the current scope settings writable - /// @return bool True if the current scope writable - bool isWritable() const override; - - /// @brief Sets the value of a setting - /// @param [in] name The Setting to be saved - /// @param [in] value The Value to be saved (Templated) - /// @param [in] scope The scope to get the value from, default is current - /// scope - void setSetting( + void signalReady() override; + void save() override; + bool scopeContains( + const QString &name, Scope scope = Scope::Current) const override; + bool isActiveScopeWritable() const override; + void setInScope( const QString &name, const QVariant &value, Scope scope = Scope::Current) override; - - /// @brief Loads a setting - /// @param [in] name The setting to be loaded - /// @param [in] defaultValue The default value of the setting - /// @param [in] scope The scope to get the value from, default is current - /// scope - QVariant loadSetting( + QVariant getFromScope( const QString &name, const QVariant &defaultValue = QVariant(), Scope scope = Scope::Current) const override; + void setActiveScope(Scope scope = Scope::User) override; + Scope activeScope() const override; + QSettings *activeSettings() const override; - /// @brief Changes the setting save and load location between System and User - /// scope - /// @param [in] scope The scope to set - void setScope(Scope scope = Scope::User) override; - - /// @brief Get the current scope the settings are loading and save from. - /// @return Scope An enum defining the current scope - Scope getScope() const override; - - /// @brief trigger a config load across all registered classes - void loadAll() override; - - /// @brief trigger a config save across all registered classes - void saveAll() override; - - /// @brief Returns the current scopes settings object - /// If more specialize control into the settings is needed this can - /// provide direct access to the settings file handler - /// @return QSettings The Settings object as a reference - QSettings *currentSettings() const override; - - /// @brief This marks the settings as unsaved if the settings() was used to - /// directly affect the config file - void markUnsaved(); - - /// @brief Register a class to receives requests to save and load settings - void registerReceiver(CommonConfig *receiver) override; - - /// @brief Checks if any registered class has any unsaved changes - /// @return bool True if any registered class has unsaved changes - bool unsavedChanges() const; +signals: + void ready(); + void saving(); private: - void load(); - - Scope m_CurrentScope = Scope::User; + Scope m_currentScope = Scope::User; std::unique_ptr m_pUserSettings; std::unique_ptr m_pSystemSettings; - - /// @brief Receivers of load/save callbacks - std::list m_pReceivers; - - /// @brief Is set to true when settings are changed - bool m_unsavedChanges = false; }; } // namespace synergy::gui diff --git a/src/lib/gui/IConfigScopes.h b/src/lib/gui/IConfigScopes.h index 39293dc96..4d78bf941 100644 --- a/src/lib/gui/IConfigScopes.h +++ b/src/lib/gui/IConfigScopes.h @@ -17,8 +17,6 @@ #pragma once -#include "gui/CommonConfig.h" - #include #include #include @@ -31,21 +29,42 @@ public: virtual ~IConfigScopes() = default; - virtual void registerReceiver(CommonConfig *receiver) = 0; - virtual void loadAll() = 0; + virtual Scope activeScope() const = 0; + virtual void setActiveScope(Scope scope = Scope::User) = 0; + virtual bool isActiveScopeWritable() const = 0; + virtual QSettings *activeSettings() const = 0; + + /** + * @brief Signals to listeners that the settings that they should read. + */ + virtual void signalReady() = 0; + + /** + * @brief Signalls to listeners to save and calls `sync` on underlying Qt + * config. + * + */ + virtual void save() = 0; + + /** + * @brief Check a scope for a config value (default is current scope). + */ virtual bool - hasSetting(const QString &name, Scope scope = Scope::Current) const = 0; - virtual QVariant loadSetting( + scopeContains(const QString &name, Scope scope = Scope::Current) const = 0; + + /** + * @brief Load a config value from a scope (default is current scope). + */ + virtual QVariant getFromScope( const QString &name, const QVariant &defaultValue = QVariant(), Scope scope = Scope::Current) const = 0; - virtual void setSetting( + + /** + * @brief Set a config value in a scope (default is current scope). + */ + virtual void setInScope( const QString &name, const QVariant &value, Scope scope = Scope::Current) = 0; - virtual Scope getScope() const = 0; - virtual void setScope(Scope scope = Scope::User) = 0; - virtual bool isWritable() const = 0; - virtual QSettings *currentSettings() const = 0; - virtual void saveAll() = 0; }; } // namespace synergy::gui diff --git a/src/lib/gui/LicenseHandler.cpp b/src/lib/gui/LicenseHandler.cpp index 49939e60a..6210aad5e 100644 --- a/src/lib/gui/LicenseHandler.cpp +++ b/src/lib/gui/LicenseHandler.cpp @@ -17,6 +17,7 @@ #include "LicenseHandler.h" +#include "constants.h" #include "license/ProductEdition.h" #include "license/parse_serial_key.h" @@ -41,6 +42,10 @@ LicenseHandler::ChangeSerialKeyResult LicenseHandler::changeSerialKey(const QString &hexString) { using enum LicenseHandler::ChangeSerialKeyResult; + if (!m_enabled) { + qFatal("cannot set serial key, licensing is disabled"); + } + if (hexString.isEmpty()) { qFatal("serial key is empty"); return kFatal; diff --git a/src/lib/gui/LicenseHandler.h b/src/lib/gui/LicenseHandler.h index 717cb99c1..e6ed2ec28 100644 --- a/src/lib/gui/LicenseHandler.h +++ b/src/lib/gui/LicenseHandler.h @@ -17,6 +17,7 @@ #pragma once +#include "gui/constants.h" #include "license/License.h" #include "license/ProductEdition.h" @@ -44,11 +45,13 @@ public: void validate() const; QString productName() const; ChangeSerialKeyResult changeSerialKey(const QString &hexString); + void setEnabled(bool enabled) { m_enabled = enabled; } signals: void serialKeyChanged(const QString &serialKey) const; void invalidLicense() const; private: + bool m_enabled = kLicensingEnabled; License m_license = License::invalid(); }; diff --git a/src/lib/gui/QIpcClient.cpp b/src/lib/gui/QIpcClient.cpp index 3ec540032..ed2ddc02d 100644 --- a/src/lib/gui/QIpcClient.cpp +++ b/src/lib/gui/QIpcClient.cpp @@ -56,13 +56,13 @@ QIpcClient::~QIpcClient() { void QIpcClient::connected() { sendHello(); - infoMessage("connection established"); + emit infoMessage("connection established"); } void QIpcClient::connectToHost() { m_Enabled = true; - infoMessage("connecting to service..."); + emit infoMessage("connecting to service..."); const auto port = static_cast(kIpcPort); m_Socket->connectToHost(QHostAddress(QHostAddress::LocalHost), port); @@ -73,7 +73,7 @@ void QIpcClient::connectToHost() { } void QIpcClient::disconnectFromHost() { - infoMessage("service disconnect"); + emit infoMessage("service disconnect"); m_Reader->stop(); m_Socket->close(); } @@ -92,7 +92,7 @@ void QIpcClient::error(QAbstractSocket::SocketError error) { break; } - errorMessage(QString("ipc connection error, %1").arg(text)); + emit errorMessage(QString("ipc connection error, %1").arg(text)); QTimer::singleShot(1000, this, SLOT(retryConnect())); } @@ -133,7 +133,9 @@ void QIpcClient::sendCommand( stream->writeRawData(elevateBuf, 1); } -void QIpcClient::handleReadLogLine(const QString &text) { readLogLine(text); } +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) { diff --git a/src/lib/gui/TlsUtility.cpp b/src/lib/gui/TlsUtility.cpp index b9b5afea2..fe17207e2 100644 --- a/src/lib/gui/TlsUtility.cpp +++ b/src/lib/gui/TlsUtility.cpp @@ -32,6 +32,9 @@ bool TlsUtility::isAvailableAndEnabled() const { } void TlsUtility::generateCertificate(bool replace) const { + qDebug("generating tls certificate, " + "all clients must trust the new fingerprint"); + if (!isAvailableAndEnabled()) { qFatal("unable to generate tls certificate, " "tls is either not available or not enabled"); diff --git a/src/lib/gui/constants.h b/src/lib/gui/constants.h index f1a9a6af9..827001437 100644 --- a/src/lib/gui/constants.h +++ b/src/lib/gui/constants.h @@ -19,25 +19,33 @@ #include +const auto kAppName = "Synergy"; + +// TODO: change to `com.symless`. we'll need to gracefully import old settings, +// since qt uses this on some platforms when saving settings. +const auto kAppDomain = "https://symless.com"; + +#ifdef SYNERGY_PRODUCT_NAME +const QString kProductName = SYNERGY_PRODUCT_NAME; +#else +const QString kProductName; +#endif + #ifdef SYNERGY_ENABLE_LICENSING const bool kLicensingEnabled = true; #else const bool kLicensingEnabled = false; #endif // SYNERGY_ENABLE_LICENSING -const auto kColorWhite = "#ffffff"; -const auto kColorPrimary = "#ff7c00"; -const auto kColorSecondary = "#4285f4"; -const auto kColorTertiary = "#33b2cc"; -const auto kColorError = "#ec4c47"; -const auto kColorNotice = "#3b67d3"; - const auto kLinkBuy = R"(Buy now)"; const auto kLinkRenew = R"(Renew now)"; const auto kLinkDownload = R"(Download now)"; const auto kUrlSourceQuery = "source=gui"; const auto kUrlWebsite = "https://symless.com"; +const auto kUrlContribute = "https://github.com/symless/synergy-core"; +const auto kUrlGnomeTrayFix = + "https://extensions.gnome.org/extension/2890/tray-icons-reloaded/"; const auto kUrlProduct = QString("%1/synergy").arg(kUrlWebsite); const auto kUrlPurchase = QString("%1/purchase?%2").arg(kUrlProduct, kUrlSourceQuery); @@ -46,21 +54,3 @@ const auto kUrlContact = const auto kUrlHelp = QString("%1/help?%2").arg(kUrlProduct, kUrlSourceQuery); const auto kUrlDownload = QString("%1/download?%2").arg(kUrlProduct, kUrlSourceQuery); - -const auto kStyleLineEditErrorBorder = - QString("border: 1px solid %1; border-radius: 2px; padding: 2px;") - .arg(kColorError); - -const auto kStyleErrorActiveLabel = // - QString("padding: 3px 5px; border-radius: 3px; " - "background-color: %1; color: %2") - .arg(kColorError, kColorWhite); - -const auto kStyleErrorInactiveLabel = // - QString("padding: 3px 5px; border-radius: 3px;" - "background-color: none"); - -const auto kStyleNoticeLabel = // - QString("padding: 3px 5px; border-radius: 3px;" - "background-color: %1; color: %2") - .arg(kColorNotice, kColorWhite); diff --git a/src/lib/gui/license_notices.cpp b/src/lib/gui/license_notices.cpp index 1e7444816..2aab5f540 100644 --- a/src/lib/gui/license_notices.cpp +++ b/src/lib/gui/license_notices.cpp @@ -19,6 +19,7 @@ #include "constants.h" #include "license/License.h" +#include "styles.h" using License = synergy::license::License; diff --git a/src/lib/gui/messages.cpp b/src/lib/gui/messages.cpp new file mode 100644 index 000000000..c057a0dfd --- /dev/null +++ b/src/lib/gui/messages.cpp @@ -0,0 +1,159 @@ +/* + * 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 . + */ + +#include "messages.h" + +#include "constants.h" +#include "styles.h" + +#include +#include +#include + +namespace synergy::gui::messages { + +void messageHandler( + QtMsgType type, const QMessageLogContext &context, const QString &message) { + + auto datetime = QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm:ss"); + const auto filename = QString(context.file).split("/").last(); + auto function = context.function ? context.function : ""; + + QString typeString; + auto out = stdout; + switch (type) { + case QtDebugMsg: + typeString = "DEBUG"; + break; + case QtInfoMsg: + typeString = "INFO"; + break; + case QtWarningMsg: + typeString = "WARNING"; + out = stderr; + break; + case QtCriticalMsg: + typeString = "CRITICAL"; + out = stderr; + break; + case QtFatalMsg: + typeString = "FATAL"; + out = stderr; + break; + } + + auto logLine = QString("[%1] %2: %3\n\t%4:%5, %6") + .arg(datetime) + .arg(typeString) + .arg(message) + .arg(filename) + .arg(context.line) + .arg(function); + + auto logLineUtf = logLine.toUtf8(); + auto logLine_c = logLineUtf.constData(); + fprintf(out, "%s\n", logLine_c); + + if (type == QtFatalMsg) { + auto contextString = + QString("%1:%2\n%3").arg(filename).arg(context.line).arg(function); + + QMessageBox::critical( + nullptr, "Fatal error", + QString("

Sorry, a fatal error has occurred " + "and the application must now exit.

" + "

Please " + R"(contact us)" + " and copy/paste the following error:

" + "
%3\n\n%4
") + .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(); + } +} + +void showCloseReminder(QWidget *parent) { + QString message = + "

Synergy will continue to run in the background and can be accessed " + "via the Synergy icon in your system notifications area. This setting " + "can be disabled.

"; + +#if defined(Q_OS_LINUX) + message += QString("

On some Linux systems such as GNOME 3, the " + "notification area might be disabled. " + "You may need to " + R"(enable an extension)" + " to see the Synergy tray icon.

") + .arg(kUrlGnomeTrayFix, kStyleLink); +#endif + + QMessageBox::information(parent, "Notification area icon", message); +} + +void showFirstRunMessage( + QWidget *parent, bool closeToTray, bool enableService, bool isServer) { + + auto message = QString("

Synergy is now connected!

"); + + if (isServer) { + message += + "

Try moving your mouse to your other computer. Once there, go ahead " + "and type something.

" + "

Don't forget, you can copy and paste between computers too.

"; + } else { + message += "

Try controlling this computer remotely.

"; + } + + if (!closeToTray && !enableService) { + message += + "

As you do not have the setting enabled to keep Synergy running in " + "the background, you'll need to keep this window open or minimized to " + "keep Synergy running.

"; + } else { + message += + "

You can now close this window and Synergy will continue to run in " + "the background. This setting can be disabled.

"; + } + + QMessageBox::information(parent, "Connected", message); +} + +void showDevThanks(QWidget *parent, const QString &productName) { + if (productName.isEmpty()) { + qFatal("product name not set"); + } + + QMessageBox::information( + parent, "Thank you!", + QString( + "

Thanks for using %1.

" + "

If you enjoy using this app, you can support the developers by " + R"(purchasing a license)" + " or " + R"(contributing code.)" + "

" + "

This message will only appear once.

") + .arg( + productName, kUrlPurchase, kColorSecondary, kUrlContribute, + kColorSecondary)); +} + +} // namespace synergy::gui::messages diff --git a/src/lib/gui/messages.h b/src/lib/gui/messages.h new file mode 100644 index 000000000..5926a9c37 --- /dev/null +++ b/src/lib/gui/messages.h @@ -0,0 +1,36 @@ +/* + * 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 . + */ + +#pragma once + +#include +#include +#include + +namespace synergy::gui::messages { + +void messageHandler( + QtMsgType type, const QMessageLogContext &context, const QString &msg); + +void showFirstRunMessage( + QWidget *parent, bool closeToTray, bool enableService, bool isServer); + +void showCloseReminder(QWidget *parent); + +void showDevThanks(QWidget *parent, const QString &productName); + +} // namespace synergy::gui::messages diff --git a/src/lib/gui/styles.h b/src/lib/gui/styles.h new file mode 100644 index 000000000..5e63d457d --- /dev/null +++ b/src/lib/gui/styles.h @@ -0,0 +1,49 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2014 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 . + */ + +#pragma once + +#include + +const auto kColorWhite = "#ffffff"; +const auto kColorPrimary = "#ff7c00"; +const auto kColorSecondary = "#4285f4"; +const auto kColorTertiary = "#33b2cc"; +const auto kColorError = "#ec4c47"; +const auto kColorNotice = "#3b67d3"; +const auto kColorLightGrey = "#666666"; + +const auto kStyleLink = // + QString("color: %1").arg(kColorSecondary); + +const auto kStyleLineEditErrorBorder = + QString("border: 1px solid %1; border-radius: 2px; padding: 2px;") + .arg(kColorError); + +const auto kStyleErrorActiveLabel = // + QString("padding: 3px 5px; border-radius: 3px; " + "background-color: %1; color: %2") + .arg(kColorError, kColorWhite); + +const auto kStyleErrorInactiveLabel = // + QString("padding: 3px 5px; border-radius: 3px;" + "background-color: none"); + +const auto kStyleNoticeLabel = // + QString("padding: 3px 5px; border-radius: 3px;" + "background-color: %1; color: %2") + .arg(kColorNotice, kColorWhite); diff --git a/src/lib/gui/version.cpp b/src/lib/gui/version.cpp new file mode 100644 index 000000000..b0dd9af13 --- /dev/null +++ b/src/lib/gui/version.cpp @@ -0,0 +1,38 @@ +/* + * 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 . + */ + +#include "version.h" + +const QString kVersion = SYNERGY_VERSION; + +#ifdef GIT_SHA_SHORT +const QString kVersionGitSha = GIT_SHA_SHORT; +#else +const QString kVersionGitSha; +#endif + +namespace synergy::gui { + +QString version() { + QString result(kVersion); + if (!kVersionGitSha.isEmpty()) { + result.append(QString(" (%1)").arg(kVersionGitSha)); + } + return result; +} + +} // namespace synergy::gui diff --git a/src/lib/gui/CommonConfig.h b/src/lib/gui/version.h similarity index 65% rename from src/lib/gui/CommonConfig.h rename to src/lib/gui/version.h index e6fae87c8..0cd51257f 100644 --- a/src/lib/gui/CommonConfig.h +++ b/src/lib/gui/version.h @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2020 Symless Ltd. + * Copyright (C) 2024 Symless Ltd. * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,20 +17,10 @@ #pragma once +#include + namespace synergy::gui { -/// @brief Common configuration interface -class CommonConfig { -public: - CommonConfig() = default; - virtual ~CommonConfig() = default; - virtual void loadSettings() = 0; - virtual void saveSettings() = 0; - bool modified() const { return m_modified; } - void setModified(bool modified) { m_modified = modified; } - -private: - bool m_modified = false; -}; +QString version(); } // namespace synergy::gui diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index 1c8099dfc..096b16c79 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -435,11 +435,13 @@ void Server::switchScreen( BaseClientProxy *dst, SInt32 x, SInt32 y, bool forScreensaver) { assert(dst != NULL); - // if trial is expired, exit the process - License license(m_args.m_serialKey); - if (license.isExpired()) { - LOG((CLOG_ERR "trial has expired, aborting server")); - exit(kExitSuccess); + if (m_args.m_serialKey.isValid) { + // if license is expired, exit the process + License license(m_args.m_serialKey); + if (license.isExpired()) { + LOG((CLOG_ERR "trial has expired, aborting server")); + exit(kExitSuccess); + } } #ifndef NDEBUG diff --git a/src/test/shared/gui/QtCoreTest.h b/src/test/shared/gui/QtCoreTest.h index 9e7a91d32..fb7d05b90 100644 --- a/src/test/shared/gui/QtCoreTest.h +++ b/src/test/shared/gui/QtCoreTest.h @@ -17,6 +17,8 @@ #pragma once +#include "gui/messages.h" + #include #include @@ -26,6 +28,7 @@ public: char **argv = nullptr; int argc = 0; s_app = std::make_unique(argc, argv); + qInstallMessageHandler(synergy::gui::messages::messageHandler); } static void TearDownTestSuite() { s_app.reset(); } diff --git a/src/test/shared/gui/QtTest.h b/src/test/shared/gui/QtTest.h index 12f67036e..cd2186fff 100644 --- a/src/test/shared/gui/QtTest.h +++ b/src/test/shared/gui/QtTest.h @@ -17,6 +17,8 @@ #pragma once +#include "gui/messages.h" + #include #include @@ -26,6 +28,7 @@ public: char **argv = nullptr; int argc = 0; s_app = std::make_unique(argc, argv); + qInstallMessageHandler(synergy::gui::messages::messageHandler); } static void TearDownTestSuite() { s_app.reset(); } diff --git a/src/test/unittests/arch/unix/ArchNetworkBSDTests.cpp b/src/test/unittests/arch/unix/ArchNetworkBSDTests.cpp index 30a77e74a..99e4397d8 100644 --- a/src/test/unittests/arch/unix/ArchNetworkBSDTests.cpp +++ b/src/test/unittests/arch/unix/ArchNetworkBSDTests.cpp @@ -33,6 +33,8 @@ using PollFD = struct pollfd[]; namespace { struct MockDeps : public ArchNetworkBSD::Deps { + std::shared_ptr m_pollFD; + MockDeps() { ON_CALL(*this, makePollFD(_)).WillByDefault([this](nfds_t n) { m_pollFD = ArchNetworkBSD::Deps::makePollFD(n); @@ -40,29 +42,31 @@ struct MockDeps : public ArchNetworkBSD::Deps { }); } + static std::shared_ptr> makeNice() { + return std::make_shared>(); + } + MOCK_METHOD(void, sleep, (double), (override)); MOCK_METHOD(int, poll, (struct pollfd *, nfds_t, int), (override)); MOCK_METHOD(std::shared_ptr, makePollFD, (nfds_t), (override)); MOCK_METHOD(ssize_t, read, (int, void *, size_t), (override)); MOCK_METHOD(void, testCancelThread, (), (override)); - - std::shared_ptr m_pollFD; }; } // namespace TEST(ArchNetworkBSDTests, pollSocket_zeroEntries_callsSleep) { - MockDeps deps; + auto deps = MockDeps::makeNice(); ArchNetworkBSD networkBSD(deps); - EXPECT_CALL(deps, sleep(1)).Times(1); + EXPECT_CALL(*deps, sleep(1)).Times(1); auto result = networkBSD.pollSocket(nullptr, 0, 1); EXPECT_EQ(result, 0); } TEST(ArchNetworkBSDTests, pollSocket_mockAccessError_throws) { - NiceMock deps; - ON_CALL(deps, poll(_, _, _)).WillByDefault([]() { + auto deps = MockDeps::makeNice(); + ON_CALL(*deps, poll(_, _, _)).WillByDefault([]() { errno = EACCES; return -1; }); @@ -77,8 +81,8 @@ TEST(ArchNetworkBSDTests, pollSocket_mockAccessError_throws) { } TEST(ArchNetworkBSDTests, pollSocket_pfdHasRevents_copiedToEntries) { - NiceMock deps; - ON_CALL(deps, poll(_, _, _)).WillByDefault([](auto pfd, auto, auto) { + auto deps = MockDeps::makeNice(); + ON_CALL(*deps, poll(_, _, _)).WillByDefault([](auto pfd, auto, auto) { pfd[0].revents = POLLIN | POLLOUT | POLLERR | POLLNVAL; return 0; }); @@ -93,50 +97,50 @@ TEST(ArchNetworkBSDTests, pollSocket_pfdHasRevents_copiedToEntries) { } TEST(ArchNetworkBSDTests, pollSocket_nullSocket_fdIsNegativeOne) { - NiceMock deps; + auto deps = MockDeps::makeNice(); ArchNetworkBSD networkBSD(deps); PollEntries entries{{nullptr, 0, 0}}; networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); - EXPECT_EQ(deps.m_pollFD[0].fd, -1); + EXPECT_EQ(deps->m_pollFD[0].fd, -1); } TEST(ArchNetworkBSDTests, pollSocket_socketSet_fdWasSet) { - NiceMock deps; + auto deps = MockDeps::makeNice(); ArchNetworkBSD networkBSD(deps); ArchSocketImpl socket{1, 0}; PollEntries entries{{&socket, 0, 0}}; networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); - EXPECT_EQ(deps.m_pollFD[0].fd, 1); + EXPECT_EQ(deps->m_pollFD[0].fd, 1); } TEST(ArchNetworkBSDTests, pollSocket_eventHasPollInBit_bitWasSet) { - NiceMock deps; + auto deps = MockDeps::makeNice(); ArchNetworkBSD networkBSD(deps); ArchSocketImpl socket{1, 0}; PollEntries entries{{&socket, IArchNetwork::kPOLLIN, 0}}; networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); - EXPECT_EQ(deps.m_pollFD[0].events, POLLIN); + EXPECT_EQ(deps->m_pollFD[0].events, POLLIN); } TEST(ArchNetworkBSDTests, pollSocket_eventHasPollOutBit_bitWasSet) { - NiceMock deps; + auto deps = MockDeps::makeNice(); ArchNetworkBSD networkBSD(deps); ArchSocketImpl socket{1, 0}; PollEntries entries{{&socket, IArchNetwork::kPOLLOUT, 0}}; networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); - EXPECT_EQ(deps.m_pollFD[0].events, POLLOUT); + EXPECT_EQ(deps->m_pollFD[0].events, POLLOUT); } TEST(ArchNetworkBSDTests, pollSocket_nullSocket_unblockPipeAppended) { - NiceMock deps; + auto deps = MockDeps::makeNice(); ArchNetworkBSD networkBSD(deps); PollEntries entries{{nullptr, 0, 0}}; @@ -144,42 +148,42 @@ TEST(ArchNetworkBSDTests, pollSocket_nullSocket_unblockPipeAppended) { // interesting: unblock pipe fd comes from `getNetworkDataForThread` which // seems to differ depending on linux distro. - EXPECT_GT(deps.m_pollFD[1].fd, -1); + EXPECT_GT(deps->m_pollFD[1].fd, -1); } TEST(ArchNetworkBSDTests, pollSocket_unblockPipeReventsError_readCalled) { const auto unblockPipeIndex = 1; - NiceMock deps; - ON_CALL(deps, poll(_, _, _)).WillByDefault([](auto pfd, auto, auto) { + auto deps = MockDeps::makeNice(); + ON_CALL(*deps, poll(_, _, _)).WillByDefault([](auto pfd, auto, auto) { pfd[unblockPipeIndex].revents = POLLIN; return 1; }); - ON_CALL(deps, read(_, _, _)).WillByDefault([]() { + ON_CALL(*deps, read(_, _, _)).WillByDefault([]() { errno = EAGAIN; return 0; }); ArchNetworkBSD networkBSD(deps); PollEntries entries{{nullptr, 0, 0}}; - EXPECT_CALL(deps, read(_, _, _)).Times(1); + EXPECT_CALL(*deps, read(_, _, _)).Times(1); networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); } TEST(ArchNetworkBSDTests, pollSocket_interruptSystemCall_testCancelThread) { - NiceMock deps; - ON_CALL(deps, poll(_, _, _)).WillByDefault([]() { + auto deps = MockDeps::makeNice(); + ON_CALL(*deps, poll(_, _, _)).WillByDefault([]() { errno = EINTR; return -1; }); ArchNetworkBSD networkBSD(deps); PollEntries entries{{nullptr, 0, 0}}; - EXPECT_CALL(deps, testCancelThread()).Times(1); + EXPECT_CALL(*deps, testCancelThread()).Times(1); networkBSD.pollSocket(entries.data(), static_cast(entries.size()), 1); } TEST(ArchNetworkBSDTests, isAnyAddr_goodAddress_returnsTrue) { - MockDeps deps; + auto deps = MockDeps::makeNice(); ArchNetworkBSD networkBSD(deps); std::unique_ptr addr; addr.reset(networkBSD.newAnyAddr(IArchNetwork::kINET6)); @@ -190,7 +194,7 @@ TEST(ArchNetworkBSDTests, isAnyAddr_goodAddress_returnsTrue) { } TEST(ArchNetworkBSDTests, isAnyAddr_badAddress_returnsFalse) { - MockDeps deps; + auto deps = MockDeps::makeNice(); ArchNetworkBSD networkBSD(deps); std::unique_ptr addr; addr.reset(networkBSD.newAnyAddr(IArchNetwork::kINET6)); diff --git a/src/test/unittests/gui/AppConfigTests.cpp b/src/test/unittests/gui/AppConfigTests.cpp index 391c94d1a..d185f75f7 100644 --- a/src/test/unittests/gui/AppConfigTests.cpp +++ b/src/test/unittests/gui/AppConfigTests.cpp @@ -27,37 +27,35 @@ namespace { class MockScopes : public synergy::gui::IConfigScopes { public: + MOCK_METHOD(void, signalReady, (), (override)); MOCK_METHOD( - void, registerReceiver, (synergy::gui::CommonConfig * receiver), - (override)); - MOCK_METHOD(void, loadAll, (), (override)); + bool, scopeContains, (const QString &name, Scope scope), + (const, override)); MOCK_METHOD( - bool, hasSetting, (const QString &name, Scope scope), (const, override)); - MOCK_METHOD( - QVariant, loadSetting, + QVariant, getFromScope, (const QString &name, const QVariant &defaultValue, Scope scope), (const, override)); MOCK_METHOD( - void, setSetting, + void, setInScope, (const QString &name, const QVariant &value, Scope scope), (override)); - MOCK_METHOD(Scope, getScope, (), (const, override)); - MOCK_METHOD(void, setScope, (Scope scope), (override)); - MOCK_METHOD(bool, isWritable, (), (const, override)); - MOCK_METHOD(QSettings *, currentSettings, (), (const, override)); - MOCK_METHOD(void, saveAll, (), (override)); + MOCK_METHOD(Scope, activeScope, (), (const, override)); + MOCK_METHOD(void, setActiveScope, (Scope scope), (override)); + MOCK_METHOD(bool, isActiveScopeWritable, (), (const, override)); + MOCK_METHOD(QSettings *, activeSettings, (), (const, override)); + MOCK_METHOD(void, save, (), (override)); }; struct MockDeps : public AppConfig::Deps { - NiceMock m_scopes; - MockDeps() { ON_CALL(*this, profileDir()).WillByDefault(Return("stub")); - ON_CALL(*this, scopes()).WillByDefault(ReturnRef(m_scopes)); ON_CALL(*this, hostname()).WillByDefault(Return("stub")); } + static std::shared_ptr> makeNice() { + return std::make_shared>(); + } + MOCK_METHOD(QString, profileDir, (), (const, override)); - MOCK_METHOD(synergy::gui::IConfigScopes &, scopes, (), (override)); MOCK_METHOD(QString, hostname, (), (const, override)); }; @@ -66,43 +64,36 @@ struct MockDeps : public AppConfig::Deps { class AppConfigTests : public Test {}; TEST_F(AppConfigTests, ctor_byDefault_screenNameIsHostname) { - NiceMock deps; - ON_CALL(deps, hostname()).WillByDefault(Return("test")); + NiceMock scopes; + auto deps = MockDeps::makeNice(); + ON_CALL(*deps, hostname()).WillByDefault(Return("test hostname")); - AppConfig appConfig(deps); + AppConfig appConfig(scopes, deps); - ASSERT_EQ(appConfig.screenName().toStdString(), "test"); + ASSERT_EQ(appConfig.screenName().toStdString(), "test hostname"); } -TEST_F(AppConfigTests, loadAllScopes_byDefault_callsScopesLoadAll) { - NiceMock deps; - AppConfig appConfig(deps); +TEST_F(AppConfigTests, ctor_byDefault_getsFromScope) { + NiceMock scopes; + auto deps = MockDeps::makeNice(); - EXPECT_CALL(deps.m_scopes, loadAll()); + ON_CALL(scopes, scopeContains(_, _)).WillByDefault(Return(true)); + ON_CALL(scopes, getFromScope(_, _, _)) + .WillByDefault(Return(QVariant("test screen"))); + EXPECT_CALL(scopes, getFromScope(_, _, _)).Times(AnyNumber()); - appConfig.loadAllScopes(); + AppConfig appConfig(scopes, deps); + + ASSERT_EQ(appConfig.screenName().toStdString(), "test screen"); } -TEST_F(AppConfigTests, loadSettings_byDefault_callsScopesLoadSetting) { - NiceMock deps; - AppConfig appConfig(deps); +TEST_F(AppConfigTests, commit_byDefault_setsToScope) { + NiceMock scopes; + auto deps = MockDeps::makeNice(); + AppConfig appConfig(scopes, deps); - ON_CALL(deps.m_scopes, hasSetting(_, _)).WillByDefault(Return(true)); - ON_CALL(deps.m_scopes, loadSetting(_, _, _)) - .WillByDefault(Return(QVariant("test"))); - EXPECT_CALL(deps.m_scopes, loadSetting(_, _, _)).Times(AnyNumber()); + ON_CALL(scopes, isActiveScopeWritable()).WillByDefault(Return(true)); + EXPECT_CALL(scopes, setInScope(_, _, _)).Times(AnyNumber()); - appConfig.loadSettings(); - - ASSERT_EQ(appConfig.screenName().toStdString(), "test"); -} - -TEST_F(AppConfigTests, saveSettings_byDefault_callsScopesSetSetting) { - NiceMock deps; - AppConfig appConfig(deps); - - ON_CALL(deps.m_scopes, isWritable()).WillByDefault(Return(true)); - EXPECT_CALL(deps.m_scopes, setSetting(_, _, _)).Times(AnyNumber()); - - appConfig.saveSettings(); + appConfig.commit(); } diff --git a/src/test/unittests/gui/LicenseHandlerTests.cpp b/src/test/unittests/gui/LicenseHandlerTests.cpp index 9abf35229..c926c01b6 100644 --- a/src/test/unittests/gui/LicenseHandlerTests.cpp +++ b/src/test/unittests/gui/LicenseHandlerTests.cpp @@ -28,6 +28,7 @@ const auto kFuture = system_clock::now() + hours(1); TEST(LicenseHandlerTests, changeSerialKey_validExpiredLicense_returnsTrue) { LicenseHandler licenseHandler; + licenseHandler.setEnabled(true); auto hexString = // "7B76313B70726F3B6E69636B20626F6C746F6E3B313B6" "E69636B4073796D6C6573732E636F6D3B203B303B307D";