diff --git a/ChangeLog b/ChangeLog index fbb9d62f7..a1c6edc39 100644 --- a/ChangeLog +++ b/ChangeLog @@ -71,6 +71,7 @@ Enhancements: - #7425 Refactored core process management and IPC client - #7426 Fixed warnings and enable errors as warnings - #7427 More reliable button click detection for add client dialog +- #7428 Refactor settings dialog to simplify enable/disable logic # 1.14.6 diff --git a/cspell.json b/cspell.json index a9b1eaa99..fe1cac8fd 100644 --- a/cspell.json +++ b/cspell.json @@ -33,6 +33,7 @@ "Olena", "outputdir", "Petroules", + "Pixmap", "Poschta", "Priddy", "runas", diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 0f5640808..15eef033c 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -94,8 +94,8 @@ MainWindow::MainWindow(ConfigScopes &configScopes, AppConfig &appConfig) m_AppConfig(appConfig), m_ServerConfig(appConfig, *this), m_CoreProcess(appConfig, m_ServerConfig), - m_ServerConnection(*this, appConfig, m_ServerConfig), - m_ClientConnection(*this, appConfig), + m_ServerConnection(this, appConfig, m_ServerConfig), + m_ClientConnection(this, appConfig), m_TlsUtility(appConfig, m_LicenseHandler.license()), m_WindowSaveTimer(this) { @@ -251,7 +251,9 @@ void MainWindow::connectSlots() { connect(m_pActionMinimize, &QAction::triggered, this, &MainWindow::hide); - connect(m_pActionRestore, &QAction::triggered, this, &MainWindow::showNormal); + connect( + m_pActionRestore, &QAction::triggered, // + [this]() { showAndActivate(); }); connect( m_pActionStartCore, &QAction::triggered, this, @@ -278,6 +280,14 @@ void MainWindow::connectSlots() { connect( &m_ServerConnection, &ServerConnection::configureClient, this, &MainWindow::onServerConnectionConfigureClient); + + connect( + &m_ServerConnection, &ServerConnection::messageShowing, this, + [this]() { showAndActivate(); }); + + connect( + &m_ClientConnection, &ClientConnection::messageShowing, this, + [this]() { showAndActivate(); }); } void MainWindow::onAppAboutToQuit() { m_ConfigScopes.save(); } @@ -339,8 +349,7 @@ void MainWindow::onTrayIconActivated(QSystemTrayIcon::ActivationReason reason) { if (isVisible()) { hide(); } else { - showNormal(); - activateWindow(); + showAndActivate(); } } } @@ -355,7 +364,7 @@ void MainWindow::onVersionCheckerUpdateFound(const QString &version) { } void MainWindow::onActionStartCoreTriggered() { - m_ClientConnection.setCheckConnection(true); + m_ClientConnection.setShowMessage(); m_CoreProcess.start(); } @@ -408,9 +417,11 @@ void MainWindow::on_m_pActionHelp_triggered() { } void MainWindow::on_m_pActionSettings_triggered() { - auto result = - SettingsDialog(this, m_AppConfig, m_LicenseHandler.license()).exec(); - if (result == QDialog::Accepted) { + auto dialog = SettingsDialog( + this, m_AppConfig, m_ServerConfig, m_LicenseHandler.license(), + m_CoreProcess); + + if (dialog.exec() == QDialog::Accepted) { m_ConfigScopes.save(); applyConfig(); @@ -445,7 +456,7 @@ void MainWindow::on_m_pLineEditClientIp_textChanged(const QString &text) { } void MainWindow::on_m_pButtonApply_clicked() { - m_ClientConnection.setCheckConnection(true); + m_ClientConnection.setShowMessage(); m_CoreProcess.restart(); } @@ -516,7 +527,7 @@ void MainWindow::open() { if (m_AppConfig.autoHide()) { hide(); } else { - showNormal(); + showAndActivate(); } m_VersionChecker.checkLatest(); @@ -651,10 +662,10 @@ void MainWindow::updateFromLogLine(const QString &line) { void MainWindow::checkConnected(const QString &line) { if (m_pRadioGroupServer->isChecked()) { - m_ServerConnection.update(line); + m_ServerConnection.handleLogLine(line); m_pLabelServerState->updateServerState(line); } else { - m_ClientConnection.update(line); + m_ClientConnection.handleLogLine(line); m_pLabelClientState->updateClientState(line); } } @@ -1071,3 +1082,13 @@ void MainWindow::enableClient(bool enable) { m_CoreProcess.setMode(CoreProcess::Mode::Client); } } + +void MainWindow::showAndActivate() { + if (!isMinimized() && !isHidden()) { + qDebug("window already visible"); + return; + } + + showNormal(); + activateWindow(); +} diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index de9edd72d..2087f4dd1 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -171,6 +171,7 @@ private: void showDevThanksMessage(); QString productName() const; void updateStatus(); + void showAndActivate(); VersionChecker m_VersionChecker; synergy::gui::TrayIcon m_TrayIcon; diff --git a/src/gui/src/ScreenSettingsDialog.cpp b/src/gui/src/ScreenSettingsDialog.cpp index bc0807485..4e58f601e 100644 --- a/src/gui/src/ScreenSettingsDialog.cpp +++ b/src/gui/src/ScreenSettingsDialog.cpp @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 Symless Ltd. * Copyright (C) 2008 Volker Lanz (vl@fidra.de) * * This package is free software; you can redistribute it and/or @@ -17,7 +17,8 @@ */ #include "ScreenSettingsDialog.h" -#include "Screen.h" + +#include "gui/config/Screen.h" #include "gui/styles.h" #include "validators/AliasValidator.h" #include "validators/ScreenNameValidator.h" @@ -28,6 +29,9 @@ #include using namespace synergy::gui; +using enum ScreenConfig::Modifier; +using enum ScreenConfig::SwitchCorner; +using enum ScreenConfig::Fix; ScreenSettingsDialog::ScreenSettingsDialog( QWidget *parent, Screen *pScreen, const ScreenList *pScreens) @@ -53,26 +57,28 @@ ScreenSettingsDialog::ScreenSettingsDialog( for (int i = 0; i < m_pScreen->aliases().count(); i++) new QListWidgetItem(m_pScreen->aliases()[i], m_pListAliases); - m_pComboBoxShift->setCurrentIndex(m_pScreen->modifier(Screen::Shift)); - m_pComboBoxCtrl->setCurrentIndex(m_pScreen->modifier(Screen::Ctrl)); - m_pComboBoxAlt->setCurrentIndex(m_pScreen->modifier(Screen::Alt)); - m_pComboBoxMeta->setCurrentIndex(m_pScreen->modifier(Screen::Meta)); - m_pComboBoxSuper->setCurrentIndex(m_pScreen->modifier(Screen::Super)); + m_pComboBoxShift->setCurrentIndex( + m_pScreen->modifier(static_cast(Shift))); + m_pComboBoxCtrl->setCurrentIndex(m_pScreen->modifier(static_cast(Ctrl))); + m_pComboBoxAlt->setCurrentIndex(m_pScreen->modifier(static_cast(Alt))); + m_pComboBoxMeta->setCurrentIndex(m_pScreen->modifier(static_cast(Meta))); + m_pComboBoxSuper->setCurrentIndex( + m_pScreen->modifier(static_cast(Super))); m_pCheckBoxCornerTopLeft->setChecked( - m_pScreen->switchCorner(Screen::TopLeft)); + m_pScreen->switchCorner(static_cast(TopLeft))); m_pCheckBoxCornerTopRight->setChecked( - m_pScreen->switchCorner(Screen::TopRight)); + m_pScreen->switchCorner(static_cast(TopRight))); m_pCheckBoxCornerBottomLeft->setChecked( - m_pScreen->switchCorner(Screen::BottomLeft)); + m_pScreen->switchCorner(static_cast(BottomLeft))); m_pCheckBoxCornerBottomRight->setChecked( - m_pScreen->switchCorner(Screen::BottomRight)); + m_pScreen->switchCorner(static_cast(BottomRight))); m_pSpinBoxSwitchCornerSize->setValue(m_pScreen->switchCornerSize()); - m_pCheckBoxCapsLock->setChecked(m_pScreen->fix(Screen::CapsLock)); - m_pCheckBoxNumLock->setChecked(m_pScreen->fix(Screen::NumLock)); - m_pCheckBoxScrollLock->setChecked(m_pScreen->fix(Screen::ScrollLock)); - m_pCheckBoxXTest->setChecked(m_pScreen->fix(Screen::XTest)); + m_pCheckBoxCapsLock->setChecked(m_pScreen->fix(CapsLock)); + m_pCheckBoxNumLock->setChecked(m_pScreen->fix(NumLock)); + m_pCheckBoxScrollLock->setChecked(m_pScreen->fix(ScrollLock)); + m_pCheckBoxXTest->setChecked(m_pScreen->fix(XTest)); } void ScreenSettingsDialog::accept() { @@ -102,26 +108,32 @@ void ScreenSettingsDialog::accept() { m_pScreen->addAlias(alias); } - m_pScreen->setModifier(Screen::Shift, m_pComboBoxShift->currentIndex()); - m_pScreen->setModifier(Screen::Ctrl, m_pComboBoxCtrl->currentIndex()); - m_pScreen->setModifier(Screen::Alt, m_pComboBoxAlt->currentIndex()); - m_pScreen->setModifier(Screen::Meta, m_pComboBoxMeta->currentIndex()); - m_pScreen->setModifier(Screen::Super, m_pComboBoxSuper->currentIndex()); + m_pScreen->setModifier( + static_cast(Shift), m_pComboBoxShift->currentIndex()); + m_pScreen->setModifier( + static_cast(Ctrl), m_pComboBoxCtrl->currentIndex()); + m_pScreen->setModifier(static_cast(Alt), m_pComboBoxAlt->currentIndex()); + m_pScreen->setModifier( + static_cast(Meta), m_pComboBoxMeta->currentIndex()); + m_pScreen->setModifier( + static_cast(Super), m_pComboBoxSuper->currentIndex()); m_pScreen->setSwitchCorner( - Screen::TopLeft, m_pCheckBoxCornerTopLeft->isChecked()); + static_cast(TopLeft), m_pCheckBoxCornerTopLeft->isChecked()); m_pScreen->setSwitchCorner( - Screen::TopRight, m_pCheckBoxCornerTopRight->isChecked()); + static_cast(TopRight), m_pCheckBoxCornerTopRight->isChecked()); m_pScreen->setSwitchCorner( - Screen::BottomLeft, m_pCheckBoxCornerBottomLeft->isChecked()); + static_cast(BottomLeft), m_pCheckBoxCornerBottomLeft->isChecked()); m_pScreen->setSwitchCorner( - Screen::BottomRight, m_pCheckBoxCornerBottomRight->isChecked()); + static_cast(BottomRight), m_pCheckBoxCornerBottomRight->isChecked()); m_pScreen->setSwitchCornerSize(m_pSpinBoxSwitchCornerSize->value()); - m_pScreen->setFix(Screen::CapsLock, m_pCheckBoxCapsLock->isChecked()); - m_pScreen->setFix(Screen::NumLock, m_pCheckBoxNumLock->isChecked()); - m_pScreen->setFix(Screen::ScrollLock, m_pCheckBoxScrollLock->isChecked()); - m_pScreen->setFix(Screen::XTest, m_pCheckBoxXTest->isChecked()); + m_pScreen->setFix( + static_cast(CapsLock), m_pCheckBoxCapsLock->isChecked()); + m_pScreen->setFix(static_cast(NumLock), m_pCheckBoxNumLock->isChecked()); + m_pScreen->setFix( + static_cast(ScrollLock), m_pCheckBoxScrollLock->isChecked()); + m_pScreen->setFix(static_cast(XTest), m_pCheckBoxXTest->isChecked()); QDialog::accept(); } diff --git a/src/gui/src/ScreenSetupModel.cpp b/src/gui/src/ScreenSetupModel.cpp index b68021b1c..7d26da740 100644 --- a/src/gui/src/ScreenSetupModel.cpp +++ b/src/gui/src/ScreenSetupModel.cpp @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 Symless Ltd. * Copyright (C) 2008 Volker Lanz (vl@fidra.de) * * This package is free software; you can redistribute it and/or @@ -17,7 +17,8 @@ */ #include "ScreenSetupModel.h" -#include "Screen.h" + +#include "gui/config/Screen.h" #include #include @@ -137,7 +138,7 @@ bool ScreenSetupModel::dropMimeData( Screen droppedScreen; stream >> droppedScreen; - Screen oldScreen = screen(parent.column(), parent.row()); + auto oldScreen = Screen(screen(parent.column(), parent.row())); if (!oldScreen.isNull() && sourceColumn != -1 && sourceRow != -1) { // mark the screen so it isn't deleted after the dragndrop succeeded // see ScreenSetupView::startDrag() diff --git a/src/gui/src/ScreenSetupModel.h b/src/gui/src/ScreenSetupModel.h index 1b54a4e98..24121df6d 100644 --- a/src/gui/src/ScreenSetupModel.h +++ b/src/gui/src/ScreenSetupModel.h @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 Symless Ltd. * Copyright (C) 2008 Volker Lanz (vl@fidra.de) * * This package is free software; you can redistribute it and/or @@ -16,16 +16,14 @@ * along with this program. If not, see . */ -#if !defined(SCREENSETUPMODEL__H) - -#define SCREENSETUPMODEL__H +#pragma once #include #include #include #include -#include "ScreenList.h" +#include "gui/config/ScreenList.h" class ScreenSetupView; class ServerConfigDialog; @@ -80,5 +78,3 @@ private: static const QString m_MimeType; }; - -#endif diff --git a/src/gui/src/ScreenSetupView.cpp b/src/gui/src/ScreenSetupView.cpp index 6821c826c..660e6a8f0 100644 --- a/src/gui/src/ScreenSetupView.cpp +++ b/src/gui/src/ScreenSetupView.cpp @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 Symless Ltd. * Copyright (C) 2008 Volker Lanz (vl@fidra.de) * * This package is free software; you can redistribute it and/or @@ -17,6 +17,7 @@ */ #include "ScreenSetupView.h" + #include "ScreenSettingsDialog.h" #include "ScreenSetupModel.h" diff --git a/src/gui/src/ScreenSetupView.h b/src/gui/src/ScreenSetupView.h index bd77d4bec..b6b1e40ea 100644 --- a/src/gui/src/ScreenSetupView.h +++ b/src/gui/src/ScreenSetupView.h @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 Symless Ltd. * Copyright (C) 2008 Volker Lanz (vl@fidra.de) * * This package is free software; you can redistribute it and/or @@ -16,14 +16,12 @@ * along with this program. If not, see . */ -#if !defined(SCREENSETUPVIEW__H) - -#define SCREENSETUPVIEW__H +#pragma once #include #include -#include "Screen.h" +#include "gui/config/Screen.h" class QWidget; class QMouseEvent; @@ -51,5 +49,3 @@ protected: void initViewItemOption(QStyleOptionViewItem *option) const override; void scrollTo(const QModelIndex &, ScrollHint) override {} }; - -#endif diff --git a/src/gui/src/ServerConfig.cpp b/src/gui/src/ServerConfig.cpp index f41b779e4..e6ced9e2a 100644 --- a/src/gui/src/ServerConfig.cpp +++ b/src/gui/src/ServerConfig.cpp @@ -27,6 +27,11 @@ #include #include +using namespace synergy::gui::proxy; +using enum ScreenConfig::Modifier; +using enum ScreenConfig::SwitchCorner; +using enum ScreenConfig::Fix; + static const struct { int x; int y; @@ -94,7 +99,7 @@ void ServerConfig::setupScreens() { hotkeys().clear(); // m_NumSwitchCorners is used as a fixed size array. See Screen::init() - for (int i = 0; i < NumSwitchCorners; i++) + for (int i = 0; i < static_cast(NumSwitchCorners); i++) switchCorners() << false; // There must always be screen objects for each cell in the screens QList. @@ -147,7 +152,7 @@ void ServerConfig::commit() { settings().beginWriteArray("hotkeys"); for (int i = 0; i < hotkeys().size(); i++) { settings().setArrayIndex(i); - hotkeys()[i].saveSettings(settings()); + hotkeys()[i].saveSettings(settings().get()); } settings().endArray(); @@ -189,7 +194,8 @@ void ServerConfig::recall() { setClientAddress(settings().value("clientAddress", "").toString()); readSettings( - settings(), switchCorners(), "switchCorner", false, NumSwitchCorners); + settings(), switchCorners(), "switchCorner", 0, + static_cast(NumSwitchCorners)); int numScreens = settings().beginReadArray("screens"); Q_ASSERT(numScreens <= screens().size()); @@ -206,7 +212,7 @@ void ServerConfig::recall() { for (int i = 0; i < numHotkeys; i++) { settings().setArrayIndex(i); Hotkey h; - h.loadSettings(settings()); + h.loadSettings(settings().get()); hotkeys().append(h); } settings().endArray(); @@ -449,7 +455,7 @@ void ServerConfig::addClient(const QString &clientName) { fixNoServer(m_pAppConfig->screenName(), serverIndex); } - m_Screens.addScreenByPriority(clientName); + m_Screens.addScreenByPriority(Screen(clientName)); } void ServerConfig::setConfigFile(const QString &configFile) { @@ -542,6 +548,6 @@ QString ServerConfig::getClientAddress() const { return clientAddress; } -QSettings &ServerConfig::settings() { - return *m_pAppConfig->scopes().activeSettings(); +QSettingsProxy &ServerConfig::settings() { + return m_pAppConfig->scopes().activeSettings(); } diff --git a/src/gui/src/ServerConfig.h b/src/gui/src/ServerConfig.h index f4764c567..741b954e0 100644 --- a/src/gui/src/ServerConfig.h +++ b/src/gui/src/ServerConfig.h @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 Symless Ltd. * Copyright (C) 2008 Volker Lanz (vl@fidra.de) * * This package is free software; you can redistribute it and/or @@ -19,9 +19,9 @@ #pragma once #include "Hotkey.h" -#include "ScreenConfig.h" -#include "ScreenList.h" #include "gui/config/IServerConfig.h" +#include "gui/config/ScreenConfig.h" +#include "gui/config/ScreenList.h" #include @@ -37,6 +37,8 @@ class MainWindow; class AppConfig; class ServerConfig : public ScreenConfig, public synergy::gui::IServerConfig { + using QSettingsProxy = synergy::gui::proxy::QSettingsProxy; + friend class ServerConfigDialog; friend QTextStream & operator<<(QTextStream &outStream, const ServerConfig &config); @@ -49,7 +51,15 @@ public: bool operator==(const ServerConfig &sc) const; - const ScreenList &screens() const { return m_Screens; } + // + // Overrides + // + const ScreenList &screens() const override { return m_Screens; } + bool enableDragAndDrop() const override { return m_EnableDragAndDrop; } + + // + // New methods + // int numColumns() const { return m_Columns; } int numRows() const { return m_Rows; } bool hasHeartbeat() const { return m_HasHeartbeat; } @@ -64,23 +74,29 @@ public: int switchCornerSize() const { return m_SwitchCornerSize; } const QList &switchCorners() const { return m_SwitchCorners; } const HotkeyList &hotkeys() const { return m_Hotkeys; } - bool enableDragAndDrop() const override { return m_EnableDragAndDrop; } bool disableLockToScreen() const { return m_DisableLockToScreen; } bool clipboardSharing() const { return m_ClipboardSharing; } size_t clipboardSharingSize() const { return m_ClipboardSharingSize; } static size_t defaultClipboardSharingSize(); - void commit(); + // + // Overrides + // bool save(const QString &fileName) const override; + bool screenExists(const QString &screenName) const override; void save(QFile &file) const override; + bool isFull() const override; + + // + // New methods + // + void commit(); int numScreens() const; int autoAddScreen(const QString name); const QString &getServerName() const; void updateServerName(); const QString &configFile() const; bool useExternalConfig() const; - bool isFull() const override; - bool screenExists(const QString &screenName) const override; void addClient(const QString &clientName); QString getClientAddress() const; void setClientAddress(const QString &address); @@ -88,7 +104,7 @@ public: private: void recall(); void setupScreens(); - QSettings &settings(); + QSettingsProxy &settings(); ScreenList &screens() { return m_Screens; } void setScreens(const ScreenList &screens) { m_Screens = screens; } void addScreen(const Screen &screen) { m_Screens.append(screen); } diff --git a/src/gui/src/ServerConfigDialog.cpp b/src/gui/src/ServerConfigDialog.cpp index e82ae9a3f..f1649227f 100644 --- a/src/gui/src/ServerConfigDialog.cpp +++ b/src/gui/src/ServerConfigDialog.cpp @@ -28,6 +28,8 @@ #include #include +using enum ScreenConfig::SwitchCorner; + ServerConfigDialog::ServerConfigDialog( QWidget *parent, ServerConfig &config, AppConfig &appConfig) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint), @@ -65,13 +67,13 @@ ServerConfigDialog::ServerConfigDialog( m_pSpinBoxSwitchDoubleTap->setValue(serverConfig().switchDoubleTap()); m_pCheckBoxCornerTopLeft->setChecked( - serverConfig().switchCorner(ScreenConfig::TopLeft)); + serverConfig().switchCorner(static_cast(TopLeft))); m_pCheckBoxCornerTopRight->setChecked( - serverConfig().switchCorner(ScreenConfig::TopRight)); + serverConfig().switchCorner(static_cast(TopRight))); m_pCheckBoxCornerBottomLeft->setChecked( - serverConfig().switchCorner(ScreenConfig::BottomLeft)); + serverConfig().switchCorner(static_cast(BottomLeft))); m_pCheckBoxCornerBottomRight->setChecked( - serverConfig().switchCorner(ScreenConfig::BottomRight)); + serverConfig().switchCorner(static_cast(BottomRight))); m_pSpinBoxSwitchCornerSize->setValue(serverConfig().switchCornerSize()); m_pCheckBoxDisableLockToScreen->setChecked( serverConfig().disableLockToScreen()); @@ -189,25 +191,25 @@ ServerConfigDialog::ServerConfigDialog( connect( m_pCheckBoxCornerTopLeft, &QCheckBox::stateChanged, this, [this](const int &v) { - serverConfig().setSwitchCorner(ScreenConfig::TopLeft, v); + serverConfig().setSwitchCorner(static_cast(TopLeft), v); onChange(); }); connect( m_pCheckBoxCornerTopRight, &QCheckBox::stateChanged, this, [this](const int &v) { - serverConfig().setSwitchCorner(ScreenConfig::TopRight, v); + serverConfig().setSwitchCorner(static_cast(TopRight), v); onChange(); }); connect( m_pCheckBoxCornerBottomLeft, &QCheckBox::stateChanged, this, [this](const int &v) { - serverConfig().setSwitchCorner(ScreenConfig::BottomLeft, v); + serverConfig().setSwitchCorner(static_cast(BottomLeft), v); onChange(); }); connect( m_pCheckBoxCornerBottomRight, &QCheckBox::stateChanged, this, [this](const int &v) { - serverConfig().setSwitchCorner(ScreenConfig::BottomRight, v); + serverConfig().setSwitchCorner(static_cast(BottomRight), v); onChange(); }); connect( diff --git a/src/gui/src/SettingsDialog.cpp b/src/gui/src/SettingsDialog.cpp index 637031c86..7b1b2622f 100644 --- a/src/gui/src/SettingsDialog.cpp +++ b/src/gui/src/SettingsDialog.cpp @@ -40,12 +40,15 @@ using namespace synergy::gui; const char *const kProProductName = "Synergy 1 Pro"; SettingsDialog::SettingsDialog( - QWidget *parent, AppConfig &config, const License &license) + MainWindow *parent, AppConfig &appConfig, const IServerConfig &serverConfig, + const License &license, const CoreProcess &coreProcess) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint), Ui::SettingsDialogBase(), - m_appConfig(config), + m_appConfig(appConfig), + m_serverConfig(serverConfig), m_license(license), - m_tlsUtility(config, license) { + m_coreProcess(coreProcess), + m_tlsUtility(appConfig, license) { setupUi(this); @@ -53,124 +56,18 @@ SettingsDialog::SettingsDialog( // the developer was looking at, and it's easy to accidentally save that. m_pTabWidget->setCurrentIndex(0); - m_pMainWindow = dynamic_cast(parent); - loadFromConfig(); - m_wasOriginallySystemScope = appConfig().isActiveScopeSystem(); - updateControlsEnabled(); - - const auto &serveConfig = m_pMainWindow->serverConfig(); + m_wasOriginallySystemScope = m_appConfig.isActiveScopeSystem(); + updateControls(); m_pScreenNameError = new validators::ValidationError(this); m_pLineEditScreenName->setValidator(new validators::ScreenNameValidator( - m_pLineEditScreenName, m_pScreenNameError, &serveConfig.screens())); + m_pLineEditScreenName, m_pScreenNameError, &serverConfig.screens())); } -void SettingsDialog::accept() { - if (!m_pLineEditScreenName->hasAcceptableInput()) { - QMessageBox::warning( - this, tr("Invalid screen name"), m_pScreenNameError->message()); - return; - } - - appConfig().setLoadFromSystemScope(m_pRadioSystemScope->isChecked()); - appConfig().setScreenName(m_pLineEditScreenName->text()); - appConfig().setPort(m_pSpinBoxPort->value()); - appConfig().setNetworkInterface(m_pLineEditInterface->text()); - appConfig().setLogLevel(m_pComboLogLevel->currentIndex()); - appConfig().setLogToFile(m_pCheckBoxLogToFile->isChecked()); - appConfig().setLogFilename(m_pLineEditLogFilename->text()); - appConfig().setElevateMode( - static_cast(m_pComboElevate->currentIndex())); - appConfig().setAutoHide(m_pCheckBoxAutoHide->isChecked()); - appConfig().setPreventSleep(m_pCheckBoxPreventSleep->isChecked()); - appConfig().setTlsCertPath(m_pLineEditCertificatePath->text()); - appConfig().setTlsKeyLength(m_pComboBoxKeyLength->currentText().toInt()); - appConfig().setTlsEnabled(m_pCheckBoxEnableCrypto->isChecked()); - appConfig().setLanguageSync(m_pCheckBoxLanguageSync->isChecked()); - appConfig().setInvertScrollDirection(m_pCheckBoxScrollDirection->isChecked()); - appConfig().setEnableService(m_pCheckBoxServiceEnabled->isChecked()); - appConfig().setCloseToTray(m_pCheckBoxCloseToTray->isChecked()); - appConfig().setInvertConnection(m_pInvertConnection->isChecked()); - - QDialog::accept(); -} - -void SettingsDialog::reject() { - // restore original system scope value on reject. - if (appConfig().isActiveScopeSystem() != m_wasOriginallySystemScope) { - appConfig().setLoadFromSystemScope(m_wasOriginallySystemScope); - } - - QDialog::reject(); -} - -void SettingsDialog::loadFromConfig() { - - m_pLineEditScreenName->setText(appConfig().screenName()); - m_pSpinBoxPort->setValue(appConfig().port()); - m_pLineEditInterface->setText(appConfig().networkInterface()); - m_pComboLogLevel->setCurrentIndex(appConfig().logLevel()); - m_pCheckBoxLogToFile->setChecked(appConfig().logToFile()); - m_pLineEditLogFilename->setText(appConfig().logFilename()); - m_pCheckBoxAutoHide->setChecked(appConfig().autoHide()); - m_pCheckBoxPreventSleep->setChecked(appConfig().preventSleep()); - m_pLineEditCertificatePath->setText(appConfig().tlsCertPath()); - m_pCheckBoxEnableCrypto->setChecked(m_appConfig.tlsEnabled()); - m_pCheckBoxLanguageSync->setChecked(m_appConfig.languageSync()); - m_pCheckBoxScrollDirection->setChecked(m_appConfig.invertScrollDirection()); - m_pCheckBoxServiceEnabled->setChecked(m_appConfig.enableService()); - m_pCheckBoxCloseToTray->setChecked(m_appConfig.closeToTray()); - - if (m_appConfig.isActiveScopeSystem()) { - m_pRadioSystemScope->setChecked(true); - } else { - m_pRadioUserScope->setChecked(true); - } - - m_pInvertConnection->setChecked(m_appConfig.invertConnection()); - m_pInvertConnection->setEnabled( - m_license.productEdition() == Edition::kBusiness); - - updateTlsControls(); -} - -void SettingsDialog::updateTlsControls() { - if (QFile(appConfig().tlsCertPath()).exists()) { - updateKeyLengthOnFile(appConfig().tlsCertPath()); - } else { - const auto keyLengthText = QString::number(appConfig().tlsKeyLength()); - m_pComboBoxKeyLength->setCurrentIndex( - m_pComboBoxKeyLength->findText(keyLengthText)); - } - - m_pCheckBoxEnableCrypto->setChecked(m_appConfig.tlsEnabled()); - - updateTlsControlsEnabled(); -} - -void SettingsDialog::updateTlsControlsEnabled() { - bool writable = appConfig().isActiveScopeWritable(); - auto clientMode = appConfig().clientGroupChecked(); - auto tlsAvailable = m_tlsUtility.isAvailableAndEnabled(); - auto tlsChecked = m_pCheckBoxEnableCrypto->isChecked(); - auto enabled = writable && !clientMode && tlsAvailable && tlsChecked; - - qDebug( - "tls enabled=%d, writable=%d, client=%d, available=%d, checked=%d", - enabled, writable, clientMode, tlsAvailable, tlsChecked); - - m_pLabelKeyLength->setEnabled(enabled); - m_pComboBoxKeyLength->setEnabled(enabled); - m_pLabelCertificate->setEnabled(enabled); - m_pLineEditCertificatePath->setEnabled(enabled); - m_pPushButtonBrowseCert->setEnabled(enabled); - m_pPushButtonRegenCert->setEnabled(enabled); -} - -bool SettingsDialog::isClientMode() const { - return m_pMainWindow->coreMode() == MainWindow::CoreMode::Client; -} +// +// Auto-connect slots +// void SettingsDialog::on_m_pCheckBoxLogToFile_stateChanged(int i) { bool checked = i == 2; @@ -206,9 +103,9 @@ void SettingsDialog::on_m_pCheckBoxEnableCrypto_clicked(bool) { void SettingsDialog::on_m_pRadioSystemScope_toggled(bool checked) { // We only need to test the System scoped Radio as they are connected - appConfig().setLoadFromSystemScope(checked); + m_appConfig.setLoadFromSystemScope(checked); loadFromConfig(); - updateControlsEnabled(); + updateControls(); } void SettingsDialog::on_m_pPushButtonBrowseCert_clicked() { @@ -233,18 +130,6 @@ void SettingsDialog::on_m_pComboBoxKeyLength_currentIndexChanged(int index) { updateTlsRegenerateButton(); } -void SettingsDialog::updateTlsRegenerateButton() { - const auto keyLength = m_pComboBoxKeyLength->currentText().toInt(); - auto keyChanged = appConfig().tlsKeyLength() != keyLength; - auto pathChanged = - appConfig().tlsCertPath() != m_pLineEditCertificatePath->text(); - // NOR the above bools, if any have changed regen should be disabled as it - // will be done on save - auto nor = !(keyChanged || pathChanged); - m_pPushButtonRegenCert->setEnabled( - nor && m_pCheckBoxEnableCrypto->isChecked()); -} - void SettingsDialog::on_m_pPushButtonRegenCert_clicked() { if (m_tlsUtility.generateCertificate()) { QMessageBox::information( @@ -253,6 +138,134 @@ void SettingsDialog::on_m_pPushButtonRegenCert_clicked() { } } +void SettingsDialog::on_m_pCheckBoxServiceEnabled_toggled(bool) { + updateControls(); +} + +// +// End of auto-connect slots +// + +void SettingsDialog::accept() { + if (!m_pLineEditScreenName->hasAcceptableInput()) { + QMessageBox::warning( + this, tr("Invalid screen name"), m_pScreenNameError->message()); + return; + } + + m_appConfig.setLoadFromSystemScope(m_pRadioSystemScope->isChecked()); + m_appConfig.setScreenName(m_pLineEditScreenName->text()); + m_appConfig.setPort(m_pSpinBoxPort->value()); + m_appConfig.setNetworkInterface(m_pLineEditInterface->text()); + m_appConfig.setLogLevel(m_pComboLogLevel->currentIndex()); + m_appConfig.setLogToFile(m_pCheckBoxLogToFile->isChecked()); + m_appConfig.setLogFilename(m_pLineEditLogFilename->text()); + m_appConfig.setElevateMode( + static_cast(m_pComboElevate->currentIndex())); + m_appConfig.setAutoHide(m_pCheckBoxAutoHide->isChecked()); + m_appConfig.setPreventSleep(m_pCheckBoxPreventSleep->isChecked()); + m_appConfig.setTlsCertPath(m_pLineEditCertificatePath->text()); + m_appConfig.setTlsKeyLength(m_pComboBoxKeyLength->currentText().toInt()); + m_appConfig.setTlsEnabled(m_pCheckBoxEnableCrypto->isChecked()); + m_appConfig.setLanguageSync(m_pCheckBoxLanguageSync->isChecked()); + m_appConfig.setInvertScrollDirection(m_pCheckBoxScrollDirection->isChecked()); + m_appConfig.setEnableService(m_pCheckBoxServiceEnabled->isChecked()); + m_appConfig.setCloseToTray(m_pCheckBoxCloseToTray->isChecked()); + m_appConfig.setInvertConnection(m_pInvertConnection->isChecked()); + + QDialog::accept(); +} + +void SettingsDialog::reject() { + // restore original system scope value on reject. + if (m_appConfig.isActiveScopeSystem() != m_wasOriginallySystemScope) { + m_appConfig.setLoadFromSystemScope(m_wasOriginallySystemScope); + } + + QDialog::reject(); +} + +void SettingsDialog::loadFromConfig() { + + m_pLineEditScreenName->setText(m_appConfig.screenName()); + m_pSpinBoxPort->setValue(m_appConfig.port()); + m_pLineEditInterface->setText(m_appConfig.networkInterface()); + m_pComboLogLevel->setCurrentIndex(m_appConfig.logLevel()); + m_pCheckBoxLogToFile->setChecked(m_appConfig.logToFile()); + m_pLineEditLogFilename->setText(m_appConfig.logFilename()); + m_pCheckBoxAutoHide->setChecked(m_appConfig.autoHide()); + m_pCheckBoxPreventSleep->setChecked(m_appConfig.preventSleep()); + m_pLineEditCertificatePath->setText(m_appConfig.tlsCertPath()); + m_pCheckBoxEnableCrypto->setChecked(m_appConfig.tlsEnabled()); + m_pCheckBoxLanguageSync->setChecked(m_appConfig.languageSync()); + m_pCheckBoxScrollDirection->setChecked(m_appConfig.invertScrollDirection()); + m_pCheckBoxServiceEnabled->setChecked(m_appConfig.enableService()); + m_pCheckBoxCloseToTray->setChecked(m_appConfig.closeToTray()); + m_pComboElevate->setCurrentIndex(static_cast(m_appConfig.elevateMode())); + + if (m_appConfig.isActiveScopeSystem()) { + m_pRadioSystemScope->setChecked(true); + } else { + m_pRadioUserScope->setChecked(true); + } + + m_pInvertConnection->setChecked(m_appConfig.invertConnection()); + m_pInvertConnection->setEnabled( + m_license.productEdition() == Edition::kBusiness); + + updateTlsControls(); +} + +void SettingsDialog::updateTlsControls() { + if (QFile(m_appConfig.tlsCertPath()).exists()) { + updateKeyLengthOnFile(m_appConfig.tlsCertPath()); + } else { + const auto keyLengthText = QString::number(m_appConfig.tlsKeyLength()); + m_pComboBoxKeyLength->setCurrentIndex( + m_pComboBoxKeyLength->findText(keyLengthText)); + } + + m_pCheckBoxEnableCrypto->setChecked(m_appConfig.tlsEnabled()); + + updateTlsControlsEnabled(); +} + +void SettingsDialog::updateTlsControlsEnabled() { + const auto writable = m_appConfig.isActiveScopeWritable(); + const auto clientMode = m_appConfig.clientGroupChecked(); + const auto tlsAvailable = m_tlsUtility.isAvailableAndEnabled(); + const auto tlsChecked = m_pCheckBoxEnableCrypto->isChecked(); + + auto enabled = writable && !clientMode && tlsAvailable && tlsChecked; + + qDebug( + "tls enabled=%d, writable=%d, client=%d, available=%d, checked=%d", + enabled, writable, clientMode, tlsAvailable, tlsChecked); + + m_pLabelKeyLength->setEnabled(enabled); + m_pComboBoxKeyLength->setEnabled(enabled); + m_pLabelCertificate->setEnabled(enabled); + m_pLineEditCertificatePath->setEnabled(enabled); + m_pPushButtonBrowseCert->setEnabled(enabled); + m_pPushButtonRegenCert->setEnabled(enabled); +} + +bool SettingsDialog::isClientMode() const { + return m_coreProcess.mode() == MainWindow::CoreMode::Client; +} + +void SettingsDialog::updateTlsRegenerateButton() { + const auto writable = m_appConfig.isActiveScopeWritable(); + const auto keyLength = m_pComboBoxKeyLength->currentText().toInt(); + const auto path = m_pLineEditCertificatePath->text(); + const auto keyChanged = m_appConfig.tlsKeyLength() != keyLength; + const auto pathChanged = m_appConfig.tlsCertPath() != path; + const auto tlsEnabled = m_pCheckBoxEnableCrypto->isChecked(); + + m_pPushButtonRegenCert->setEnabled( + writable && tlsEnabled && (keyChanged || pathChanged)); +} + void SettingsDialog::updateKeyLengthOnFile(const QString &path) { TlsCertificate ssl; if (!QFile(path).exists()) { @@ -262,52 +275,48 @@ void SettingsDialog::updateKeyLengthOnFile(const QString &path) { auto length = ssl.getCertKeyLength(path); auto index = m_pComboBoxKeyLength->findText(QString::number(length)); m_pComboBoxKeyLength->setCurrentIndex(index); - appConfig().setTlsKeyLength(length); + m_appConfig.setTlsKeyLength(length); } -void SettingsDialog::updateControlsEnabled() { - bool writable = appConfig().isActiveScopeWritable(); +void SettingsDialog::updateControls() { + +#if defined(Q_OS_WIN) + const auto serviceAvailable = true; +#else + // service not supported on unix yet, so always disable. + const auto serviceAvailable = false; + m_pGroupService->setTitle("Service (Windows only)"); +#endif + + const bool writable = m_appConfig.isActiveScopeWritable(); + const bool serviceChecked = m_pCheckBoxServiceEnabled->isChecked(); + const bool logToFile = m_pCheckBoxLogToFile->isChecked(); m_pLineEditScreenName->setEnabled(writable); m_pSpinBoxPort->setEnabled(writable); m_pLineEditInterface->setEnabled(writable); m_pComboLogLevel->setEnabled(writable); m_pCheckBoxLogToFile->setEnabled(writable); - m_pComboElevate->setEnabled(writable); m_pCheckBoxAutoHide->setEnabled(writable); m_pCheckBoxPreventSleep->setEnabled(writable); m_pLineEditCertificatePath->setEnabled(writable); m_pComboBoxKeyLength->setEnabled(writable); m_pPushButtonBrowseCert->setEnabled(writable); m_pCheckBoxEnableCrypto->setEnabled(writable); - m_pCheckBoxServiceEnabled->setEnabled(writable); m_pCheckBoxCloseToTray->setEnabled(writable); + m_pCheckBoxServiceEnabled->setEnabled(writable && serviceAvailable); + m_pLabelElevate->setEnabled(writable && serviceChecked && serviceAvailable); + m_pComboElevate->setEnabled(writable && serviceChecked && serviceAvailable); + m_pCheckBoxLanguageSync->setEnabled(writable && isClientMode()); m_pCheckBoxScrollDirection->setEnabled(writable && isClientMode()); -#if !defined(Q_OS_WIN) - m_pCheckBoxServiceEnabled->setEnabled(false); -#endif - - m_pLabelLogPath->setEnabled(writable && m_pCheckBoxLogToFile->isChecked()); - m_pLineEditLogFilename->setEnabled( - writable && m_pCheckBoxLogToFile->isChecked()); - m_pButtonBrowseLog->setEnabled(writable && m_pCheckBoxLogToFile->isChecked()); + m_pLabelLogPath->setEnabled(writable && logToFile); + m_pLineEditLogFilename->setEnabled(writable && logToFile); + m_pButtonBrowseLog->setEnabled(writable && logToFile); updateTlsControlsEnabled(); - - if (writable) { - updateTlsRegenerateButton(); - } - -#if defined(Q_OS_WIN) - m_pComboElevate->setCurrentIndex(static_cast(appConfig().elevateMode())); -#else - // elevate checkbox is only usable on ms windows. - m_pLabelElevate->setEnabled(false); - m_pComboElevate->setEnabled(false); -#endif - + updateTlsRegenerateButton(); updateTlsControls(); } diff --git a/src/gui/src/SettingsDialog.h b/src/gui/src/SettingsDialog.h index aa0e060aa..0f6a3d51b 100644 --- a/src/gui/src/SettingsDialog.h +++ b/src/gui/src/SettingsDialog.h @@ -20,6 +20,9 @@ #include "ui_SettingsDialogBase.h" +#include "gui/config/AppConfig.h" +#include "gui/config/IServerConfig.h" +#include "gui/core/CoreProcess.h" #include "gui/core/CoreTool.h" #include "gui/tls/TlsUtility.h" #include "license/License.h" @@ -28,15 +31,19 @@ #include class MainWindow; -class AppConfig; class SettingsDialog : public QDialog, public Ui::SettingsDialogBase { + using IServerConfig = synergy::gui::IServerConfig; + using CoreProcess = synergy::gui::CoreProcess; + using License = synergy::license::License; + Q_OBJECT public: SettingsDialog( - QWidget *parent, AppConfig &config, - const synergy::license::License &license); + MainWindow *parent, AppConfig &appConfig, + const IServerConfig &serverConfig, const License &license, + const CoreProcess &coreProcess); static QString browseForSynergyc( QWidget *parent, const QString &programDir, const QString &coreClientName); @@ -44,10 +51,19 @@ public: QWidget *parent, const QString &programDir, const QString &coreServerName); -protected: +private slots: + void on_m_pCheckBoxEnableCrypto_clicked(bool checked); + void on_m_pCheckBoxLogToFile_stateChanged(int); + void on_m_pButtonBrowseLog_clicked(); + void on_m_pRadioSystemScope_toggled(bool checked); + void on_m_pPushButtonBrowseCert_clicked(); + void on_m_pComboBoxKeyLength_currentIndexChanged(int index); + void on_m_pPushButtonRegenCert_clicked(); + void on_m_pCheckBoxServiceEnabled_toggled(bool checked); + +private: void accept() override; void reject() override; - AppConfig &appConfig() { return m_appConfig; } /// @brief Load all settings. void loadFromConfig(); @@ -59,31 +75,23 @@ protected: void updateKeyLengthOnFile(const QString &path); /// @brief Enables controls when they should be. - void updateControlsEnabled(); + void updateControls(); bool isClientMode() const; void updateTlsControls(); void updateTlsControlsEnabled(); -private: - MainWindow *m_pMainWindow; - AppConfig &m_appConfig; [[no_unique_address]] CoreTool m_coreTool; - const synergy::license::License &m_license; - synergy::gui::TlsUtility m_tlsUtility; validators::ValidationError *m_pScreenNameError; /// @brief Stores settings scope at start of settings dialog - /// This is neccessary to restore state if user changes + /// This is necessary to restore state if user changes /// the scope and doesn't save changes bool m_wasOriginallySystemScope = false; -private slots: - void on_m_pCheckBoxEnableCrypto_clicked(bool checked); - void on_m_pCheckBoxLogToFile_stateChanged(int); - void on_m_pButtonBrowseLog_clicked(); - void on_m_pRadioSystemScope_toggled(bool checked); - void on_m_pPushButtonBrowseCert_clicked(); - void on_m_pComboBoxKeyLength_currentIndexChanged(int index); - void on_m_pPushButtonRegenCert_clicked(); + AppConfig &m_appConfig; + const IServerConfig &m_serverConfig; + const License &m_license; + const CoreProcess &m_coreProcess; + synergy::gui::TlsUtility m_tlsUtility; }; diff --git a/src/gui/src/validators/ScreenDuplicationsValidator.cpp b/src/gui/src/validators/ScreenDuplicationsValidator.cpp index 7e9545c1b..168b33518 100644 --- a/src/gui/src/validators/ScreenDuplicationsValidator.cpp +++ b/src/gui/src/validators/ScreenDuplicationsValidator.cpp @@ -1,7 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2021 Symless Ltd. - * Copyright (C) 2008 Volker Lanz (vl@fidra.de) + * Copyright (C) 2021 Symless Ltd. * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -15,6 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + #include "ScreenDuplicationsValidator.h" namespace validators { diff --git a/src/gui/src/validators/ScreenDuplicationsValidator.h b/src/gui/src/validators/ScreenDuplicationsValidator.h index 075048977..d54ce635d 100644 --- a/src/gui/src/validators/ScreenDuplicationsValidator.h +++ b/src/gui/src/validators/ScreenDuplicationsValidator.h @@ -1,7 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2021 Symless Ltd. - * Copyright (C) 2008 Volker Lanz (vl@fidra.de) + * Copyright (C) 2021 Symless Ltd. * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -15,11 +14,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef SCREENDUPLICATIONSVALIDATOR_H -#define SCREENDUPLICATIONSVALIDATOR_H + +#pragma once #include "IStringValidator.h" -#include "ScreenList.h" + +#include "gui/config/ScreenList.h" namespace validators { @@ -35,5 +35,3 @@ public: }; } // namespace validators - -#endif // SCREENDUPLICATIONSVALIDATOR_H diff --git a/src/gui/src/validators/ScreenNameValidator.cpp b/src/gui/src/validators/ScreenNameValidator.cpp index 74edf74df..faec86e56 100644 --- a/src/gui/src/validators/ScreenNameValidator.cpp +++ b/src/gui/src/validators/ScreenNameValidator.cpp @@ -1,7 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2021 Symless Ltd. - * Copyright (C) 2008 Volker Lanz (vl@fidra.de) + * Copyright (C) 2021 Symless Ltd. * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License diff --git a/src/gui/src/validators/ScreenNameValidator.h b/src/gui/src/validators/ScreenNameValidator.h index 872848106..3afdc0b4d 100644 --- a/src/gui/src/validators/ScreenNameValidator.h +++ b/src/gui/src/validators/ScreenNameValidator.h @@ -18,7 +18,7 @@ #pragma once #include "LineEditValidator.h" -#include "ScreenList.h" +#include "gui/config/ScreenList.h" #include "validators/ValidationError.h" namespace validators { diff --git a/src/lib/gui/config/AppConfig.cpp b/src/lib/gui/config/AppConfig.cpp index cede9cce0..7130ba5d4 100644 --- a/src/lib/gui/config/AppConfig.cpp +++ b/src/lib/gui/config/AppConfig.cpp @@ -520,7 +520,7 @@ bool AppConfig::useInternalConfig() const { return m_UseInternalConfig; } bool AppConfig::clientGroupChecked() const { return m_ClientGroupChecked; } -QString AppConfig::serverHostname() const { return m_ServerHostname; } +const QString &AppConfig::serverHostname() const { return m_ServerHostname; } void AppConfig::setActivationHasRun(bool value) { m_ActivationHasRun = value; } diff --git a/src/lib/gui/config/AppConfig.h b/src/lib/gui/config/AppConfig.h index 0e5b158ea..ebe7e9506 100644 --- a/src/lib/gui/config/AppConfig.h +++ b/src/lib/gui/config/AppConfig.h @@ -136,10 +136,9 @@ public: */ void commit(); - /** - * Getters - */ - + // + // Getters (overrides) + // ProcessMode processMode() const override; ElevateMode elevateMode() const override; bool tlsEnabled() const override; @@ -161,6 +160,12 @@ public: bool useExternalConfig() const override; const QString &configFile() const override; const QString &networkInterface() const override; + const QString &serverHostname() const override; + + // + // Getters (new methods) + // + bool isActiveScopeWritable() const; bool isActiveScopeSystem() const; int logLevel() const; @@ -172,7 +177,6 @@ public: bool serverGroupChecked() const; bool useInternalConfig() const; bool clientGroupChecked() const; - QString serverHostname() const; QString lastVersion() const; bool enableService() const; bool closeToTray() const; @@ -182,9 +186,9 @@ public: bool showDevThanks() const; bool showCloseReminder() const; - /** - * Setters - */ + // + // Setters + // void setActivationHasRun(bool value); void setScreenName(const QString &s); diff --git a/src/lib/gui/config/ConfigScopes.cpp b/src/lib/gui/config/ConfigScopes.cpp index 121d5ea9b..facfb06cd 100644 --- a/src/lib/gui/config/ConfigScopes.cpp +++ b/src/lib/gui/config/ConfigScopes.cpp @@ -30,6 +30,8 @@ const auto kUnixSystemConfigPath = "/usr/local/etc/symless/"; namespace synergy::gui { +using namespace proxy; + QString getSystemSettingPath() { const QString settingFilename(kSystemConfigFilename); #if defined(Q_OS_WIN) @@ -92,6 +94,7 @@ ConfigScopes::ConfigScopes() { // 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(); + m_userSettingsProxy.set(*m_pUserSettings); qDebug() << "user settings path:" << m_pUserSettings->fileName(); @@ -104,6 +107,7 @@ ConfigScopes::ConfigScopes() { m_pSystemSettings = std::make_unique( QSettings::Format::IniFormat, QSettings::Scope::SystemScope, orgName, appName); + m_systemSettingsProxy.set(*m_pSystemSettings); qDebug() << "system settings path:" << m_pSystemSettings->fileName(); @@ -124,7 +128,7 @@ void ConfigScopes::save() { } bool ConfigScopes::isActiveScopeWritable() const { - return activeSettings()->isWritable(); + return activeSettings().isWritable(); } void ConfigScopes::setActiveScope(ConfigScopes::Scope scope) { @@ -140,15 +144,23 @@ bool ConfigScopes::scopeContains(const QString &name, Scope scope) const { case Scope::System: return m_pSystemSettings->contains(name); default: - return activeSettings()->contains(name); + return activeSettings().contains(name); } } -QSettings *ConfigScopes::activeSettings() const { +QSettingsProxy &ConfigScopes::activeSettings() { if (m_currentScope == Scope::User) { - return m_pUserSettings.get(); + return m_userSettingsProxy; } else { - return m_pSystemSettings.get(); + return m_systemSettingsProxy; + } +} + +const QSettingsProxy &ConfigScopes::activeSettings() const { + if (m_currentScope == Scope::User) { + return m_userSettingsProxy; + } else { + return m_systemSettingsProxy; } } @@ -160,7 +172,7 @@ QVariant ConfigScopes::getFromScope( case Scope::System: return m_pSystemSettings->value(name, defaultValue); default: - return activeSettings()->value(name, defaultValue); + return activeSettings().value(name, defaultValue); } } @@ -174,7 +186,7 @@ void ConfigScopes::setInScope( m_pSystemSettings->setValue(name, value); break; default: - activeSettings()->setValue(name, value); + activeSettings().setValue(name, value); break; } } diff --git a/src/lib/gui/config/ConfigScopes.h b/src/lib/gui/config/ConfigScopes.h index 00c5db925..85668a0b2 100644 --- a/src/lib/gui/config/ConfigScopes.h +++ b/src/lib/gui/config/ConfigScopes.h @@ -28,6 +28,8 @@ namespace synergy::gui { /// @brief Encapsulates Qt config for both user and global scopes. class ConfigScopes : public QObject, public IConfigScopes { + using QSettingsProxy = synergy::gui::proxy::QSettingsProxy; + Q_OBJECT public: @@ -47,7 +49,8 @@ public: Scope scope = Scope::Current) const override; void setActiveScope(Scope scope = Scope::User) override; Scope activeScope() const override; - QSettings *activeSettings() const override; + QSettingsProxy &activeSettings() override; + const QSettingsProxy &activeSettings() const override; signals: void ready(); @@ -57,6 +60,8 @@ private: Scope m_currentScope = Scope::User; std::unique_ptr m_pUserSettings; std::unique_ptr m_pSystemSettings; + QSettingsProxy m_userSettingsProxy; + QSettingsProxy m_systemSettingsProxy; }; } // namespace synergy::gui diff --git a/src/lib/gui/config/IAppConfig.h b/src/lib/gui/config/IAppConfig.h index bd958da20..460c25b9a 100644 --- a/src/lib/gui/config/IAppConfig.h +++ b/src/lib/gui/config/IAppConfig.h @@ -49,6 +49,7 @@ public: virtual bool useExternalConfig() const = 0; virtual const QString &configFile() const = 0; virtual const QString &networkInterface() const = 0; + virtual const QString &serverHostname() const = 0; }; } // namespace synergy::gui diff --git a/src/lib/gui/config/IConfigScopes.h b/src/lib/gui/config/IConfigScopes.h index 4d78bf941..a6b9e409b 100644 --- a/src/lib/gui/config/IConfigScopes.h +++ b/src/lib/gui/config/IConfigScopes.h @@ -17,6 +17,8 @@ #pragma once +#include "gui/proxy/QSettingsProxy.h" + #include #include #include @@ -24,6 +26,8 @@ namespace synergy::gui { class IConfigScopes { + using QSettingsProxy = synergy::gui::proxy::QSettingsProxy; + public: enum class Scope { Current, System, User }; @@ -32,7 +36,8 @@ public: virtual Scope activeScope() const = 0; virtual void setActiveScope(Scope scope = Scope::User) = 0; virtual bool isActiveScopeWritable() const = 0; - virtual QSettings *activeSettings() const = 0; + virtual QSettingsProxy &activeSettings() = 0; + virtual const QSettingsProxy &activeSettings() const = 0; /** * @brief Signals to listeners that the settings that they should read. @@ -40,7 +45,7 @@ public: virtual void signalReady() = 0; /** - * @brief Signalls to listeners to save and calls `sync` on underlying Qt + * @brief Signals to listeners to save and calls `sync` on underlying Qt * config. * */ diff --git a/src/lib/gui/config/IServerConfig.h b/src/lib/gui/config/IServerConfig.h index 313dda7f9..1114c97d3 100644 --- a/src/lib/gui/config/IServerConfig.h +++ b/src/lib/gui/config/IServerConfig.h @@ -20,6 +20,8 @@ #include #include +#include "ScreenList.h" + namespace synergy::gui { class IServerConfig { @@ -30,6 +32,7 @@ public: virtual bool save(const QString &fileName) const = 0; virtual void save(QFile &file) const = 0; virtual bool enableDragAndDrop() const = 0; + virtual const ScreenList &screens() const = 0; }; } // namespace synergy::gui diff --git a/src/gui/src/Screen.cpp b/src/lib/gui/config/Screen.cpp similarity index 73% rename from src/gui/src/Screen.cpp rename to src/lib/gui/config/Screen.cpp index bb5f0c688..1ebde0ad3 100644 --- a/src/gui/src/Screen.cpp +++ b/src/lib/gui/config/Screen.cpp @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 Symless Ltd. * Copyright (C) 2008 Volker Lanz (vl@fidra.de) * * This package is free software; you can redistribute it and/or @@ -17,19 +17,19 @@ */ #include "Screen.h" +#include "config/ScreenConfig.h" #include #include -Screen::Screen() - : m_Pixmap(QPixmap(":res/icons/64x64/video-display.png")), - m_Swapped(false) { - init(); -} +using namespace synergy::gui::proxy; +using enum ScreenConfig::Modifier; +using enum ScreenConfig::SwitchCorner; +using enum ScreenConfig::Fix; -Screen::Screen(const QString &name) - : m_Pixmap(QPixmap(":res/icons/64x64/video-display.png")), - m_Swapped(false) { +Screen::Screen() { init(); } + +Screen::Screen(const QString &name) { init(); setName(name); } @@ -45,17 +45,17 @@ void Screen::init() { // m_Modifiers, m_SwitchCorners and m_Fixes are QLists we use like fixed-size // arrays, thus we need to make sure to fill them with the required number of // elements. - for (int i = 0; i < NumModifiers; i++) + for (int i = 0; i < static_cast(NumModifiers); i++) modifiers() << i; - for (int i = 0; i < NumSwitchCorners; i++) + for (int i = 0; i < static_cast(NumSwitchCorners); i++) switchCorners() << false; - for (int i = 0; i < NumFixes; i++) + for (int i = 0; i < static_cast(NumFixes); i++) fixes() << false; } -void Screen::loadSettings(QSettings &settings) { +void Screen::loadSettings(QSettingsProxy &settings) { setName(settings.value("name").toString()); if (name().isEmpty()) @@ -66,13 +66,14 @@ void Screen::loadSettings(QSettings &settings) { readSettings(settings, aliases(), "alias", QString("")); readSettings( settings, modifiers(), "modifier", static_cast(DefaultMod), - NumModifiers); + static_cast(NumModifiers)); readSettings( - settings, switchCorners(), "switchCorner", false, NumSwitchCorners); - readSettings(settings, fixes(), "fix", false, NumFixes); + settings, switchCorners(), "switchCorner", 0, + static_cast(NumSwitchCorners)); + readSettings(settings, fixes(), "fix", 0, static_cast(NumFixes)); } -void Screen::saveSettings(QSettings &settings) const { +void Screen::saveSettings(QSettingsProxy &settings) const { settings.setValue("name", name()); if (name().isEmpty()) @@ -130,16 +131,3 @@ bool Screen::operator==(const Screen &screen) const { m_Fixes == screen.m_Fixes && m_Swapped == screen.m_Swapped && m_isServer == screen.m_isServer; } - -QDataStream &operator<<(QDataStream &outStream, const Screen &screen) { - return outStream << screen.name() << screen.switchCornerSize() - << screen.aliases() << screen.modifiers() - << screen.switchCorners() << screen.fixes() - << screen.isServer(); -} - -QDataStream &operator>>(QDataStream &inStream, Screen &screen) { - return inStream >> screen.m_Name >> screen.m_SwitchCornerSize >> - screen.m_Aliases >> screen.m_Modifiers >> screen.m_SwitchCorners >> - screen.m_Fixes >> screen.m_isServer; -} diff --git a/src/gui/src/Screen.h b/src/lib/gui/config/Screen.h similarity index 72% rename from src/gui/src/Screen.h rename to src/lib/gui/config/Screen.h index 4b2253506..eee8e7fda 100644 --- a/src/gui/src/Screen.h +++ b/src/lib/gui/config/Screen.h @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 Symless Ltd. * Copyright (C) 2008 Volker Lanz (vl@fidra.de) * * This package is free software; you can redistribute it and/or @@ -20,6 +20,8 @@ #include "ScreenConfig.h" +#include "gui/proxy/QSettingsProxy.h" + #include #include #include @@ -30,34 +32,49 @@ class QTextStream; class ScreenSettingsDialog; class Screen : public ScreenConfig { - friend QDataStream &operator<<(QDataStream &outStream, const Screen &screen); - friend QDataStream &operator>>(QDataStream &inStream, Screen &screen); + using QSettingsProxy = synergy::gui::proxy::QSettingsProxy; + friend class ScreenSettingsDialog; friend class ScreenSetupModel; friend class ScreenSetupView; -public: - Screen(); - Screen(const QString &name); + friend QDataStream &operator<<(QDataStream &outStream, const Screen &screen) { + return outStream << screen.name() << screen.switchCornerSize() + << screen.aliases() << screen.modifiers() + << screen.switchCorners() << screen.fixes() + << screen.isServer(); + } + + friend QDataStream &operator>>(QDataStream &inStream, Screen &screen) { + return inStream >> screen.m_Name >> screen.m_SwitchCornerSize >> + screen.m_Aliases >> screen.m_Modifiers >> screen.m_SwitchCorners >> + screen.m_Fixes >> screen.m_isServer; + } public: + explicit Screen(); + explicit Screen(const QString &name); + const QPixmap &pixmap() const { return m_Pixmap; } const QString &name() const { return m_Name; } const QStringList &aliases() const { return m_Aliases; } bool isNull() const { return m_Name.isEmpty(); } int modifier(int m) const { - return m_Modifiers[m] == DefaultMod ? m : m_Modifiers[m]; + return m_Modifiers[m] == + static_cast(ScreenConfig::Modifier::DefaultMod) + ? m + : m_Modifiers[m]; } const QList &modifiers() const { return m_Modifiers; } bool switchCorner(int c) const { return m_SwitchCorners[c]; } const QList &switchCorners() const { return m_SwitchCorners; } int switchCornerSize() const { return m_SwitchCornerSize; } - bool fix(Fix f) const { return m_Fixes[f]; } + bool fix(Fix f) const { return m_Fixes[static_cast(f)]; } const QList &fixes() const { return m_Fixes; } - void loadSettings(QSettings &settings); - void saveSettings(QSettings &settings) const; + void loadSettings(QSettingsProxy &settings); + void saveSettings(QSettingsProxy &settings) const; QTextStream &writeScreensSection(QTextStream &outStream) const; QTextStream &writeAliasesSection(QTextStream &outStream) const; @@ -84,18 +101,13 @@ protected: void setSwapped(bool on) { m_Swapped = on; } private: - QPixmap m_Pixmap; + QPixmap m_Pixmap = QPixmap(":res/icons/64x64/video-display.png"); QString m_Name; - QStringList m_Aliases; QList m_Modifiers; QList m_SwitchCorners; int m_SwitchCornerSize; QList m_Fixes; - - bool m_Swapped; + bool m_Swapped = false; bool m_isServer = false; }; - -QDataStream &operator<<(QDataStream &outStream, const Screen &screen); -QDataStream &operator>>(QDataStream &inStream, Screen &screen); diff --git a/src/gui/src/ScreenConfig.cpp b/src/lib/gui/config/ScreenConfig.cpp similarity index 96% rename from src/gui/src/ScreenConfig.cpp rename to src/lib/gui/config/ScreenConfig.cpp index 55042a76f..697b70ba7 100644 --- a/src/gui/src/ScreenConfig.cpp +++ b/src/lib/gui/config/ScreenConfig.cpp @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 Symless Ltd. * Copyright (C) 2008 Volker Lanz (vl@fidra.de) * * This package is free software; you can redistribute it and/or diff --git a/src/gui/src/ScreenConfig.h b/src/lib/gui/config/ScreenConfig.h similarity index 78% rename from src/gui/src/ScreenConfig.h rename to src/lib/gui/config/ScreenConfig.h index 61e78b037..c4e5aa5f8 100644 --- a/src/gui/src/ScreenConfig.h +++ b/src/lib/gui/config/ScreenConfig.h @@ -18,14 +18,17 @@ #pragma once -#include #include #include +#include "gui/proxy/QSettingsProxy.h" + /// @brief Screen configuration base class class ScreenConfig { + using QSettingsProxy = synergy::gui::proxy::QSettingsProxy; + public: - enum Modifier { + enum class Modifier { DefaultMod = -1, Shift, Ctrl, @@ -35,29 +38,28 @@ public: None, NumModifiers }; - enum SwitchCorner { + enum class SwitchCorner { TopLeft, TopRight, BottomLeft, BottomRight, NumSwitchCorners }; - enum Fix { CapsLock, NumLock, ScrollLock, XTest, NumFixes }; + enum class Fix { CapsLock, NumLock, ScrollLock, XTest, NumFixes }; protected: explicit ScreenConfig() = default; ~ScreenConfig() = default; -protected: template void readSettings( - QSettings &settings, T1 &array, const QString &arrayName, - const T2 &deflt) { + QSettingsProxy &settings, T1 &array, const QString &arrayName, + const T2 &defaultValue) { int entries = settings.beginReadArray(arrayName + "Array"); array.clear(); for (int i = 0; i < entries; i++) { settings.setArrayIndex(i); - QVariant v = settings.value(arrayName, deflt); + QVariant v = settings.value(arrayName, defaultValue); array.append(v.value()); } settings.endArray(); @@ -65,13 +67,13 @@ protected: template void readSettings( - QSettings &settings, T1 &array, const QString &arrayName, const T2 &deflt, - int entries) { + QSettingsProxy &settings, T1 &array, const QString &arrayName, + const T2 &defaultValue, int entries) { Q_ASSERT(array.size() >= entries); settings.beginReadArray(arrayName + "Array"); for (int i = 0; i < entries; i++) { settings.setArrayIndex(i); - QVariant v = settings.value(arrayName, deflt); + QVariant v = settings.value(arrayName, defaultValue); array[i] = v.value(); } settings.endArray(); @@ -79,7 +81,8 @@ protected: template void writeSettings( - QSettings &settings, const T &array, const QString &arrayName) const { + QSettingsProxy &settings, const T &array, + const QString &arrayName) const { settings.beginWriteArray(arrayName + "Array"); for (int i = 0; i < array.size(); i++) { settings.setArrayIndex(i); diff --git a/src/gui/src/ScreenList.cpp b/src/lib/gui/config/ScreenList.cpp similarity index 97% rename from src/gui/src/ScreenList.cpp rename to src/lib/gui/config/ScreenList.cpp index e269c03a6..c06624ebb 100644 --- a/src/gui/src/ScreenList.cpp +++ b/src/lib/gui/config/ScreenList.cpp @@ -90,7 +90,8 @@ ScreenList::ScreenList(int width) : QList(), m_width(width) {} void ScreenList::addScreenByPriority(const Screen &newScreen) { int serverIndex = getServerIndex(*this); - auto indexes = getNeighborsIndexes(serverIndex, m_width, size()); + auto indexes = + getNeighborsIndexes(serverIndex, m_width, static_cast(size())); bool isAdded = false; for (const auto &index : indexes) { diff --git a/src/gui/src/ScreenList.h b/src/lib/gui/config/ScreenList.h similarity index 90% rename from src/gui/src/ScreenList.h rename to src/lib/gui/config/ScreenList.h index f2c247b56..c8bd89fcc 100644 --- a/src/gui/src/ScreenList.h +++ b/src/lib/gui/config/ScreenList.h @@ -1,7 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2012-2021 Symless Ltd. - * Copyright (C) 2008 Volker Lanz (vl@fidra.de) + * Copyright (C) 2021 Symless Ltd. * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -15,8 +14,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef SCREENLIST_H -#define SCREENLIST_H + +#pragma once #include "Screen.h" @@ -51,5 +50,3 @@ public: */ bool operator==(const ScreenList &sc) const; }; - -#endif // SCREENLIST_H diff --git a/src/lib/gui/core/ClientConnection.cpp b/src/lib/gui/core/ClientConnection.cpp index 43fb09d5b..050227297 100644 --- a/src/lib/gui/core/ClientConnection.cpp +++ b/src/lib/gui/core/ClientConnection.cpp @@ -17,71 +17,67 @@ #include "ClientConnection.h" +#include "messages.h" + #include #include namespace synergy::gui { -ClientConnection::ClientConnection(QWidget &parent, AppConfig &appConfig) - : m_parent(parent), - m_appConfig(appConfig) {} +// +// ClientConnection::Deps +// -void ClientConnection::update(const QString &line) { - if (m_checkConnection && checkMainWindow()) { - if (line.contains("failed to connect to server")) { - m_checkConnection = false; - if (!line.contains("server refused client with our name") && - !line.contains("Trying next address")) { - showMessage(getMessage(line)); - } - } else if (line.contains("connected to server")) { - m_checkConnection = false; +void ClientConnection::Deps::showError( + QWidget *parent, messages::ClientError error, + const QString &address) const { + messages::showClientConnectError(parent, error, address); +} + +// +// ClientConnection +// + +void ClientConnection::handleLogLine(const QString &logLine) { + if (!m_showMessage) { + qDebug("message already shown, skipping"); + return; + } + + if (logLine.contains("failed to connect to server")) { + m_showMessage = false; + + // ignore the message if it's about the server refusing by name as + // this will trigger the server to show an 'add client' dialog. + if (logLine.contains("server refused client with our name")) { + qDebug("ignoring client name refused message"); + return; } + + showMessage(logLine); + } else if (logLine.contains("connected to server")) { + m_showMessage = false; } } -bool ClientConnection::checkMainWindow() { - bool result = m_parent.isActiveWindow(); +void ClientConnection::showMessage(const QString &logLine) { + using enum messages::ClientError; - if (m_parent.isMinimized() || m_parent.isHidden()) { - m_parent.showNormal(); - m_parent.activateWindow(); - result = true; - } + emit messageShowing(); - return result; -} + const auto address = m_appConfig.serverHostname(); + auto message = + QString("

The connection to server '%1' didn't work.

").arg(address); -QString ClientConnection::getMessage(const QString &line) const { - QString message(QObject::tr("Connection failed.\nCheck the IP address on the " - "server, your TLS and firewall settings.")); - - if (line.contains("server already has a connected client with our name")) { - message = - QObject::tr("Connection failed.\nYou can’t name 2 computers the same."); + if (logLine.contains("server already has a connected client with our name")) { + m_deps->showError(m_pParent, AlreadyConnected, address); + } else if (QHostAddress a(address); a.isNull()) { + qDebug("ip not detected, showing hostname error"); + m_deps->showError(m_pParent, HostnameError, address); } else { - QHostAddress address(m_appConfig.serverHostname()); - if (address.isNull()) { - message = - QObject::tr( - "We can’t connect to the server \"%1\" try to connect using the " - "server IP address and check your firewall settings.") - .arg(m_appConfig.serverHostname()); - } + qDebug("ip detected, showing generic error"); + m_deps->showError(m_pParent, GenericError, address); } - - return message; -} - -void ClientConnection::showMessage(const QString &message) const { - QMessageBox dialog(&m_parent); - dialog.addButton(QObject::tr("Close"), QMessageBox::RejectRole); - dialog.setText(message); - dialog.exec(); -} - -void ClientConnection::setCheckConnection(bool checkConnection) { - m_checkConnection = checkConnection; } } // namespace synergy::gui diff --git a/src/lib/gui/core/ClientConnection.h b/src/lib/gui/core/ClientConnection.h index 5c64ce6f3..8b7d31946 100644 --- a/src/lib/gui/core/ClientConnection.h +++ b/src/lib/gui/core/ClientConnection.h @@ -17,27 +17,50 @@ #pragma once -#include "gui/config/AppConfig.h" +#include "gui/config/IAppConfig.h" +#include "gui/messages.h" + +#include #include #include +#include + +class QWidget; namespace synergy::gui { -class ClientConnection { +class ClientConnection : public QObject { + Q_OBJECT + public: - explicit ClientConnection(QWidget &parent, AppConfig &appConfig); - void update(const QString &line); - void setCheckConnection(bool checkConnection); + struct Deps { + virtual ~Deps() = default; + virtual void showError( + QWidget *parent, messages::ClientError error, + const QString &address) const; + }; + + explicit ClientConnection( + QWidget *parent, IAppConfig &appConfig, + std::shared_ptr deps = std::make_shared()) + : m_pParent(parent), + m_appConfig(appConfig), + m_deps(deps) {} + + void handleLogLine(const QString &line); + void setShowMessage() { m_showMessage = true; } + +signals: + void messageShowing(); private: - QString getMessage(const QString &line) const; - bool checkMainWindow(); - void showMessage(const QString &message) const; + void showMessage(const QString &logLine); - QWidget &m_parent; - AppConfig &m_appConfig; - bool m_checkConnection = false; + QWidget *m_pParent; + IAppConfig &m_appConfig; + std::shared_ptr m_deps; + bool m_showMessage = true; }; } // namespace synergy::gui diff --git a/src/lib/gui/core/ServerConnection.cpp b/src/lib/gui/core/ServerConnection.cpp index 3f23e1b61..bc489f17a 100644 --- a/src/lib/gui/core/ServerConnection.cpp +++ b/src/lib/gui/core/ServerConnection.cpp @@ -18,42 +18,46 @@ #include "ServerConnection.h" #include "ServerMessage.h" -#include "gui/config/AppConfig.h" +#include "messages.h" #include #include namespace synergy::gui { -ServerConnection::ServerConnection( - QWidget &parent, AppConfig &appConfig, IServerConfig &serverConfig) - : m_parent(parent), - m_appConfig(appConfig), - m_serverConfig(serverConfig) {} +// +// ServerConnection::Deps +// -void ServerConnection::update(const QString &line) { - ServerMessage message(line); +messages::NewClientPromptResult ServerConnection::Deps::showNewClientPrompt( + QWidget *parent, const QString &clientName) const { + return messages::showNewClientPrompt(parent, clientName); +} + +// +// ServerConnection +// + +ServerConnection::ServerConnection( + QWidget *parent, IAppConfig &appConfig, IServerConfig &serverConfig, + std::shared_ptr deps) + : m_pParent(parent), + m_appConfig(appConfig), + m_serverConfig(serverConfig), + m_pDeps(deps) {} + +void ServerConnection::handleLogLine(const QString &logLine) { + ServerMessage message(logLine); if (!m_appConfig.useExternalConfig() && message.isNewClientMessage() && !m_ignoredClients.contains(message.getClientName())) { - addClient(message.getClientName()); + handleNewClient(message.getClientName()); } } -// TOOD: merge duplicated code between client and server connection -bool ServerConnection::checkMainWindow() { - bool result = m_parent.isActiveWindow(); +void ServerConnection::handleNewClient(const QString &clientName) { + using enum messages::NewClientPromptResult; - if (m_parent.isMinimized() || m_parent.isHidden()) { - m_parent.showNormal(); - m_parent.activateWindow(); - result = true; - } - - return result; -} - -void ServerConnection::addClient(const QString &clientName) { if (m_serverConfig.isFull()) { qDebug( "server config full, skipping add client prompt for: %s", @@ -68,30 +72,17 @@ void ServerConnection::addClient(const QString &clientName) { return; } - if (!checkMainWindow()) { - qDebug( - "main window not active, skipping add client prompt for: %s", - qPrintable(clientName)); - return; - } + emit messageShowing(); - QMessageBox message(&m_parent); - const QPushButton *ignore = - message.addButton("Ignore", QMessageBox::RejectRole); - const QPushButton *add = - message.addButton("Add client", QMessageBox::AcceptRole); - message.setText( - QString("A new client called '%1' wants to connect").arg(clientName)); - message.exec(); - - if (message.clickedButton() == add) { + const auto result = m_pDeps->showNewClientPrompt(m_pParent, clientName); + if (result == Add) { qDebug("accepted dialog, adding client: %s", qPrintable(clientName)); emit configureClient(clientName); - } else if (message.clickedButton() == ignore) { + } else if (result == Ignore) { qDebug("declined dialog, ignoring client: %s", qPrintable(clientName)); m_ignoredClients.append(clientName); } else { - qFatal("no expected dialog button was clicked"); + qFatal("unexpected add client result"); } } diff --git a/src/lib/gui/core/ServerConnection.h b/src/lib/gui/core/ServerConnection.h index 312acab5e..5351b7e6d 100644 --- a/src/lib/gui/core/ServerConnection.h +++ b/src/lib/gui/core/ServerConnection.h @@ -20,8 +20,9 @@ #include #include -#include "gui/config/AppConfig.h" +#include "gui/config/IAppConfig.h" #include "gui/config/IServerConfig.h" +#include "gui/messages.h" namespace synergy::gui { @@ -30,20 +31,28 @@ class ServerConnection : public QObject { using IServerConfig = synergy::gui::IServerConfig; public: + struct Deps { + virtual ~Deps() = default; + virtual messages::NewClientPromptResult + showNewClientPrompt(QWidget *parent, const QString &clientName) const; + }; + explicit ServerConnection( - QWidget &parent, AppConfig &appConfig, IServerConfig &serverConfig); - void update(const QString &line); + QWidget *parent, IAppConfig &appConfig, IServerConfig &serverConfig, + std::shared_ptr deps = std::make_shared()); + void handleLogLine(const QString &logLine); signals: + void messageShowing(); void configureClient(const QString &clientName); private: - void addClient(const QString &clientName); - bool checkMainWindow(); + void handleNewClient(const QString &clientName); - QWidget &m_parent; - AppConfig &m_appConfig; + QWidget *m_pParent; + IAppConfig &m_appConfig; IServerConfig &m_serverConfig; + std::shared_ptr m_pDeps; QStringList m_ignoredClients; }; diff --git a/src/lib/gui/core/ServerMessage.cpp b/src/lib/gui/core/ServerMessage.cpp index c8124534f..48e930121 100644 --- a/src/lib/gui/core/ServerMessage.cpp +++ b/src/lib/gui/core/ServerMessage.cpp @@ -21,7 +21,7 @@ namespace synergy::gui { ServerMessage::ServerMessage(const QString &message) : m_message(message), - m_clienName(parseClientName(message)) {} + m_clientName(parseClientName(message)) {} bool ServerMessage::isNewClientMessage() const { return m_message.contains("unrecognised client name"); @@ -39,7 +39,7 @@ bool ServerMessage::isDisconnectedMessage() const { return m_message.contains("has disconnected"); } -const QString &ServerMessage::getClientName() const { return m_clienName; } +const QString &ServerMessage::getClientName() const { return m_clientName; } QString ServerMessage::parseClientName(const QString &line) const { QString clientName("Unknown"); diff --git a/src/lib/gui/core/ServerMessage.h b/src/lib/gui/core/ServerMessage.h index 557179161..5ced6a079 100644 --- a/src/lib/gui/core/ServerMessage.h +++ b/src/lib/gui/core/ServerMessage.h @@ -23,7 +23,7 @@ namespace synergy::gui { class ServerMessage { QString m_message; - QString m_clienName; + QString m_clientName; public: explicit ServerMessage(const QString &message); diff --git a/src/lib/gui/messages.cpp b/src/lib/gui/messages.cpp index 0afe54487..e97d87890 100644 --- a/src/lib/gui/messages.cpp +++ b/src/lib/gui/messages.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -181,4 +182,59 @@ void showDevThanks(QWidget *parent, const QString &productName) { kColorSecondary)); } +void showClientConnectError( + QWidget *parent, ClientError error, const QString &address) { + using enum ClientError; + + auto message = + QString("

The connection to server '%1' didn't work.

").arg(address); + + if (error == AlreadyConnected) { + message += // + "

Two of your client computers have the same name or there are " + "two instances of the client process running.

" + "

Please ensure that you're using a unique name and that only a " + "single client process is running.

"; + } else if (error == HostnameError) { + message += // + "

Please try to connect to the server using the server IP address " + "instead of the hostname.

" + "

If that doesn't work, please check your TLS and " + "firewall settings.

"; + } else if (error == GenericError) { + message += // + "

Please check your TLS and firewall settings.

"; + } else { + qFatal("unknown client error"); + } + + QMessageBox dialog(parent); + dialog.addButton(QObject::tr("Close"), QMessageBox::RejectRole); + dialog.setText(message); + dialog.exec(); +} + +NewClientPromptResult +showNewClientPrompt(QWidget *parent, const QString &clientName) { + using enum NewClientPromptResult; + + QMessageBox message(parent); + const QPushButton *ignore = + message.addButton("Ignore", QMessageBox::RejectRole); + const QPushButton *add = + message.addButton("Add client", QMessageBox::AcceptRole); + message.setText( + QString("A new client called '%1' wants to connect").arg(clientName)); + message.exec(); + + if (message.clickedButton() == add) { + return Add; + } else if (message.clickedButton() == ignore) { + return Ignore; + } else { + qFatal("no expected dialog button was clicked"); + abort(); + } +} + } // namespace synergy::gui::messages diff --git a/src/lib/gui/messages.h b/src/lib/gui/messages.h index 5926a9c37..239f8c197 100644 --- a/src/lib/gui/messages.h +++ b/src/lib/gui/messages.h @@ -19,10 +19,15 @@ #include #include -#include + +class QWidget; namespace synergy::gui::messages { +enum class ClientError { AlreadyConnected, HostnameError, GenericError }; + +enum class NewClientPromptResult { Add, Ignore }; + void messageHandler( QtMsgType type, const QMessageLogContext &context, const QString &msg); @@ -33,4 +38,10 @@ void showCloseReminder(QWidget *parent); void showDevThanks(QWidget *parent, const QString &productName); +void showClientConnectError( + QWidget *parent, ClientError error, const QString &address); + +NewClientPromptResult +showNewClientPrompt(QWidget *parent, const QString &clientName); + } // namespace synergy::gui::messages diff --git a/src/lib/gui/proxy/QSettingsProxy.cpp b/src/lib/gui/proxy/QSettingsProxy.cpp new file mode 100644 index 000000000..7e4fdeebd --- /dev/null +++ b/src/lib/gui/proxy/QSettingsProxy.cpp @@ -0,0 +1,61 @@ +/* + * 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 "QSettingsProxy.h" + +namespace synergy::gui::proxy { + +int QSettingsProxy::beginReadArray(const QString &prefix) { + return m_pSettings->beginReadArray(prefix); +} + +void QSettingsProxy::setArrayIndex(int i) { m_pSettings->setArrayIndex(i); } + +QVariant QSettingsProxy::value(const QString &key) const { + return m_pSettings->value(key); +} + +QVariant +QSettingsProxy::value(const QString &key, const QVariant &defaultValue) const { + return m_pSettings->value(key, defaultValue); +} + +void QSettingsProxy::endArray() { m_pSettings->endArray(); } + +void QSettingsProxy::beginWriteArray(const QString &prefix) { + m_pSettings->beginWriteArray(prefix); +} + +void QSettingsProxy::setValue(const QString &key, const QVariant &value) { + m_pSettings->setValue(key, value); +} + +void QSettingsProxy::beginGroup(const QString &prefix) { + m_pSettings->beginGroup(prefix); +} + +void QSettingsProxy::remove(const QString &key) { m_pSettings->remove(key); } + +void QSettingsProxy::endGroup() { m_pSettings->endGroup(); } + +bool QSettingsProxy::isWritable() const { return m_pSettings->isWritable(); } + +bool QSettingsProxy::contains(const QString &key) const { + return m_pSettings->contains(key); +} + +} // namespace synergy::gui::proxy diff --git a/src/lib/gui/proxy/QSettingsProxy.h b/src/lib/gui/proxy/QSettingsProxy.h new file mode 100644 index 000000000..398025f05 --- /dev/null +++ b/src/lib/gui/proxy/QSettingsProxy.h @@ -0,0 +1,48 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2024 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +namespace synergy::gui::proxy { + +class QSettingsProxy { +public: + virtual ~QSettingsProxy() = default; + virtual int beginReadArray(const QString &prefix); + virtual void beginWriteArray(const QString &prefix); + virtual void setArrayIndex(int i); + virtual QVariant value(const QString &key) const; + virtual QVariant + value(const QString &key, const QVariant &defaultValue) const; + virtual void endArray(); + virtual void setValue(const QString &key, const QVariant &value); + virtual void beginGroup(const QString &prefix); + virtual void endGroup(); + virtual void remove(const QString &key); + virtual bool isWritable() const; + virtual bool contains(const QString &key) const; + + void set(QSettings &settings) { m_pSettings = &settings; } + QSettings &get() const { return *m_pSettings; } + +private: + QSettings *m_pSettings; +}; + +} // namespace synergy::gui::proxy diff --git a/src/test/integtests/gui/core/CoreToolTests.cpp b/src/test/integtests/gui/core/CoreToolTests.cpp index 90e98b356..48daec5e3 100644 --- a/src/test/integtests/gui/core/CoreToolTests.cpp +++ b/src/test/integtests/gui/core/CoreToolTests.cpp @@ -16,13 +16,12 @@ */ #include "gui/core/CoreTool.h" -#include "shared/gui/QtCoreTest.h" +#include "shared/gui/TestQtCoreApp.h" #include -class CoreToolTests : public QtCoreTest {}; - -TEST_F(CoreToolTests, getProfileDir_noMock_returnsNonEmpty) { +TEST(CoreToolTests, getProfileDir_noMock_returnsNonEmpty) { + TestQtCoreApp app; CoreTool coreTool; QString profileDir = coreTool.getProfileDir(); @@ -30,7 +29,8 @@ TEST_F(CoreToolTests, getProfileDir_noMock_returnsNonEmpty) { EXPECT_FALSE(profileDir.isEmpty()); } -TEST_F(CoreToolTests, getInstalledDir_noMock_returnsNonEmpty) { +TEST(CoreToolTests, getInstalledDir_noMock_returnsNonEmpty) { + TestQtCoreApp app; CoreTool coreTool; QString installedDir = coreTool.getInstalledDir(); @@ -38,7 +38,8 @@ TEST_F(CoreToolTests, getInstalledDir_noMock_returnsNonEmpty) { EXPECT_FALSE(installedDir.isEmpty()); } -TEST_F(CoreToolTests, getArch_noMock_returnsNonEmpty) { +TEST(CoreToolTests, getArch_noMock_returnsNonEmpty) { + TestQtCoreApp app; CoreTool coreTool; QString arch = coreTool.getArch(); @@ -46,7 +47,8 @@ TEST_F(CoreToolTests, getArch_noMock_returnsNonEmpty) { EXPECT_FALSE(arch.isEmpty()); } -TEST_F(CoreToolTests, getSerialKeyFilePath_noMock_returnsNonEmpty) { +TEST(CoreToolTests, getSerialKeyFilePath_noMock_returnsNonEmpty) { + TestQtCoreApp app; CoreTool coreTool; QString serialKeyFilePath = coreTool.getSerialKeyFilePath(); diff --git a/src/test/shared/gui/QtCoreTest.h b/src/test/shared/gui/QtCoreTest.h deleted file mode 100644 index fb7d05b90..000000000 --- a/src/test/shared/gui/QtCoreTest.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 "gui/messages.h" - -#include -#include - -class QtCoreTest : public ::testing::Test { -public: - static void SetUpTestSuite() { - char **argv = nullptr; - int argc = 0; - s_app = std::make_unique(argc, argv); - qInstallMessageHandler(synergy::gui::messages::messageHandler); - } - - static void TearDownTestSuite() { s_app.reset(); } - - static std::unique_ptr s_app; -}; diff --git a/src/test/shared/gui/QtTest.cpp b/src/test/shared/gui/TestQtCoreApp.h similarity index 75% rename from src/test/shared/gui/QtTest.cpp rename to src/test/shared/gui/TestQtCoreApp.h index 984cb9416..c72e74468 100644 --- a/src/test/shared/gui/QtTest.cpp +++ b/src/test/shared/gui/TestQtCoreApp.h @@ -15,10 +15,14 @@ * along with this program. If not, see . */ -#ifdef QT_GUI_LIB +#pragma once -#include "QtTest.h" +#include -std::unique_ptr QtTest::s_app; +int argc = 0; // NOLINT +char **argv = nullptr; // NOLINT -#endif +class TestQtCoreApp : public QCoreApplication { +public: + explicit TestQtCoreApp() : QCoreApplication(argc, argv) {} +}; diff --git a/src/test/shared/gui/QtCoreTest.cpp b/src/test/shared/gui/TestQtFullApp.h similarity index 72% rename from src/test/shared/gui/QtCoreTest.cpp rename to src/test/shared/gui/TestQtFullApp.h index 8331e4694..810508d86 100644 --- a/src/test/shared/gui/QtCoreTest.cpp +++ b/src/test/shared/gui/TestQtFullApp.h @@ -15,10 +15,17 @@ * along with this program. If not, see . */ -#ifdef QT_GUI_LIB +#pragma once -#include "QtCoreTest.h" +#include -std::unique_ptr QtCoreTest::s_app; +int argc = 0; // NOLINT +char **argv = nullptr; // NOLINT -#endif +/** + * Prefer using `TestQtCoreApp` instead. + */ +class TestQtFullApp : public QApplication { +public: + explicit TestQtFullApp() : QApplication(argc, argv) {} +}; diff --git a/src/test/shared/gui/mocks/AppConfigMock.h b/src/test/shared/gui/mocks/AppConfigMock.h new file mode 100644 index 000000000..966b44b46 --- /dev/null +++ b/src/test/shared/gui/mocks/AppConfigMock.h @@ -0,0 +1,65 @@ +/* + * 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 "gui/config/IAppConfig.h" + +#include "gui/config/ElevateMode.h" + +#include + +class AppConfigMock : public synergy::gui::IAppConfig { + using ProcessMode = synergy::gui::ProcessMode; + +public: + AppConfigMock() { + ON_CALL(*this, screenName()).WillByDefault(testing::ReturnRef(m_stubName)); + ON_CALL(*this, networkInterface()) + .WillByDefault(testing::ReturnRef(m_stubInterface)); + ON_CALL(*this, logLevelText()) + .WillByDefault(testing::Return("stub log level")); + } + + MOCK_METHOD(QString, tlsCertPath, (), (const, override)); + MOCK_METHOD(int, tlsKeyLength, (), (const, override)); + MOCK_METHOD(bool, tlsEnabled, (), (const, override)); + MOCK_METHOD(ProcessMode, processMode, (), (const, override)); + MOCK_METHOD(ElevateMode, elevateMode, (), (const, override)); + MOCK_METHOD(QString, logLevelText, (), (const, override)); + MOCK_METHOD(const QString &, screenName, (), (const, override)); + MOCK_METHOD(bool, preventSleep, (), (const, override)); + MOCK_METHOD(bool, logToFile, (), (const, override)); + MOCK_METHOD(const QString &, logFilename, (), (const, override)); + MOCK_METHOD(QString, coreServerName, (), (const, override)); + MOCK_METHOD(QString, coreClientName, (), (const, override)); + MOCK_METHOD(bool, invertConnection, (), (const, override)); + MOCK_METHOD(void, persistLogDir, (), (const, override)); + MOCK_METHOD(QString, serialKey, (), (const, override)); + MOCK_METHOD(bool, languageSync, (), (const, override)); + MOCK_METHOD(bool, invertScrollDirection, (), (const, override)); + MOCK_METHOD(int, port, (), (const, override)); + MOCK_METHOD(bool, useExternalConfig, (), (const, override)); + MOCK_METHOD(const QString &, configFile, (), (const, override)); + MOCK_METHOD(const QString &, networkInterface, (), (const, override)); + MOCK_METHOD(const QString &, serverHostname, (), (const, override)); + +private: + const QString m_stubName = "stub name"; + const QString m_stubInterface = "stub interface"; + const QString m_stubAddress = "stub address"; +}; diff --git a/src/test/shared/gui/QtTest.h b/src/test/shared/gui/mocks/ServerConfigMock.h similarity index 57% rename from src/test/shared/gui/QtTest.h rename to src/test/shared/gui/mocks/ServerConfigMock.h index cd2186fff..d7c4942ab 100644 --- a/src/test/shared/gui/QtTest.h +++ b/src/test/shared/gui/mocks/ServerConfigMock.h @@ -17,21 +17,17 @@ #pragma once -#include "gui/messages.h" +#include "gui/config/IServerConfig.h" -#include -#include +#include -class QtTest : public ::testing::Test { +class ServerConfigMock : public synergy::gui::IServerConfig { public: - static void SetUpTestSuite() { - char **argv = nullptr; - int argc = 0; - s_app = std::make_unique(argc, argv); - qInstallMessageHandler(synergy::gui::messages::messageHandler); - } - - static void TearDownTestSuite() { s_app.reset(); } - - static std::unique_ptr s_app; + MOCK_METHOD(bool, isFull, (), (const, override)); + MOCK_METHOD( + bool, screenExists, (const QString &screenName), (const, override)); + MOCK_METHOD(bool, save, (const QString &fileName), (const, override)); + MOCK_METHOD(void, save, (QFile & file), (const, override)); + MOCK_METHOD(bool, enableDragAndDrop, (), (const, override)); + MOCK_METHOD(const ScreenList &, screens, (), (const, override)); }; diff --git a/src/test/unittests/gui/config/AppConfigTests.cpp b/src/test/unittests/gui/config/AppConfigTests.cpp index ddad213e0..bd5a242c7 100644 --- a/src/test/unittests/gui/config/AppConfigTests.cpp +++ b/src/test/unittests/gui/config/AppConfigTests.cpp @@ -17,15 +17,20 @@ #include "gui/config/AppConfig.h" +#include "gui/proxy/QSettingsProxy.h" + #include "gmock/gmock.h" #include #include using namespace testing; +using namespace synergy::gui::proxy; namespace { -class MockScopes : public synergy::gui::IConfigScopes { +class ConfigScopesMock : public synergy::gui::IConfigScopes { + using QSettingsProxy = synergy::gui::proxy::QSettingsProxy; + public: MOCK_METHOD(void, signalReady, (), (override)); MOCK_METHOD( @@ -41,18 +46,19 @@ public: 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(const QSettingsProxy &, activeSettings, (), (const, override)); + MOCK_METHOD(QSettingsProxy &, activeSettings, (), (override)); MOCK_METHOD(void, save, (), (override)); }; -struct MockDeps : public AppConfig::Deps { - MockDeps() { +struct DepsMock : public AppConfig::Deps { + DepsMock() { ON_CALL(*this, profileDir()).WillByDefault(Return("stub")); ON_CALL(*this, hostname()).WillByDefault(Return("stub")); } - static std::shared_ptr> makeNice() { - return std::make_shared>(); + static std::shared_ptr> makeNice() { + return std::make_shared>(); } MOCK_METHOD(QString, profileDir, (), (const, override)); @@ -64,8 +70,8 @@ struct MockDeps : public AppConfig::Deps { class AppConfigTests : public Test {}; TEST_F(AppConfigTests, ctor_byDefault_screenNameIsHostname) { - NiceMock scopes; - auto deps = MockDeps::makeNice(); + NiceMock scopes; + auto deps = DepsMock::makeNice(); ON_CALL(*deps, hostname()).WillByDefault(Return("test hostname")); AppConfig appConfig(scopes, deps); @@ -74,8 +80,8 @@ TEST_F(AppConfigTests, ctor_byDefault_screenNameIsHostname) { } TEST_F(AppConfigTests, ctor_byDefault_getsFromScope) { - NiceMock scopes; - auto deps = MockDeps::makeNice(); + NiceMock scopes; + auto deps = DepsMock::makeNice(); ON_CALL(scopes, scopeContains(_, _)).WillByDefault(Return(true)); ON_CALL(scopes, getFromScope(_, _, _)) @@ -88,8 +94,8 @@ TEST_F(AppConfigTests, ctor_byDefault_getsFromScope) { } TEST_F(AppConfigTests, commit_byDefault_setsToScope) { - NiceMock scopes; - auto deps = MockDeps::makeNice(); + NiceMock scopes; + auto deps = DepsMock::makeNice(); AppConfig appConfig(scopes, deps); ON_CALL(scopes, isActiveScopeWritable()).WillByDefault(Return(true)); diff --git a/src/test/unittests/gui/config/ScreenTests.cpp b/src/test/unittests/gui/config/ScreenTests.cpp new file mode 100644 index 000000000..99e2ae25b --- /dev/null +++ b/src/test/unittests/gui/config/ScreenTests.cpp @@ -0,0 +1,68 @@ +/* + * 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 "gui/config/Screen.h" + +#include "gui/proxy/QSettingsProxy.h" +#include "shared/gui/TestQtCoreApp.h" + +#include +#include + +using namespace synergy::gui::proxy; +using namespace testing; + +class QSettingsProxyMock : public QSettingsProxy { +public: + MOCK_METHOD(int, beginReadArray, (const QString &prefix), (override)); + MOCK_METHOD(void, beginWriteArray, (const QString &prefix), (override)); + MOCK_METHOD(void, setArrayIndex, (int i), (override)); + MOCK_METHOD(QVariant, value, (const QString &key), (const, override)); + MOCK_METHOD( + QVariant, value, (const QString &key, const QVariant &defaultValue), + (const, override)); + MOCK_METHOD(void, endArray, (), (override)); + MOCK_METHOD( + void, setValue, (const QString &key, const QVariant &value), (override)); + MOCK_METHOD(void, beginGroup, (const QString &prefix), (override)); + MOCK_METHOD(void, endGroup, (), (override)); + MOCK_METHOD(void, remove, (const QString &key), (override)); + MOCK_METHOD(bool, isWritable, (), (const, override)); + MOCK_METHOD(bool, contains, (const QString &key), (const, override)); +}; + +TEST(ScreenTests, loadSettings_whenHasSetting_readsArray) { + TestQtCoreApp app; + NiceMock settings; + Screen screen; + ON_CALL(settings, value(_)).WillByDefault(Return("stub")); + + EXPECT_CALL(settings, beginReadArray(_)).Times(4); + + screen.loadSettings(settings); +} + +TEST(ScreenTests, saveSettings_whenNameIsSet_writesArray) { + TestQtCoreApp app; + NiceMock settings; + Screen screen; + screen.setName("stub"); + + EXPECT_CALL(settings, beginWriteArray(_)).Times(4); + + screen.saveSettings(settings); +} diff --git a/src/test/unittests/gui/core/ClientConnectionTests.cpp b/src/test/unittests/gui/core/ClientConnectionTests.cpp new file mode 100644 index 000000000..00aaf4fb2 --- /dev/null +++ b/src/test/unittests/gui/core/ClientConnectionTests.cpp @@ -0,0 +1,129 @@ +/* + * 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 "gui/core/ClientConnection.h" + +#include "shared/gui/mocks/AppConfigMock.h" + +#include +#include + +class QWidget; + +using testing::_; +using testing::NiceMock; +using namespace synergy::gui; +using enum messages::ClientError; + +namespace { + +struct DepsMock : public ClientConnection::Deps { + MOCK_METHOD( + void, showError, + (QWidget * parent, messages::ClientError error, const QString &address), + (const, override)); +}; + +} // namespace + +class ClientConnectionTests : public testing::Test { +public: + ClientConnectionTests() { + ON_CALL(m_appConfig, serverHostname()) + .WillByDefault(testing::ReturnRef(stub)); + } + + std::shared_ptr m_pDeps = std::make_shared>(); + NiceMock m_appConfig; + +private: + const QString stub = "stub"; +}; + +TEST_F(ClientConnectionTests, handleLogLine_alreadyConnected_showError) { + ClientConnection clientConnection(nullptr, m_appConfig, m_pDeps); + const QString serverName = "test server"; + ON_CALL(m_appConfig, serverHostname()) + .WillByDefault(testing::ReturnRef(serverName)); + + EXPECT_CALL(*m_pDeps, showError(_, AlreadyConnected, serverName)); + + clientConnection.handleLogLine( + "failed to connect to server\n" + "server already has a connected client with our name"); +} + +TEST_F(ClientConnectionTests, handleLogLine_withHostname_showError) { + ClientConnection clientConnection(nullptr, m_appConfig, m_pDeps); + const QString serverName = "test-hostname"; + ON_CALL(m_appConfig, serverHostname()) + .WillByDefault(testing::ReturnRef(serverName)); + + EXPECT_CALL(*m_pDeps, showError(_, HostnameError, serverName)); + + clientConnection.handleLogLine("failed to connect to server"); +} + +TEST_F(ClientConnectionTests, handleLogLine_withIpAddress_showError) { + ClientConnection clientConnection(nullptr, m_appConfig, m_pDeps); + const QString serverName = "1.1.1.1"; + ON_CALL(m_appConfig, serverHostname()) + .WillByDefault(testing::ReturnRef(serverName)); + + EXPECT_CALL(*m_pDeps, showError(_, GenericError, serverName)); + + clientConnection.handleLogLine("failed to connect to server"); +} + +TEST_F(ClientConnectionTests, handleLogLine_messageShown_shouldNotShowAgain) { + ClientConnection clientConnection(nullptr, m_appConfig, m_pDeps); + + clientConnection.handleLogLine("failed to connect to server"); + + EXPECT_CALL(*m_pDeps, showError(_, _, _)).Times(0); + + clientConnection.handleLogLine("failed to connect to server"); +} + +TEST_F( + ClientConnectionTests, + handleLogLine_serverRefusedClient_shouldNotShowError) { + ClientConnection clientConnection(nullptr, m_appConfig, m_pDeps); + + EXPECT_CALL(*m_pDeps, showError(_, _, _)).Times(0); + + clientConnection.handleLogLine("failed to connect to server\n" + "server refused client with our name"); +} + +TEST_F( + ClientConnectionTests, handleLogLine_connected_shouldPreventFutureError) { + ClientConnection clientConnection(nullptr, m_appConfig, m_pDeps); + clientConnection.handleLogLine("connected to server"); + + EXPECT_CALL(*m_pDeps, showError(_, _, _)).Times(0); + + clientConnection.handleLogLine("failed to connect to server"); +} + +TEST_F(ClientConnectionTests, handleLogLine_otherMessage_shouldNotShowError) { + ClientConnection clientConnection(nullptr, m_appConfig, m_pDeps); + + EXPECT_CALL(*m_pDeps, showError(_, _, _)).Times(0); + + clientConnection.handleLogLine("hello world"); +} diff --git a/src/test/unittests/gui/core/CoreProcessTests.cpp b/src/test/unittests/gui/core/CoreProcessTests.cpp index 73ab3aa91..610e921fb 100644 --- a/src/test/unittests/gui/core/CoreProcessTests.cpp +++ b/src/test/unittests/gui/core/CoreProcessTests.cpp @@ -19,6 +19,8 @@ #include "gui/core/CoreProcess.h" #include "gui/ipc/IQIpcClient.h" #include "gui/proxy/QProcessProxy.h" +#include "shared/gui/mocks/AppConfigMock.h" +#include "shared/gui/mocks/ServerConfigMock.h" #include "gmock/gmock.h" #include @@ -30,58 +32,13 @@ using ::testing::NiceMock; using ::testing::Return; using ::testing::ReturnRef; -class MockAppConfig : public IAppConfig { -public: - MockAppConfig() { - ON_CALL(*this, screenName()).WillByDefault(ReturnRef(m_stubName)); - ON_CALL(*this, networkInterface()) - .WillByDefault(ReturnRef(m_stubInterface)); - ON_CALL(*this, logLevelText()).WillByDefault(Return("stub log level")); - } +namespace { - MOCK_METHOD(QString, tlsCertPath, (), (const, override)); - MOCK_METHOD(int, tlsKeyLength, (), (const, override)); - MOCK_METHOD(bool, tlsEnabled, (), (const, override)); - MOCK_METHOD(ProcessMode, processMode, (), (const, override)); - MOCK_METHOD(ElevateMode, elevateMode, (), (const, override)); - MOCK_METHOD(QString, logLevelText, (), (const, override)); - MOCK_METHOD(const QString &, screenName, (), (const, override)); - MOCK_METHOD(bool, preventSleep, (), (const, override)); - MOCK_METHOD(bool, logToFile, (), (const, override)); - MOCK_METHOD(const QString &, logFilename, (), (const, override)); - MOCK_METHOD(QString, coreServerName, (), (const, override)); - MOCK_METHOD(QString, coreClientName, (), (const, override)); - MOCK_METHOD(bool, invertConnection, (), (const, override)); - MOCK_METHOD(void, persistLogDir, (), (const, override)); - MOCK_METHOD(QString, serialKey, (), (const, override)); - MOCK_METHOD(bool, languageSync, (), (const, override)); - MOCK_METHOD(bool, invertScrollDirection, (), (const, override)); - MOCK_METHOD(int, port, (), (const, override)); - MOCK_METHOD(bool, useExternalConfig, (), (const, override)); - MOCK_METHOD(const QString &, configFile, (), (const, override)); - MOCK_METHOD(const QString &, networkInterface, (), (const, override)); - -private: - const QString m_stubName = "stub name"; - const QString m_stubInterface = "stub interface"; - const QString m_stubAddress = "stub address"; -}; - -class MockServerConfig : public IServerConfig { -public: - MOCK_METHOD(bool, isFull, (), (const, override)); - MOCK_METHOD( - bool, screenExists, (const QString &screenName), (const, override)); - MOCK_METHOD(bool, save, (const QString &fileName), (const, override)); - MOCK_METHOD(void, save, (QFile & file), (const, override)); - MOCK_METHOD(bool, enableDragAndDrop, (), (const, override)); -}; - -class MockQProcessProxy : public proxy::QProcessProxy { +class QProcessProxyMock : public proxy::QProcessProxy { public: operator bool() const override { return toBool(); } - MockQProcessProxy() { + QProcessProxyMock() { ON_CALL(*this, toBool()).WillByDefault(Return(true)); ON_CALL(*this, state()) .WillByDefault(Return(QProcess::ProcessState::Running)); @@ -100,9 +57,9 @@ public: MOCK_METHOD(QString, readAllStandardError, (), (override)); }; -class MockQIpcClient : public ipc::IQIpcClient { +class QIpcClientMock : public ipc::IQIpcClient { public: - MockQIpcClient() { + QIpcClientMock() { ON_CALL(*this, isConnected()).WillByDefault(Return(true)); } @@ -115,9 +72,9 @@ public: MOCK_METHOD(bool, isConnected, (), (const, override)); }; -class MockDeps : public CoreProcess::Deps { +class DepsMock : public CoreProcess::Deps { public: - MockDeps() { + DepsMock() { ON_CALL(*this, process()).WillByDefault(ReturnRef(m_process)); ON_CALL(*this, ipcClient()).WillByDefault(ReturnRef(m_ipcClient)); ON_CALL(*this, appPath(_)).WillByDefault(Return("stub app path")); @@ -131,21 +88,23 @@ public: MOCK_METHOD(bool, fileExists, (const QString &path), (const, override)); MOCK_METHOD(QString, getProfileRoot, (), (const, override)); - NiceMock m_process; - NiceMock m_ipcClient; + NiceMock m_process; + NiceMock m_ipcClient; }; class CoreProcessTests : public ::testing::Test { public: CoreProcessTests() : m_coreProcess(m_appConfig, m_serverConfig, m_pDeps) {} - NiceMock m_appConfig; - NiceMock m_serverConfig; - std::shared_ptr> m_pDeps = - std::make_shared>(); + NiceMock m_appConfig; + NiceMock m_serverConfig; + std::shared_ptr> m_pDeps = + std::make_shared>(); CoreProcess m_coreProcess; }; +} // namespace + TEST_F(CoreProcessTests, start_serverDesktop_callsProcessStart) { m_coreProcess.setMode(CoreProcess::Mode::Server); diff --git a/src/test/unittests/gui/core/ServerConnectionTests.cpp b/src/test/unittests/gui/core/ServerConnectionTests.cpp new file mode 100644 index 000000000..5a6d19569 --- /dev/null +++ b/src/test/unittests/gui/core/ServerConnectionTests.cpp @@ -0,0 +1,91 @@ +/* + * 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 "gui/core/ServerConnection.h" + +#include "shared/gui/mocks/AppConfigMock.h" +#include "shared/gui/mocks/ServerConfigMock.h" + +#include "gmock/gmock.h" +#include +#include + +using testing::_; +using testing::NiceMock; +using namespace synergy::gui; + +class QWidget; + +namespace { + +struct DepsMock : public ServerConnection::Deps { + MOCK_METHOD( + messages::NewClientPromptResult, showNewClientPrompt, + (QWidget * parent, const QString &clientName), (const, override)); +}; + +} // namespace + +class ServerConnectionTests : public testing::Test { +public: + std::shared_ptr m_pDeps = std::make_shared>(); + NiceMock m_appConfig; + NiceMock m_serverConfig; +}; + +TEST_F(ServerConnectionTests, handleLogLine_newClient_shouldShowPrompt) { + ServerConnection serverConnection( + nullptr, m_appConfig, m_serverConfig, m_pDeps); + + QString clientName = "test client"; + EXPECT_CALL(*m_pDeps, showNewClientPrompt(_, clientName)); + + serverConnection.handleLogLine(R"(unrecognised client name "test client")"); +} + +TEST_F(ServerConnectionTests, handleLogLine_ignoredClient_shouldNotShowPrompt) { + ServerConnection serverConnection( + nullptr, m_appConfig, m_serverConfig, m_pDeps); + ON_CALL(*m_pDeps, showNewClientPrompt(_, _)) + .WillByDefault(testing::Return(messages::NewClientPromptResult::Ignore)); + serverConnection.handleLogLine(R"(unrecognised client name "stub")"); + + EXPECT_CALL(*m_pDeps, showNewClientPrompt(_, _)).Times(0); + + serverConnection.handleLogLine(R"(unrecognised client name "stub")"); +} + +TEST_F( + ServerConnectionTests, handleLogLine_serverConfigFull_shouldNotShowPrompt) { + ServerConnection serverConnection( + nullptr, m_appConfig, m_serverConfig, m_pDeps); + ON_CALL(m_serverConfig, isFull()).WillByDefault(testing::Return(true)); + + EXPECT_CALL(*m_pDeps, showNewClientPrompt(_, _)).Times(0); + + serverConnection.handleLogLine(R"(unrecognised client name "test client")"); +} + +TEST_F(ServerConnectionTests, handleLogLine_screenExists_shouldNotShowPrompt) { + ServerConnection serverConnection( + nullptr, m_appConfig, m_serverConfig, m_pDeps); + ON_CALL(m_serverConfig, screenExists(_)).WillByDefault(testing::Return(true)); + + EXPECT_CALL(*m_pDeps, showNewClientPrompt(_, _)).Times(0); + + serverConnection.handleLogLine(R"(unrecognised client name "test client")"); +}