From fc879323bc98e2452b9a9adf572524f5b893882e Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Wed, 12 Oct 2016 12:56:52 +0100 Subject: [PATCH 01/88] #5657 Removed password log in in activation window --- src/gui/res/ActivationDialog.ui | 94 +++++++------------------------ src/gui/src/ActivationDialog.cpp | 95 +++++--------------------------- src/gui/src/ActivationDialog.h | 4 -- 3 files changed, 33 insertions(+), 160 deletions(-) diff --git a/src/gui/res/ActivationDialog.ui b/src/gui/res/ActivationDialog.ui index fb700f91..d11a1a9f 100644 --- a/src/gui/res/ActivationDialog.ui +++ b/src/gui/res/ActivationDialog.ui @@ -7,7 +7,7 @@ 0 0 440 - 314 + 214 @@ -15,7 +15,7 @@ - + 75 @@ -23,76 +23,7 @@ - &Account login - - - true - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - 20 - - - 10 - - - - - Email: - - - - - - - - 0 - 0 - - - - QLineEdit::Normal - - - - - - - Password: - - - - - - - - 0 - 0 - - - - QLineEdit::Password - - - - - - - - - - 75 - true - - - - &Serial key + Serial key @@ -109,20 +40,33 @@ - false + true <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> +</style></head><body style=" font-family:'.SF NS Text'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p></body></html> false + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 40251019..2170ef86 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -20,26 +20,8 @@ ActivationDialog::ActivationDialog(QWidget* parent, AppConfig& appConfig) : m_appConfig (&appConfig) { ui->setupUi(this); - - ui->m_pLineEditEmail->setText(appConfig.activateEmail()); - ui->m_pTextEditSerialKey->setText(appConfig.serialKey()); - - if (!appConfig.serialKey().isEmpty()) { - ui->m_pRadioButtonActivate->setAutoExclusive(false); - ui->m_pRadioButtonSubscription->setAutoExclusive(false); - ui->m_pRadioButtonActivate->setChecked(false); - ui->m_pRadioButtonSubscription->setChecked(true); - ui->m_pRadioButtonActivate->setAutoExclusive(true); - ui->m_pRadioButtonSubscription->setAutoExclusive(true); - ui->m_pTextEditSerialKey->setFocus(); - ui->m_pTextEditSerialKey->moveCursor(QTextCursor::End); - } else { - if (ui->m_pLineEditEmail->text().isEmpty()) { - ui->m_pLineEditEmail->setFocus(); - } else { - ui->m_pLineEditPassword->setFocus(); - } - } + ui->m_pTextEditSerialKey->setFocus(); + ui->m_pTextEditSerialKey->moveCursor(QTextCursor::End); } ActivationDialog::~ActivationDialog() @@ -74,30 +56,6 @@ void ActivationDialog::reject() } } -void ActivationDialog::on_m_pRadioButtonSubscription_toggled(bool checked) -{ - if (checked) { - ui->m_pLineEditEmail->setEnabled(false); - ui->m_pLineEditPassword->setEnabled(false); - ui->m_pTextEditSerialKey->setEnabled(true); - ui->m_pTextEditSerialKey->setFocus(); - } -} - -void ActivationDialog::on_m_pRadioButtonActivate_toggled(bool checked) -{ - if (checked) { - ui->m_pLineEditEmail->setEnabled(true); - ui->m_pLineEditPassword->setEnabled(true); - ui->m_pTextEditSerialKey->setEnabled(false); - if (ui->m_pLineEditEmail->text().isEmpty()) { - ui->m_pLineEditEmail->setFocus(); - } else { - ui->m_pLineEditPassword->setFocus(); - } - } -} - void ActivationDialog::accept() { QMessageBox message; @@ -108,45 +66,20 @@ void ActivationDialog::accept() m_appConfig->saveSettings(); try { - if (ui->m_pRadioButtonActivate->isChecked()) { - WebClient webClient; - QString email = ui->m_pLineEditEmail->text(); - QString password = ui->m_pLineEditPassword->text(); + QString serialKey = ui->m_pTextEditSerialKey->toPlainText(); - if (!webClient.setEmail (email, error)) { - message.critical (this, "Invalid Email Address", tr("%1").arg(error)); - return; - } - else if (!webClient.setPassword (password, error)) { - message.critical (this, "Invalid Password", tr("%1").arg(error)); - return; - } - else if (!webClient.getEdition (edition, error)) { - FailedLoginDialog failedLoginDialog (this, error); - failedLoginDialog.exec(); - return; - } - - m_appConfig->setActivateEmail (email); - m_appConfig->clearSerialKey(); - ui->m_pTextEditSerialKey->clear(); - notifyActivation ("login:" + m_appConfig->activateEmail()); + if (!m_appConfig->setSerialKey (serialKey, error)) { + message.critical(this, "Invalid Serial Key", tr("%1").arg(error)); + return; } - else { - QString serialKey = ui->m_pTextEditSerialKey->toPlainText(); - if (!m_appConfig->setSerialKey (serialKey, error)) { - message.critical (this, "Invalid Serial Key", tr("%1").arg(error)); - return; - } - - SubscriptionManager subscriptionManager (this, *m_appConfig, edition); - if (!subscriptionManager.activateSerial (serialKey)) { - return; - } - m_appConfig->setActivateEmail(""); - notifyActivation ("serial:" + m_appConfig->serialKey()); + SubscriptionManager subscriptionManager (this, *m_appConfig, edition); + if (!subscriptionManager.activateSerial (serialKey)) { + return; } + m_appConfig->setActivateEmail(""); + notifyActivation("serial:" + m_appConfig->serialKey()); + } catch (std::exception& e) { message.critical (this, "Unknown Error", @@ -159,7 +92,7 @@ void ActivationDialog::accept() m_appConfig->setEdition(edition); m_appConfig->saveSettings(); - message.information (this, "Activated!", - tr("Thanks for activating %1!").arg (getEditionName (edition))); + message.information(this, "Activated!", + tr("Thanks for activating %1!").arg (getEditionName (edition))); QDialog::accept(); } diff --git a/src/gui/src/ActivationDialog.h b/src/gui/src/ActivationDialog.h index 6fb926cc..0f3328ac 100644 --- a/src/gui/src/ActivationDialog.h +++ b/src/gui/src/ActivationDialog.h @@ -27,10 +27,6 @@ protected: private: Ui::ActivationDialog *ui; AppConfig* m_appConfig; - -private slots: - void on_m_pRadioButtonSubscription_toggled(bool checked); - void on_m_pRadioButtonActivate_toggled(bool checked); }; #endif // ACTIVATIONDIALOG_H From a50ae2ad36fca1471bcf454156f1806b6008737e Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Wed, 12 Oct 2016 12:59:38 +0100 Subject: [PATCH 02/88] Fixed code style --- src/gui/src/ActivationDialog.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 2170ef86..9f2e3d51 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -17,7 +17,7 @@ ActivationDialog::ActivationDialog(QWidget* parent, AppConfig& appConfig) : QDialog(parent), ui(new Ui::ActivationDialog), - m_appConfig (&appConfig) + m_appConfig(&appConfig) { ui->setupUi(this); ui->m_pTextEditSerialKey->setFocus(); @@ -68,13 +68,13 @@ void ActivationDialog::accept() try { QString serialKey = ui->m_pTextEditSerialKey->toPlainText(); - if (!m_appConfig->setSerialKey (serialKey, error)) { + if (!m_appConfig->setSerialKey(serialKey, error)) { message.critical(this, "Invalid Serial Key", tr("%1").arg(error)); return; } - SubscriptionManager subscriptionManager (this, *m_appConfig, edition); - if (!subscriptionManager.activateSerial (serialKey)) { + SubscriptionManager subscriptionManager(this, *m_appConfig, edition); + if (!subscriptionManager.activateSerial(serialKey)) { return; } m_appConfig->setActivateEmail(""); @@ -82,7 +82,7 @@ void ActivationDialog::accept() } catch (std::exception& e) { - message.critical (this, "Unknown Error", + message.critical(this, "Unknown Error", tr("An error occurred while trying to activate Synergy. " "Please contact the helpdesk, and provide the " "following details.\n\n%1").arg(e.what())); @@ -93,6 +93,6 @@ void ActivationDialog::accept() m_appConfig->saveSettings(); message.information(this, "Activated!", - tr("Thanks for activating %1!").arg (getEditionName (edition))); + tr("Thanks for activating %1!").arg(getEditionName(edition))); QDialog::accept(); } From 2b9f48602c13b5e783446e96ce348697bab7b38c Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Wed, 12 Oct 2016 15:09:29 +0100 Subject: [PATCH 03/88] #5620 Make Xcode 8 happy with null cast --- src/lib/platform/OSXClipboard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/platform/OSXClipboard.cpp b/src/lib/platform/OSXClipboard.cpp index 84f7c7ce..7dae2081 100644 --- a/src/lib/platform/OSXClipboard.cpp +++ b/src/lib/platform/OSXClipboard.cpp @@ -122,7 +122,7 @@ OSXClipboard::add(EFormat format, const String & data) PasteboardPutItemFlavor( m_pboard, - (PasteboardItemID) 0, + nullptr, flavorType, dataRef, kPasteboardFlavorNoFlags); From d1396c976727779accd7a7bac1bc61d8e154abfa Mon Sep 17 00:00:00 2001 From: rishubil Date: Tue, 28 Oct 2014 21:51:53 +0900 Subject: [PATCH 04/88] #3797 Fix "Unix Makefile" build on macOS --- ext/toolchain/commands1.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ext/toolchain/commands1.py b/ext/toolchain/commands1.py index 7f357029..78fd27e6 100644 --- a/ext/toolchain/commands1.py +++ b/ext/toolchain/commands1.py @@ -430,14 +430,16 @@ class InternalCommands: if generator.cmakeName.find('Unix Makefiles') != -1: cmake_args += ' -DCMAKE_BUILD_TYPE=' + target.capitalize() - elif sys.platform == "darwin": + if sys.platform == "darwin": macSdkMatch = re.match("(\d+)\.(\d+)", self.macSdk) if not macSdkMatch: raise Exception("unknown osx version: " + self.macSdk) - sdkDir = self.getMacSdkDir() - cmake_args += " -DCMAKE_OSX_SYSROOT=" + sdkDir - cmake_args += " -DCMAKE_OSX_DEPLOYMENT_TARGET=" + self.macSdk + if generator.cmakeName.find('Unix Makefiles') == -1: + sdkDir = self.getMacSdkDir() + cmake_args += " -DCMAKE_OSX_SYSROOT=" + sdkDir + cmake_args += " -DCMAKE_OSX_DEPLOYMENT_TARGET=" + self.macSdk + cmake_args += " -DOSX_TARGET_MAJOR=" + macSdkMatch.group(1) cmake_args += " -DOSX_TARGET_MINOR=" + macSdkMatch.group(2) @@ -551,7 +553,7 @@ class InternalCommands: if os.path.exists(sdkPath): return sdkPath - return "/Developer/SDKs/" + sdkDirName + ".sdk" + return os.popen('xcodebuild -version -sdk macosx' + self.macSdk + ' Path').read().strip() # http://tinyurl.com/cs2rxxb def fixCmakeEclipseBug(self): From df88faaad8dccba38f91f026dc09b4984539d239 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Wed, 12 Oct 2016 15:43:01 +0100 Subject: [PATCH 05/88] #5620 Convert OSXScreen into Objective C++ --- src/lib/platform/OSXScreen.h | 2 ++ src/lib/platform/{OSXScreen.cpp => OSXScreen.mm} | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) rename src/lib/platform/{OSXScreen.cpp => OSXScreen.mm} (99%) diff --git a/src/lib/platform/OSXScreen.h b/src/lib/platform/OSXScreen.h index dce5ac08..0357583e 100644 --- a/src/lib/platform/OSXScreen.h +++ b/src/lib/platform/OSXScreen.h @@ -344,4 +344,6 @@ private: Mutex* m_carbonLoopMutex; CondVar* m_carbonLoopReady; #endif + + class OSXScreenImpl* m_impl; }; diff --git a/src/lib/platform/OSXScreen.cpp b/src/lib/platform/OSXScreen.mm similarity index 99% rename from src/lib/platform/OSXScreen.cpp rename to src/lib/platform/OSXScreen.mm index e81f1621..aa8f1e95 100644 --- a/src/lib/platform/OSXScreen.cpp +++ b/src/lib/platform/OSXScreen.mm @@ -112,7 +112,8 @@ OSXScreen::OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCurso m_lastSingleClickYCursor(0), m_autoShowHideCursor(autoShowHideCursor), m_events(events), - m_getDropTargetThread(NULL) + m_getDropTargetThread(NULL), + m_impl(NULL) { try { m_displayID = CGMainDisplayID(); From 5ea1fdc7c69059a8f94cbb79e2a5e6bf53b2f1c0 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Wed, 12 Oct 2016 16:10:57 +0100 Subject: [PATCH 06/88] #5620 Remove deprecated NXClickTime call --- src/lib/platform/CMakeLists.txt | 2 +- src/lib/platform/OSXScreen.mm | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/platform/CMakeLists.txt b/src/lib/platform/CMakeLists.txt index 6c272c21..481d8ef9 100644 --- a/src/lib/platform/CMakeLists.txt +++ b/src/lib/platform/CMakeLists.txt @@ -19,7 +19,7 @@ if (WIN32) file(GLOB sources "MSWindows*.cpp") elseif (APPLE) file(GLOB headers "OSX*.h" "IOSX*.h") - file(GLOB sources "OSX*.cpp" "IOSX*.cpp" "OSX*.m") + file(GLOB sources "OSX*.cpp" "IOSX*.cpp" "OSX*.m" "OSX*.mm") elseif (UNIX) file(GLOB headers "XWindows*.h") file(GLOB sources "XWindows*.cpp") diff --git a/src/lib/platform/OSXScreen.mm b/src/lib/platform/OSXScreen.mm index aa8f1e95..2c2e66d6 100644 --- a/src/lib/platform/OSXScreen.mm +++ b/src/lib/platform/OSXScreen.mm @@ -45,6 +45,8 @@ #include #include +#import + // Set some enums for fast user switching if we're building with an SDK // from before such support was added. #if !defined(MAC_OS_X_VERSION_10_3) || \ @@ -527,9 +529,7 @@ OSXScreen::fakeMouseButton(ButtonID id, bool press) // we define our own defaults. const double maxDiff = sqrt(2) + 0.0001; - - NXEventHandle handle = NXOpenEventStatus(); - double clickTime = NXClickTime(handle); + double clickTime = [NSEvent doubleClickInterval]; // As long as the click is within the time window and distance window // increase clickState (double click, triple click, etc) From c21fc4a6dd73bb8b1afa46ec551df3062d1aa86f Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Wed, 12 Oct 2016 16:50:11 +0100 Subject: [PATCH 07/88] #3797 Revert to using hardcoded SDK path for buildbot --- ext/toolchain/commands1.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/toolchain/commands1.py b/ext/toolchain/commands1.py index 78fd27e6..9c28ab75 100644 --- a/ext/toolchain/commands1.py +++ b/ext/toolchain/commands1.py @@ -553,7 +553,8 @@ class InternalCommands: if os.path.exists(sdkPath): return sdkPath - return os.popen('xcodebuild -version -sdk macosx' + self.macSdk + ' Path').read().strip() + # return os.popen('xcodebuild -version -sdk macosx' + self.macSdk + ' Path').read().strip() + return "/Developer/SDKs/" + sdkDirName + ".sdk" # http://tinyurl.com/cs2rxxb def fixCmakeEclipseBug(self): From 817f8f2bcb92f96e2fcd47792ba02a69898155ed Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 13 Oct 2016 11:11:37 +0100 Subject: [PATCH 08/88] Updated git ignore list --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index fae40b00..347b91b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ config.h +.DS_Store *.pyc +*.o *~ \.*.swp +*build-gui-Desktop_Qt* /bin /lib /build From c7cd74ab5fb9b2952eaab2df0cc799cc765a6f07 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 13 Oct 2016 11:11:56 +0100 Subject: [PATCH 09/88] Fixed code style --- src/gui/src/MainWindow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index f9903a12..c927839f 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -746,7 +746,6 @@ bool MainWindow::serverArgs(QStringList& args, QString& app) } } - app = appPath(appConfig().synergysName()); if (!QFile::exists(app)) From d92fcd2453ea32f70e20798b459ec82a671a11d1 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 13 Oct 2016 11:15:38 +0100 Subject: [PATCH 10/88] #5657 Added version 2 trial serial key support --- src/lib/synergy/SubscriptionKey.h | 1 + src/lib/synergy/SubscriptionManager.cpp | 45 +++++++++++++++++++------ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/lib/synergy/SubscriptionKey.h b/src/lib/synergy/SubscriptionKey.h index 28744fed..d63a7d8f 100644 --- a/src/lib/synergy/SubscriptionKey.h +++ b/src/lib/synergy/SubscriptionKey.h @@ -27,4 +27,5 @@ struct SubscriptionKey { int m_userLimit; int m_warnTime; int m_expireTime; + bool m_trial; }; diff --git a/src/lib/synergy/SubscriptionManager.cpp b/src/lib/synergy/SubscriptionManager.cpp index 78207dc2..a6a68b5b 100644 --- a/src/lib/synergy/SubscriptionManager.cpp +++ b/src/lib/synergy/SubscriptionManager.cpp @@ -140,9 +140,11 @@ SubscriptionManager::parsePlainSerial(const String& plainText, SubscriptionKey& pos += 1; } - // e.g.: {v1;trial;Bob;1;email;company name;1398297600;1398384000} + bool validSerial = false; + if ((parts.size() == 8) && (parts.at(0).find("v1") != String::npos)) { + // e.g.: {v1;basic;Bob;1;email;company name;1398297600;1398384000} key.m_type = parts.at(1); key.m_name = parts.at(2); sscanf(parts.at(3).c_str(), "%d", &key.m_userLimit); @@ -150,9 +152,23 @@ SubscriptionManager::parsePlainSerial(const String& plainText, SubscriptionKey& key.m_company = parts.at(5); sscanf(parts.at(6).c_str(), "%d", &key.m_warnTime); sscanf(parts.at(7).c_str(), "%d", &key.m_expireTime); - + + validSerial = true; + } + else if ((parts.size() == 9) + && (parts.at(0).find("v2") != String::npos)) { + // e.g.: {v2;trial;basic;Bob;1;email;company name;1398297600;1398384000} + key.m_trial = parts.at(1) == "trial" ? true : false; + key.m_type = parts.at(2); + key.m_name = parts.at(3); + sscanf(parts.at(4).c_str(), "%d", &key.m_userLimit); + key.m_email = parts.at(5); + key.m_company = parts.at(6); + sscanf(parts.at(7).c_str(), "%d", &key.m_warnTime); + sscanf(parts.at(8).c_str(), "%d", &key.m_expireTime); + // only limit to trial version - if (key.m_type == "trial") { + if (key.m_trial) { if (time(0) > key.m_expireTime) { throw XSubscription("trial has expired"); } @@ -161,18 +177,25 @@ SubscriptionManager::parsePlainSerial(const String& plainText, SubscriptionKey& const int spd = 60 * 60 * 24; int dayLeft = secLeft / spd + 1; LOG((CLOG_NOTE "trial will end in %d %s", - dayLeft, - dayLeft == 1 ? "day" : "days")); + dayLeft, + dayLeft == 1 ? "day" : "days")); + } + else { + } } - + + validSerial = true; + } + + if (validSerial) { const char* userText = (key.m_userLimit == 1) ? "user" : "users"; LOG((CLOG_INFO "%s subscription valid is for %d %s, registered to %s", - key.m_type.c_str(), - key.m_userLimit, - userText, - key.m_name.c_str())); - + key.m_type.c_str(), + key.m_userLimit, + userText, + key.m_name.c_str())); + return; } } From 4be9fc1800d4e6e4c379feecfe536ea6e3dbc8b5 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 13 Oct 2016 11:18:03 +0100 Subject: [PATCH 11/88] Removed unused code --- src/lib/synergy/ArgParser.cpp | 8 -------- src/lib/synergy/ToolApp.cpp | 11 ----------- src/lib/synergy/ToolApp.h | 1 - src/lib/synergy/ToolArgs.cpp | 2 -- src/lib/synergy/ToolArgs.h | 2 -- 5 files changed, 24 deletions(-) diff --git a/src/lib/synergy/ArgParser.cpp b/src/lib/synergy/ArgParser.cpp index 431a91b7..4e1dafdd 100644 --- a/src/lib/synergy/ArgParser.cpp +++ b/src/lib/synergy/ArgParser.cpp @@ -189,18 +189,10 @@ ArgParser::parseToolArgs(ToolArgs& args, int argc, const char* const* argv) args.m_loginAuthenticate = true; return true; } - else if (isArg(i, argc, argv, NULL, "--get-plugin-list", 0)) { - args.m_getPluginList = true; - return true; - } else if (isArg(i, argc, argv, NULL, "--get-installed-dir", 0)) { args.m_getInstalledDir = true; return true; } - else if (isArg(i, argc, argv, NULL, "--get-plugin-dir", 0)) { - args.m_getPluginDir = true; - return true; - } else if (isArg(i, argc, argv, NULL, "--get-profile-dir", 0)) { args.m_getProfileDir = true; return true; diff --git a/src/lib/synergy/ToolApp.cpp b/src/lib/synergy/ToolApp.cpp index e6695f51..2aafdea1 100644 --- a/src/lib/synergy/ToolApp.cpp +++ b/src/lib/synergy/ToolApp.cpp @@ -72,15 +72,9 @@ ToolApp::run(int argc, char** argv) else if (m_args.m_loginAuthenticate) { loginAuth(); } - else if (m_args.m_getPluginList) { - getPluginList(); - } else if (m_args.m_getInstalledDir) { std::cout << ARCH->getInstalledDirectory() << std::endl; } - else if (m_args.m_getPluginDir) { - std::cout << ARCH->getPluginDirectory() << std::endl; - } else if (m_args.m_getProfileDir) { std::cout << ARCH->getProfileDirectory() << std::endl; } @@ -171,11 +165,6 @@ ToolApp::loginAuth() } } -void -ToolApp::getPluginList() -{ -} - void ToolApp::notifyActivation() { diff --git a/src/lib/synergy/ToolApp.h b/src/lib/synergy/ToolApp.h index 8706c79a..39c87ca7 100644 --- a/src/lib/synergy/ToolApp.h +++ b/src/lib/synergy/ToolApp.h @@ -29,7 +29,6 @@ public: private: void loginAuth(); - void getPluginList(); void notifyActivation(); private: diff --git a/src/lib/synergy/ToolArgs.cpp b/src/lib/synergy/ToolArgs.cpp index f5d2524a..500a1692 100644 --- a/src/lib/synergy/ToolArgs.cpp +++ b/src/lib/synergy/ToolArgs.cpp @@ -20,8 +20,6 @@ ToolArgs::ToolArgs() : m_printActiveDesktopName(false), m_loginAuthenticate(false), - m_getPluginList(false), - m_getPluginDir(false), m_getInstalledDir(false), m_getProfileDir(false), m_getArch(false), diff --git a/src/lib/synergy/ToolArgs.h b/src/lib/synergy/ToolArgs.h index 0ebc0a4a..df126d89 100644 --- a/src/lib/synergy/ToolArgs.h +++ b/src/lib/synergy/ToolArgs.h @@ -26,8 +26,6 @@ public: public: bool m_printActiveDesktopName; bool m_loginAuthenticate; - bool m_getPluginList; - bool m_getPluginDir; bool m_getInstalledDir; bool m_getProfileDir; bool m_getArch; From 82e55702ef031f6e28fcdfe4bdfe0ee5bb6d954e Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 13 Oct 2016 11:20:43 +0100 Subject: [PATCH 12/88] #5657 Removed serial key code from syntool --- src/lib/synergy/ArgParser.cpp | 16 ---------------- src/lib/synergy/ToolApp.cpp | 30 ------------------------------ src/lib/synergy/ToolArgs.cpp | 5 +---- src/lib/synergy/ToolArgs.h | 3 --- 4 files changed, 1 insertion(+), 53 deletions(-) diff --git a/src/lib/synergy/ArgParser.cpp b/src/lib/synergy/ArgParser.cpp index 4e1dafdd..11adc2d5 100644 --- a/src/lib/synergy/ArgParser.cpp +++ b/src/lib/synergy/ArgParser.cpp @@ -201,22 +201,6 @@ ArgParser::parseToolArgs(ToolArgs& args, int argc, const char* const* argv) args.m_getArch = true; return true; } - else if (isArg(i, argc, argv, NULL, "--subscription-serial", 1)) { - args.m_subscriptionSerial = argv[++i]; - if (args.m_subscriptionSerial.empty()) { - LOG((CLOG_CRIT "subscription error: serial was not provided")); - return false; - } - return true; - } - else if (isArg(i, argc, argv, NULL, "--get-subscription-filename", 0)) { - args.m_getSubscriptionFilename = true; - return true; - } - else if (isArg(i, argc, argv, NULL, "--check-subscription", 0)) { - args.m_checkSubscription = true; - return true; - } else if (isArg(i, argc, argv, NULL, "--notify-activation", 0)) { args.m_notifyActivation = true; return true; diff --git a/src/lib/synergy/ToolApp.cpp b/src/lib/synergy/ToolApp.cpp index 2aafdea1..444997d8 100644 --- a/src/lib/synergy/ToolApp.cpp +++ b/src/lib/synergy/ToolApp.cpp @@ -81,36 +81,6 @@ ToolApp::run(int argc, char** argv) else if (m_args.m_getArch) { std::cout << ARCH->getPlatformName() << std::endl; } - else if (!m_args.m_subscriptionSerial.empty()) { - try { - SubscriptionManager subscriptionManager; - subscriptionManager.activate(m_args.m_subscriptionSerial); - } - catch (XSubscription& e) { - LOG((CLOG_CRIT "subscription error: %s", e.what())); - return kExitSubscription; - } - } - else if (m_args.m_getSubscriptionFilename) { - try { - SubscriptionManager subscriptionManager; - subscriptionManager.printFilename(); - } - catch (XSubscription& e) { - LOG((CLOG_CRIT "subscription error: %s", e.what())); - return kExitSubscription; - } - } - else if (m_args.m_checkSubscription) { - try { - SubscriptionManager subscriptionManager; - subscriptionManager.checkFile(""); - } - catch (XSubscription& e) { - LOG((CLOG_CRIT "subscription error: %s", e.what())); - return kExitSubscription; - } - } else if (m_args.m_notifyActivation) { notifyActivation(); } diff --git a/src/lib/synergy/ToolArgs.cpp b/src/lib/synergy/ToolArgs.cpp index 500a1692..5f67c666 100644 --- a/src/lib/synergy/ToolArgs.cpp +++ b/src/lib/synergy/ToolArgs.cpp @@ -23,9 +23,6 @@ ToolArgs::ToolArgs() : m_getInstalledDir(false), m_getProfileDir(false), m_getArch(false), - m_getSubscriptionFilename(false), - m_checkSubscription(false), - m_notifyActivation(false), - m_subscriptionSerial() + m_notifyActivation(false) { } diff --git a/src/lib/synergy/ToolArgs.h b/src/lib/synergy/ToolArgs.h index df126d89..5febab9e 100644 --- a/src/lib/synergy/ToolArgs.h +++ b/src/lib/synergy/ToolArgs.h @@ -29,8 +29,5 @@ public: bool m_getInstalledDir; bool m_getProfileDir; bool m_getArch; - bool m_getSubscriptionFilename; - bool m_checkSubscription; bool m_notifyActivation; - String m_subscriptionSerial; }; From 92680b2877f45eb7956cb339667981fe26be50cd Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 13 Oct 2016 13:53:09 +0100 Subject: [PATCH 13/88] #5657 Extracted shared code between GUI and core --- src/lib/CMakeLists.txt | 2 +- src/lib/shared/CMakeLists.txt | 26 ++ .../SubscriptionKey.h => shared/SerialKey.h} | 22 +- src/lib/synergy/SubscriptionManager.cpp | 222 ------------------ src/lib/synergy/SubscriptionManager.h | 55 ----- src/lib/synergy/ToolApp.cpp | 1 - .../unittests/synergy/SubscriptionTests.cpp | 196 ++++++++-------- 7 files changed, 141 insertions(+), 383 deletions(-) create mode 100644 src/lib/shared/CMakeLists.txt rename src/lib/{synergy/SubscriptionKey.h => shared/SerialKey.h} (66%) delete mode 100644 src/lib/synergy/SubscriptionManager.cpp delete mode 100644 src/lib/synergy/SubscriptionManager.h diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 48beb80a..f53d9db8 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -25,7 +25,7 @@ add_subdirectory(net) add_subdirectory(platform) add_subdirectory(server) add_subdirectory(synergy) - +add_subdirectory(shared) if (WIN32) add_subdirectory(synwinhk) diff --git a/src/lib/shared/CMakeLists.txt b/src/lib/shared/CMakeLists.txt new file mode 100644 index 00000000..042d866c --- /dev/null +++ b/src/lib/shared/CMakeLists.txt @@ -0,0 +1,26 @@ +# synergy -- mouse and keyboard sharing utility +# Copyright (C) 2016 Symless Ltd. +# +# This package is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# found in the file LICENSE that should have accompanied this file. +# +# This package is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +file(GLOB headers "*.h") +file(GLOB sources "*.cpp") + +if (SYNERGY_ADD_HEADERS) + list(APPEND sources ${headers}) +endif() + +add_library(shared STATIC ${sources}) + +target_link_libraries(shared arch base) + diff --git a/src/lib/synergy/SubscriptionKey.h b/src/lib/shared/SerialKey.h similarity index 66% rename from src/lib/synergy/SubscriptionKey.h rename to src/lib/shared/SerialKey.h index d63a7d8f..45d9728e 100644 --- a/src/lib/synergy/SubscriptionKey.h +++ b/src/lib/shared/SerialKey.h @@ -17,13 +17,23 @@ #pragma once -#include "base/String.h" +#include -struct SubscriptionKey { - String m_name; - String m_type; - String m_email; - String m_company; +class SerialKey { +public: + SerialKey(std::string serial); + + bool isValid(unsigned long long currentTime) const; + bool isExpiring(unsigned long long currentTime) const; + bool isExpired(unsigned long long currentTime) const; + bool isTrial() const; + int edition() const; + +private: + std::string m_name; + std::string m_type; + std::string m_email; + std::string m_company; int m_userLimit; int m_warnTime; int m_expireTime; diff --git a/src/lib/synergy/SubscriptionManager.cpp b/src/lib/synergy/SubscriptionManager.cpp deleted file mode 100644 index a6a68b5b..00000000 --- a/src/lib/synergy/SubscriptionManager.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/* - * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2015 Synergy Seamless Inc. - * - * 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 "synergy/SubscriptionManager.h" - -#include "synergy/XSynergy.h" -#include "arch/Arch.h" -#include "base/Log.h" -#include "base/String.h" -#include "common/Version.h" - -#include -#include -#include -#include -#include -//#include - -#if SYSAPI_WIN32 -const char* kFile = "Synergy.subkey"; -#else -const char* kFile = ".synergy.subkey"; -#endif - -// -// SubscriptionManager -// - -SubscriptionManager::SubscriptionManager() : - m_key() -{ -} - -void -SubscriptionManager::checkFile(const String& filename_) -{ - String filename = filename_; - if (filename.empty()) { - filename = getFilename(); - } - - std::ifstream stream(filename.c_str()); - if (!stream.is_open()) { - throw XSubscription(synergy::string::sprintf( - "Could not open, path=%s", filename.c_str())); - } - - String serial; - stream >> serial; - - String plainText = decode(serial); - parsePlainSerial(plainText, m_key); - - LOG((CLOG_DEBUG "subscription is valid")); -} - -void -SubscriptionManager::activate(const String& serial) -{ - String plainText = decode(serial); - parsePlainSerial(plainText, m_key); - - String filename = getFilename(); - std::ofstream stream(filename.c_str()); - if (!stream.is_open()) { - throw XSubscription(synergy::string::sprintf( - "Could not open, file=%s", filename.c_str())); - } - - stream << serial << std::endl; - LOG((CLOG_DEBUG "subscription file created, path=%s", filename.c_str())); -} - -String -SubscriptionManager::decode(const String& input) -{ - static const char* const lut = "0123456789ABCDEF"; - size_t len = input.length(); - if (len & 1) { - throw XSubscription("Invalid serial, wrong length."); - } - - String output; - output.reserve(len / 2); - for (size_t i = 0; i < len; i += 2) { - - char a = input[i]; - char b = input[i + 1]; - - const char* p = std::lower_bound(lut, lut + 16, a); - const char* q = std::lower_bound(lut, lut + 16, b); - - if (*q != b || *p != a) { - throw XSubscription("Invalid serial, unrecognized digit."); - } - - output.push_back(static_cast(((p - lut) << 4) | (q - lut))); - } - - return output; -} - -void -SubscriptionManager::parsePlainSerial(const String& plainText, SubscriptionKey& key) -{ - String serial; - String parityStart = plainText.substr(0, 1); - String parityEnd = plainText.substr(plainText.length() - 1, 1); - - // check for parity chars { and }, record parity result, then remove them. - if (parityStart == "{" && parityEnd == "}") { - serial = plainText.substr(1, plainText.length() - 2); - - // tokenize serialised subscription. - std::vector parts; - std::string::size_type pos = 0; - bool look = true; - while (look) { - std::string::size_type start = pos; - pos = serial.find(";", pos); - if (pos == String::npos) { - pos = plainText.length(); - look = false; - } - parts.push_back(serial.substr(start, pos - start)); - pos += 1; - } - - bool validSerial = false; - - if ((parts.size() == 8) - && (parts.at(0).find("v1") != String::npos)) { - // e.g.: {v1;basic;Bob;1;email;company name;1398297600;1398384000} - key.m_type = parts.at(1); - key.m_name = parts.at(2); - sscanf(parts.at(3).c_str(), "%d", &key.m_userLimit); - key.m_email = parts.at(4); - key.m_company = parts.at(5); - sscanf(parts.at(6).c_str(), "%d", &key.m_warnTime); - sscanf(parts.at(7).c_str(), "%d", &key.m_expireTime); - - validSerial = true; - } - else if ((parts.size() == 9) - && (parts.at(0).find("v2") != String::npos)) { - // e.g.: {v2;trial;basic;Bob;1;email;company name;1398297600;1398384000} - key.m_trial = parts.at(1) == "trial" ? true : false; - key.m_type = parts.at(2); - key.m_name = parts.at(3); - sscanf(parts.at(4).c_str(), "%d", &key.m_userLimit); - key.m_email = parts.at(5); - key.m_company = parts.at(6); - sscanf(parts.at(7).c_str(), "%d", &key.m_warnTime); - sscanf(parts.at(8).c_str(), "%d", &key.m_expireTime); - - // only limit to trial version - if (key.m_trial) { - if (time(0) > key.m_expireTime) { - throw XSubscription("trial has expired"); - } - else if (time(0) > key.m_warnTime) { - int secLeft = key.m_expireTime - static_cast(time(0)); - const int spd = 60 * 60 * 24; - int dayLeft = secLeft / spd + 1; - LOG((CLOG_NOTE "trial will end in %d %s", - dayLeft, - dayLeft == 1 ? "day" : "days")); - } - else { - - } - } - - validSerial = true; - } - - if (validSerial) { - const char* userText = (key.m_userLimit == 1) ? "user" : "users"; - LOG((CLOG_INFO "%s subscription valid is for %d %s, registered to %s", - key.m_type.c_str(), - key.m_userLimit, - userText, - key.m_name.c_str())); - - return; - } - } - - throw XSubscription(synergy::string::sprintf("Serial is invalid.")); -} - -String -SubscriptionManager::getFilename() -{ - String path = ARCH->getProfileDirectory(); - path = ARCH->concatPath(path, kFile); - if (path.empty()) { - throw XSubscription("Could not get filename."); - } - - return path; -} - -void -SubscriptionManager::printFilename() -{ - std::cout << getFilename() << std::endl; -} diff --git a/src/lib/synergy/SubscriptionManager.h b/src/lib/synergy/SubscriptionManager.h deleted file mode 100644 index fb52701b..00000000 --- a/src/lib/synergy/SubscriptionManager.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2015 Synergy Seamless Inc. - * - * 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 "SubscriptionKey.h" -#include "common/common.h" - -#include "gtest/gtest_prod.h" - -class SubscriptionManager { -public: - SubscriptionManager(); - - //! Check the subscription activation file - void checkFile(const String& filename); - - //! Create a subscription activation file based on a serial - void activate(const String& serial); - - //! Use standard output to return subscription filename to gui - void printFilename(); - -private: - FRIEND_TEST(SubscriptionTests, decode_invalidLength_throwException); - FRIEND_TEST(SubscriptionTests, decode_invalidSerial_outputPlainText); - FRIEND_TEST(SubscriptionTests, decode_unrecognizedDigit_throwException); - FRIEND_TEST(SubscriptionTests, parsePlainSerial_noParity_throwException); - FRIEND_TEST(SubscriptionTests, parsePlainSerial_invalidSerial_throwException); - FRIEND_TEST(SubscriptionTests, parsePlainSerial_validSerial_validSubscriptionKey); - FRIEND_TEST(SubscriptionTests, parsePlainSerial_expiredTrialSerial_throwException); - FRIEND_TEST(SubscriptionTests, parsePlainSerial_expiredBasicSerial_validSubscriptionKey); - FRIEND_TEST(SubscriptionTests, parsePlainSerial_validSerialWithoutCompany_validSubscriptionKey); - -private: - String decode(const String& input); - void parsePlainSerial(const String& plainText, SubscriptionKey& key); - String getFilename(); - - SubscriptionKey m_key; -}; diff --git a/src/lib/synergy/ToolApp.cpp b/src/lib/synergy/ToolApp.cpp index 444997d8..bf3dfdce 100644 --- a/src/lib/synergy/ToolApp.cpp +++ b/src/lib/synergy/ToolApp.cpp @@ -18,7 +18,6 @@ #include "synergy/ToolApp.h" #include "synergy/ArgParser.h" -#include "synergy/SubscriptionManager.h" #include "arch/Arch.h" #include "base/Log.h" #include "base/String.h" diff --git a/src/test/unittests/synergy/SubscriptionTests.cpp b/src/test/unittests/synergy/SubscriptionTests.cpp index eeda3e7b..2cab31b9 100644 --- a/src/test/unittests/synergy/SubscriptionTests.cpp +++ b/src/test/unittests/synergy/SubscriptionTests.cpp @@ -14,101 +14,101 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - -#include "synergy/SubscriptionManager.h" -#include "synergy/XSynergy.h" - -#include "test/global/gtest.h" - -TEST(SubscriptionTests, decode_invalidLength_throwException) -{ - SubscriptionManager subscriptionManager; - String serial("ABC"); - - EXPECT_THROW(subscriptionManager.decode(serial), XSubscription); -} - -TEST(SubscriptionTests, decode_unrecognizedDigit_throwException) -{ - SubscriptionManager subscriptionManager; - String serial("MOCK"); - - EXPECT_THROW(subscriptionManager.decode(serial), XSubscription); -} - -TEST(SubscriptionTests, parsePlainSerial_noParity_throwException) -{ - SubscriptionManager subscriptionManager; - String painText("MOCK"); - SubscriptionKey key; - - EXPECT_THROW(subscriptionManager.parsePlainSerial(painText, key), XSubscription); -} - -TEST(SubscriptionTests, parsePlainSerial_invalidSerial_throwException) -{ - SubscriptionManager subscriptionManager; - String painText("{MOCK}"); - SubscriptionKey key; - - EXPECT_THROW(subscriptionManager.parsePlainSerial(painText, key), XSubscription); -} - -TEST(SubscriptionTests, parsePlainSerial_validSerial_validSubscriptionKey) -{ - // valid until 2 March 2049 - SubscriptionManager subscriptionManager; - String painText("{v1;trial;Bob;1;a@a.a;mock company;2147483647;2147483647}"); - SubscriptionKey key; - subscriptionManager.parsePlainSerial(painText, key); - - EXPECT_EQ("trial", key.m_type); - EXPECT_EQ("Bob", key.m_name); - EXPECT_EQ(1, key.m_userLimit); - EXPECT_EQ("a@a.a", key.m_email); - EXPECT_EQ("mock company", key.m_company); - EXPECT_EQ(2147483647, key.m_warnTime); - EXPECT_EQ(2147483647, key.m_expireTime); -} - -TEST(SubscriptionTests, parsePlainSerial_validSerialWithoutCompany_validSubscriptionKey) -{ - // valid until 2 March 2049 - SubscriptionManager subscriptionManager; - String painText("{v1;trial;Bob;1;a@a.a;;2147483647;2147483647}"); - SubscriptionKey key; - subscriptionManager.parsePlainSerial(painText, key); - - EXPECT_EQ("trial", key.m_type); - EXPECT_EQ("Bob", key.m_name); - EXPECT_EQ(1, key.m_userLimit); - EXPECT_EQ("a@a.a", key.m_email); - EXPECT_EQ("", key.m_company); - EXPECT_EQ(2147483647, key.m_warnTime); - EXPECT_EQ(2147483647, key.m_expireTime); -} - -TEST(SubscriptionTests, parsePlainSerial_expiredTrialSerial_throwException) -{ - SubscriptionManager subscriptionManager; - String painText("{v1;trial;Bob;1;1398297600;1398384000}"); - SubscriptionKey key; - - EXPECT_THROW(subscriptionManager.parsePlainSerial(painText, key), XSubscription); -} - -TEST(SubscriptionTests, parsePlainSerial_expiredBasicSerial_validSubscriptionKey) -{ - SubscriptionManager subscriptionManager; - String painText("{v1;basic;Bob;1;a@a.a;mock company;1398297600;1398384000}"); - SubscriptionKey key; - subscriptionManager.parsePlainSerial(painText, key); - - EXPECT_EQ("basic", key.m_type); - EXPECT_EQ("Bob", key.m_name); - EXPECT_EQ(1, key.m_userLimit); - EXPECT_EQ("a@a.a", key.m_email); - EXPECT_EQ("mock company", key.m_company); - EXPECT_EQ(1398297600, key.m_warnTime); - EXPECT_EQ(1398384000, key.m_expireTime); -} +// +//#include "synergy/LicenseManager.h" +//#include "synergy/XSynergy.h" +// +//#include "test/global/gtest.h" +// +//TEST(SubscriptionTests, decode_invalidLength_throwException) +//{ +// LicenseManager LicenseManager; +// String serial("ABC"); +// +// EXPECT_THROW(LicenseManager.decode(serial), XSubscription); +//} +// +//TEST(SubscriptionTests, decode_unrecognizedDigit_throwException) +//{ +// LicenseManager LicenseManager; +// String serial("MOCK"); +// +// EXPECT_THROW(LicenseManager.decode(serial), XSubscription); +//} +// +//TEST(SubscriptionTests, parsePlainSerial_noParity_throwException) +//{ +// LicenseManager LicenseManager; +// String painText("MOCK"); +// SubscriptionKey key; +// +// EXPECT_THROW(LicenseManager.parsePlainSerial(painText, key), XSubscription); +//} +// +//TEST(SubscriptionTests, parsePlainSerial_invalidSerial_throwException) +//{ +// LicenseManager LicenseManager; +// String painText("{MOCK}"); +// SubscriptionKey key; +// +// EXPECT_THROW(LicenseManager.parsePlainSerial(painText, key), XSubscription); +//} +// +//TEST(SubscriptionTests, parsePlainSerial_validSerial_validSubscriptionKey) +//{ +// // valid until 2 March 2049 +// LicenseManager LicenseManager; +// String painText("{v1;trial;Bob;1;a@a.a;mock company;2147483647;2147483647}"); +// SubscriptionKey key; +// LicenseManager.parsePlainSerial(painText, key); +// +// EXPECT_EQ("trial", key.m_type); +// EXPECT_EQ("Bob", key.m_name); +// EXPECT_EQ(1, key.m_userLimit); +// EXPECT_EQ("a@a.a", key.m_email); +// EXPECT_EQ("mock company", key.m_company); +// EXPECT_EQ(2147483647, key.m_warnTime); +// EXPECT_EQ(2147483647, key.m_expireTime); +//} +// +//TEST(SubscriptionTests, parsePlainSerial_validSerialWithoutCompany_validSubscriptionKey) +//{ +// // valid until 2 March 2049 +// LicenseManager LicenseManager; +// String painText("{v1;trial;Bob;1;a@a.a;;2147483647;2147483647}"); +// SubscriptionKey key; +// LicenseManager.parsePlainSerial(painText, key); +// +// EXPECT_EQ("trial", key.m_type); +// EXPECT_EQ("Bob", key.m_name); +// EXPECT_EQ(1, key.m_userLimit); +// EXPECT_EQ("a@a.a", key.m_email); +// EXPECT_EQ("", key.m_company); +// EXPECT_EQ(2147483647, key.m_warnTime); +// EXPECT_EQ(2147483647, key.m_expireTime); +//} +// +//TEST(SubscriptionTests, parsePlainSerial_expiredTrialSerial_throwException) +//{ +// LicenseManager LicenseManager; +// String painText("{v1;trial;Bob;1;1398297600;1398384000}"); +// SubscriptionKey key; +// +// EXPECT_THROW(LicenseManager.parsePlainSerial(painText, key), XSubscription); +//} +// +//TEST(SubscriptionTests, parsePlainSerial_expiredBasicSerial_validSubscriptionKey) +//{ +// LicenseManager LicenseManager; +// String painText("{v1;basic;Bob;1;a@a.a;mock company;1398297600;1398384000}"); +// SubscriptionKey key; +// LicenseManager.parsePlainSerial(painText, key); +// +// EXPECT_EQ("basic", key.m_type); +// EXPECT_EQ("Bob", key.m_name); +// EXPECT_EQ(1, key.m_userLimit); +// EXPECT_EQ("a@a.a", key.m_email); +// EXPECT_EQ("mock company", key.m_company); +// EXPECT_EQ(1398297600, key.m_warnTime); +// EXPECT_EQ(1398384000, key.m_expireTime); +//} From 92a885524bfc2d47c6505fffa7c5540a691adae0 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 13 Oct 2016 14:00:19 +0100 Subject: [PATCH 14/88] #5657 Added temporary implementation for SerialKey --- src/lib/shared/SerialKey.cpp | 60 ++++++++++++++++++++++++++++++++++++ src/lib/shared/SerialKey.h | 4 +-- 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/lib/shared/SerialKey.cpp diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp new file mode 100644 index 00000000..2b5f6040 --- /dev/null +++ b/src/lib/shared/SerialKey.cpp @@ -0,0 +1,60 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2016 Symless Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "SerialKey.h" + +SerialKey::SerialKey(std::string serial) : + m_userLimit(1), + m_warnTime(1), + m_expireTime(1), + m_trial(true) +{ + m_userLimit = 1; + m_warnTime = 1; + m_expireTime = 1; + m_trial = true; +} + +bool +SerialKey::isValid(unsigned long long currentTime) const +{ + return true; +} + +bool +SerialKey::isExpiring(unsigned long long currentTime) const +{ + return true; +} + +bool +SerialKey::isExpired(unsigned long long currentTime) const +{ + return true; +} + +bool +SerialKey::isTrial() const +{ + return true; +} + +int +SerialKey::edition() const +{ + return 1; +} diff --git a/src/lib/shared/SerialKey.h b/src/lib/shared/SerialKey.h index 45d9728e..d9101f41 100644 --- a/src/lib/shared/SerialKey.h +++ b/src/lib/shared/SerialKey.h @@ -1,6 +1,6 @@ /* * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2015 Synergy Seamless Inc. + * Copyright (C) 2016 Symless Ltd. * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,7 +31,7 @@ public: private: std::string m_name; - std::string m_type; + std::string m_edition; std::string m_email; std::string m_company; int m_userLimit; From 2a452307cd00181cc95a6e9e60124f6142bf0398 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 13 Oct 2016 17:55:09 +0100 Subject: [PATCH 15/88] #5657 Fleshed out the implementation of SerialKey --- src/lib/shared/SerialKey.cpp | 167 ++++++++++++++++++++++++++++++++--- src/lib/shared/SerialKey.h | 35 +++++++- 2 files changed, 185 insertions(+), 17 deletions(-) diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index 2b5f6040..5f50045e 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -17,44 +17,185 @@ #include "SerialKey.h" +#include + +using namespace std; + SerialKey::SerialKey(std::string serial) : m_userLimit(1), - m_warnTime(1), - m_expireTime(1), - m_trial(true) + m_warnTime(0), + m_expireTime(0), + m_edition(kBasic), + m_trial(true), + m_valid(false) { - m_userLimit = 1; - m_warnTime = 1; - m_expireTime = 1; - m_trial = true; + string plainText = decode(serial); + if (!plainText.empty()) { + parse(serial); + } } - + bool SerialKey::isValid(unsigned long long currentTime) const { - return true; + bool result = false; + + if (m_valid) { + if (m_trial) { + if (currentTime < m_expireTime) { + result = true; + } + } + else { + result = true; + } + } + + return result; } bool SerialKey::isExpiring(unsigned long long currentTime) const { - return true; + bool result = false; + + if (m_valid) { + if (m_warnTime < currentTime && currentTime < m_expireTime) { + result = true; + } + } + + return result; } bool SerialKey::isExpired(unsigned long long currentTime) const { - return true; + bool result = false; + + if (m_valid) { + if (currentTime > m_expireTime) { + result = true; + } + } + + return result; } bool SerialKey::isTrial() const { - return true; + return m_trial; } int SerialKey::edition() const { - return 1; + return m_edition; +} + +unsigned long long +SerialKey::dayLeft(unsigned long long currentTime) const +{ + unsigned long long timeLeft = 0; + if (m_expireTime > currentTime) { + timeLeft = m_expireTime - currentTime; + } + + unsigned long long day = 60 * 60 * 24; + + return timeLeft / day; +} + +std::string +SerialKey::decode(const std::string& serial) const +{ + static const char* const lut = "0123456789ABCDEF"; + string output; + size_t len = serial.length(); + if (len & 1) { + return output; + } + + output.reserve(len / 2); + for (size_t i = 0; i < len; i += 2) { + + char a = serial[i]; + char b = serial[i + 1]; + + const char* p = std::lower_bound(lut, lut + 16, a); + const char* q = std::lower_bound(lut, lut + 16, b); + + if (*q != b || *p != a) { + return output; + } + + output.push_back(static_cast(((p - lut) << 4) | (q - lut))); + } + + return output; +} + +void +SerialKey::parse(std::string plainSerial) +{ + string parityStart = plainSerial.substr(0, 1); + string parityEnd = plainSerial.substr(plainSerial.length() - 1, 1); + + // check for parity chars { and }, record parity result, then remove them. + if (parityStart == "{" && parityEnd == "}") { + plainSerial = plainSerial.substr(1, plainSerial.length() - 2); + + // tokenize serialised subscription. + vector parts; + std::string::size_type pos = 0; + bool look = true; + while (look) { + std::string::size_type start = pos; + pos = plainSerial.find(";", pos); + if (pos == string::npos) { + pos = plainSerial.length(); + look = false; + } + parts.push_back(plainSerial.substr(start, pos - start)); + pos += 1; + } + + if ((parts.size() == 8) + && (parts.at(0).find("v1") != string::npos)) { + // e.g.: {v1;basic;Bob;1;email;company name;1398297600;1398384000} + m_edition = getEdition(parts.at(1)); + m_name = parts.at(2); + sscanf(parts.at(3).c_str(), "%d", &m_userLimit); + m_email = parts.at(4); + m_company = parts.at(5); + sscanf(parts.at(6).c_str(), "%lld", &m_warnTime); + sscanf(parts.at(7).c_str(), "%lld", &m_expireTime); + m_valid = true; + } + else if ((parts.size() == 9) + && (parts.at(0).find("v2") != string::npos)) { + // e.g.: {v2;trial;basic;Bob;1;email;company name;1398297600;1398384000} + m_trial = parts.at(1) == "trial" ? true : false; + m_edition = getEdition(parts.at(2)); + m_name = parts.at(3); + sscanf(parts.at(4).c_str(), "%d", &m_userLimit); + m_email = parts.at(5); + m_company = parts.at(6); + sscanf(parts.at(7).c_str(), "%lld", &m_warnTime); + sscanf(parts.at(8).c_str(), "%lld", &m_expireTime); + m_valid = true; + } + } +} + +Edition +SerialKey::getEdition(std::string editionStr) +{ + Edition e = kBasic; + if (editionStr == "pro") { + e = kPro; + } + + return e; } diff --git a/src/lib/shared/SerialKey.h b/src/lib/shared/SerialKey.h index d9101f41..d920ba53 100644 --- a/src/lib/shared/SerialKey.h +++ b/src/lib/shared/SerialKey.h @@ -19,6 +19,15 @@ #include +#ifdef TEST_ENV +#include "gtest/gtest_prod.h" +#endif + +enum Edition{ + kBasic, + kPro +}; + class SerialKey { public: SerialKey(std::string serial); @@ -28,14 +37,32 @@ public: bool isExpired(unsigned long long currentTime) const; bool isTrial() const; int edition() const; - + unsigned long long dayLeft(unsigned long long currentTime) const; + +private: + std::string decode(const std::string& serial) const; + void parse(std::string plainSerial); + Edition getEdition(std::string editionStr); + +#ifdef TEST_ENV +private: + FRIEND_TEST(SerialKeyTests, decode_empty_returnEmptyString); + FRIEND_TEST(SerialKeyTests, decode_invalidDigit_returnEmptyString); + FRIEND_TEST(SerialKeyTests, decode_validSerial_returnPlainText); + FRIEND_TEST(SerialKeyTests, parse_noParty_invalid); + FRIEND_TEST(SerialKeyTests, parse_invalidPartsLenghth_invalid); + FRIEND_TEST(SerialKeyTests, parse_validV1Serial_valid); + FRIEND_TEST(SerialKeyTests, parse_validV2Serial_valid); +#endif + private: std::string m_name; - std::string m_edition; std::string m_email; std::string m_company; int m_userLimit; - int m_warnTime; - int m_expireTime; + unsigned long long m_warnTime; + unsigned long long m_expireTime; + Edition m_edition; bool m_trial; + bool m_valid; }; From 235f528dd98fed31a3149ac5bae304814815fea5 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 13 Oct 2016 17:55:38 +0100 Subject: [PATCH 16/88] #5657 Added unit tests for SerialKey --- src/lib/shared/CMakeLists.txt | 6 + src/test/unittests/CMakeLists.txt | 2 +- src/test/unittests/shared/SerialKeyTests.cpp | 80 ++++++++++++ .../unittests/synergy/SubscriptionTests.cpp | 114 ------------------ 4 files changed, 87 insertions(+), 115 deletions(-) create mode 100644 src/test/unittests/shared/SerialKeyTests.cpp delete mode 100644 src/test/unittests/synergy/SubscriptionTests.cpp diff --git a/src/lib/shared/CMakeLists.txt b/src/lib/shared/CMakeLists.txt index 042d866c..891f4aa7 100644 --- a/src/lib/shared/CMakeLists.txt +++ b/src/lib/shared/CMakeLists.txt @@ -22,5 +22,11 @@ endif() add_library(shared STATIC ${sources}) +include_directories( + ../ + ../../../ext + ../../../ext/gtest-1.6.0/include +) + target_link_libraries(shared arch base) diff --git a/src/test/unittests/CMakeLists.txt b/src/test/unittests/CMakeLists.txt index 4cdab9bf..3e49dc3c 100644 --- a/src/test/unittests/CMakeLists.txt +++ b/src/test/unittests/CMakeLists.txt @@ -68,4 +68,4 @@ endif() add_executable(unittests ${sources}) target_link_libraries(unittests - arch base client server common io net platform server synergy mt ipc gtest gmock ${libs} ${OPENSSL_LIBS}) + arch base client server common io net platform server synergy mt ipc gtest gmock shared ${libs} ${OPENSSL_LIBS}) diff --git a/src/test/unittests/shared/SerialKeyTests.cpp b/src/test/unittests/shared/SerialKeyTests.cpp new file mode 100644 index 00000000..59199435 --- /dev/null +++ b/src/test/unittests/shared/SerialKeyTests.cpp @@ -0,0 +1,80 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2016 Symless Inc. + * + * 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 . + */ + +#define TEST_ENV + +#include "shared/SerialKey.h" + +#include "test/global/gtest.h" + +TEST(SerialKeyTests, decode_empty_returnEmptyString) +{ + SerialKey serial(""); + std::string plainText = serial.decode(""); + EXPECT_EQ(0, plainText.size()); +} + +TEST(SerialKeyTests, decode_invalidDigit_returnEmptyString) +{ + SerialKey serial(""); + std::string plainText = serial.decode("MOCKZ"); + EXPECT_EQ(0, plainText.size()); +} + +TEST(SerialKeyTests, decode_validSerial_returnPlainText) +{ + SerialKey serial(""); + std::string plainText = serial.decode("53796E6572677920726F636B7321"); + EXPECT_EQ("Synergy rocks!", plainText); +} + +TEST(SerialKeyTests, parse_noParty_invalid) +{ + SerialKey serial(""); + serial.parse("MOCK"); + EXPECT_FALSE(serial.isValid(0)); +} + +TEST(SerialKeyTests, parse_invalidPartsLenghth_invalid) +{ + SerialKey serial(""); + serial.parse("{Synergy;Rocks}"); + EXPECT_FALSE(serial.isValid(0)); +} + +TEST(SerialKeyTests, parse_validV1Serial_valid) +{ + SerialKey serial(""); + serial.parse("{v1;basic;Bob;1;email;company name;0;86400}"); + EXPECT_EQ(true, serial.isValid(0)); + EXPECT_EQ(kBasic, serial.edition()); + EXPECT_FALSE(serial.isExpired(0)); + EXPECT_EQ(true, serial.dayLeft(0)); + EXPECT_EQ(true, serial.isExpiring(1)); +} + +TEST(SerialKeyTests, parse_validV2Serial_valid) +{ + SerialKey serial(""); + serial.parse("{v2;trial;pro;Bob;1;email;company name;0;86400}"); + EXPECT_EQ(true, serial.isValid(0)); + EXPECT_EQ(kPro, serial.edition()); + EXPECT_FALSE(serial.isExpired(0)); + EXPECT_EQ(true, serial.dayLeft(0)); + EXPECT_EQ(true, serial.isExpiring(1)); + EXPECT_EQ(true, serial.isTrial()); +} diff --git a/src/test/unittests/synergy/SubscriptionTests.cpp b/src/test/unittests/synergy/SubscriptionTests.cpp deleted file mode 100644 index 2cab31b9..00000000 --- a/src/test/unittests/synergy/SubscriptionTests.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* - * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2015 Synergy Seamless Inc. - * - * 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 "synergy/LicenseManager.h" -//#include "synergy/XSynergy.h" -// -//#include "test/global/gtest.h" -// -//TEST(SubscriptionTests, decode_invalidLength_throwException) -//{ -// LicenseManager LicenseManager; -// String serial("ABC"); -// -// EXPECT_THROW(LicenseManager.decode(serial), XSubscription); -//} -// -//TEST(SubscriptionTests, decode_unrecognizedDigit_throwException) -//{ -// LicenseManager LicenseManager; -// String serial("MOCK"); -// -// EXPECT_THROW(LicenseManager.decode(serial), XSubscription); -//} -// -//TEST(SubscriptionTests, parsePlainSerial_noParity_throwException) -//{ -// LicenseManager LicenseManager; -// String painText("MOCK"); -// SubscriptionKey key; -// -// EXPECT_THROW(LicenseManager.parsePlainSerial(painText, key), XSubscription); -//} -// -//TEST(SubscriptionTests, parsePlainSerial_invalidSerial_throwException) -//{ -// LicenseManager LicenseManager; -// String painText("{MOCK}"); -// SubscriptionKey key; -// -// EXPECT_THROW(LicenseManager.parsePlainSerial(painText, key), XSubscription); -//} -// -//TEST(SubscriptionTests, parsePlainSerial_validSerial_validSubscriptionKey) -//{ -// // valid until 2 March 2049 -// LicenseManager LicenseManager; -// String painText("{v1;trial;Bob;1;a@a.a;mock company;2147483647;2147483647}"); -// SubscriptionKey key; -// LicenseManager.parsePlainSerial(painText, key); -// -// EXPECT_EQ("trial", key.m_type); -// EXPECT_EQ("Bob", key.m_name); -// EXPECT_EQ(1, key.m_userLimit); -// EXPECT_EQ("a@a.a", key.m_email); -// EXPECT_EQ("mock company", key.m_company); -// EXPECT_EQ(2147483647, key.m_warnTime); -// EXPECT_EQ(2147483647, key.m_expireTime); -//} -// -//TEST(SubscriptionTests, parsePlainSerial_validSerialWithoutCompany_validSubscriptionKey) -//{ -// // valid until 2 March 2049 -// LicenseManager LicenseManager; -// String painText("{v1;trial;Bob;1;a@a.a;;2147483647;2147483647}"); -// SubscriptionKey key; -// LicenseManager.parsePlainSerial(painText, key); -// -// EXPECT_EQ("trial", key.m_type); -// EXPECT_EQ("Bob", key.m_name); -// EXPECT_EQ(1, key.m_userLimit); -// EXPECT_EQ("a@a.a", key.m_email); -// EXPECT_EQ("", key.m_company); -// EXPECT_EQ(2147483647, key.m_warnTime); -// EXPECT_EQ(2147483647, key.m_expireTime); -//} -// -//TEST(SubscriptionTests, parsePlainSerial_expiredTrialSerial_throwException) -//{ -// LicenseManager LicenseManager; -// String painText("{v1;trial;Bob;1;1398297600;1398384000}"); -// SubscriptionKey key; -// -// EXPECT_THROW(LicenseManager.parsePlainSerial(painText, key), XSubscription); -//} -// -//TEST(SubscriptionTests, parsePlainSerial_expiredBasicSerial_validSubscriptionKey) -//{ -// LicenseManager LicenseManager; -// String painText("{v1;basic;Bob;1;a@a.a;mock company;1398297600;1398384000}"); -// SubscriptionKey key; -// LicenseManager.parsePlainSerial(painText, key); -// -// EXPECT_EQ("basic", key.m_type); -// EXPECT_EQ("Bob", key.m_name); -// EXPECT_EQ(1, key.m_userLimit); -// EXPECT_EQ("a@a.a", key.m_email); -// EXPECT_EQ("mock company", key.m_company); -// EXPECT_EQ(1398297600, key.m_warnTime); -// EXPECT_EQ(1398384000, key.m_expireTime); -//} From 719e64dc8f5ab82bb424333b0a38e14fc585a2a2 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 13 Oct 2016 18:07:21 +0100 Subject: [PATCH 17/88] #5657 Added missing dependencies on Linux and Windows --- src/lib/shared/SerialKey.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index 5f50045e..54a45f86 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -17,6 +17,9 @@ #include "SerialKey.h" +#include +#include +#include #include using namespace std; From fd8e778b2a86d692248b9fa03f99d9f846836ff7 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Thu, 13 Oct 2016 14:49:53 +0100 Subject: [PATCH 18/88] #5657 Fix serial key file path in GUI --- src/gui/src/CoreInterface.cpp | 14 +++++++++++--- src/gui/src/CoreInterface.h | 2 +- src/gui/src/SubscriptionManager.cpp | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/gui/src/CoreInterface.cpp b/src/gui/src/CoreInterface.cpp index d537a24f..b06a5d90 100644 --- a/src/gui/src/CoreInterface.cpp +++ b/src/gui/src/CoreInterface.cpp @@ -22,10 +22,18 @@ #include #include +#include +#include #include static const char kCoreBinary[] = "syntool"; +#ifdef Q_WS_WIN +static const char kSerialKeyFilename[] = "Synergy.subkey"; +#else +static const char kSerialKeyFilename[] = ".synergy.subkey"; +#endif + CoreInterface::CoreInterface() { } @@ -54,10 +62,10 @@ QString CoreInterface::getArch() return run(args); } -QString CoreInterface::getSubscriptionFilename() +QString CoreInterface::getSerialKeyFilePath() { - QStringList args("--get-subscription-filename"); - return run(args); + QString filename = getProfileDir() + QDir::separator() + kSerialKeyFilename; + return filename; } QString CoreInterface::activateSerial(const QString& serial) diff --git a/src/gui/src/CoreInterface.h b/src/gui/src/CoreInterface.h index 13e8fd87..cd61ae25 100644 --- a/src/gui/src/CoreInterface.h +++ b/src/gui/src/CoreInterface.h @@ -28,7 +28,7 @@ public: QString getProfileDir(); QString getInstalledDir(); QString getArch(); - QString getSubscriptionFilename(); + QString getSerialKeyFilePath(); QString activateSerial(const QString& serial); QString checkSubscription(); QString notifyActivation(const QString& identity); diff --git a/src/gui/src/SubscriptionManager.cpp b/src/gui/src/SubscriptionManager.cpp index 77e0a91c..cc842be3 100644 --- a/src/gui/src/SubscriptionManager.cpp +++ b/src/gui/src/SubscriptionManager.cpp @@ -85,9 +85,9 @@ bool SubscriptionManager::checkSubscription() bool SubscriptionManager::fileExists() { CoreInterface coreInterface; - QString subscriptionFilename = coreInterface.getSubscriptionFilename(); + QString serialKeyFilePath = coreInterface.getSerialKeyFilePath(); - return QFile::exists(subscriptionFilename); + return QFile::exists(serialKeyFilePath); } void SubscriptionManager::checkError(QString& error) From 743e96f2778f31247771ac07dbf3e4634ff7f40a Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Thu, 13 Oct 2016 15:13:12 +0100 Subject: [PATCH 19/88] #5657 Don't store activation email --- src/gui/gui.pro | 6 ++++-- src/gui/src/ActivationDialog.cpp | 2 +- src/gui/src/AppConfig.cpp | 8 -------- src/gui/src/AppConfig.h | 2 -- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/gui/gui.pro b/src/gui/gui.pro index c0bf4fad..836e5ec5 100644 --- a/src/gui/gui.pro +++ b/src/gui/gui.pro @@ -64,7 +64,8 @@ SOURCES += src/main.cpp \ src/ActivationNotifier.cpp \ src/ActivationDialog.cpp \ src/CancelActivationDialog.cpp \ - src/FailedLoginDialog.cpp + src/FailedLoginDialog.cpp \ + ../lib/shared/SerialKey.cpp HEADERS += src/MainWindow.h \ src/AboutDialog.h \ src/ServerConfig.h \ @@ -112,7 +113,8 @@ HEADERS += src/MainWindow.h \ src/ElevateMode.h \ src/ActivationDialog.h \ src/CancelActivationDialog.h \ - src/FailedLoginDialog.h + src/FailedLoginDialog.h \ + ../lib/shared/SerialKey.h RESOURCES += res/Synergy.qrc RC_FILE = res/win/Synergy.rc macx { diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 9f2e3d51..fc671501 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -77,7 +77,7 @@ void ActivationDialog::accept() if (!subscriptionManager.activateSerial(serialKey)) { return; } - m_appConfig->setActivateEmail(""); + notifyActivation("serial:" + m_appConfig->serialKey()); } diff --git a/src/gui/src/AppConfig.cpp b/src/gui/src/AppConfig.cpp index 9848e6b4..059cca59 100644 --- a/src/gui/src/AppConfig.cpp +++ b/src/gui/src/AppConfig.cpp @@ -184,7 +184,6 @@ void AppConfig::saveSettings() settings().setValue("elevateModeEnum", static_cast(m_ElevateMode)); settings().setValue("autoConfigPrompted", m_AutoConfigPrompted); settings().setValue("edition", m_Edition); - settings().setValue("activateEmail", m_ActivateEmail); settings().setValue("cryptoEnabled", m_CryptoEnabled); settings().setValue("autoHide", m_AutoHide); settings().setValue("serialKey", m_Serialkey); @@ -245,13 +244,6 @@ void AppConfig::setEdition(int e) { int AppConfig::edition() const { return m_Edition; } -bool AppConfig::setActivateEmail(QString e) { - m_ActivateEmail = e; - return true; -} - -QString AppConfig::activateEmail() { return m_ActivateEmail; } - bool AppConfig::setSerialKey(QString serial, QString& errorOut) { if (serial.isEmpty()) { errorOut = "Your serial key cannot be blank."; diff --git a/src/gui/src/AppConfig.h b/src/gui/src/AppConfig.h index 271ef683..9ebf2c99 100644 --- a/src/gui/src/AppConfig.h +++ b/src/gui/src/AppConfig.h @@ -79,8 +79,6 @@ class AppConfig: public QObject void setAutoConfigPrompted(bool prompted); void setEdition(int e); int edition() const; - bool setActivateEmail(QString e); - QString activateEmail(); bool setSerialKey(QString serial, QString& error); void clearSerialKey(); QString serialKey(); From 540882056f770e3dcc2e9d4c6489afadbdbd86da Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Fri, 14 Oct 2016 11:28:37 +0100 Subject: [PATCH 20/88] #5657 Create a global SubscriptionManager instance --- src/gui/gui.pro | 5 +- src/gui/src/ActivationDialog.cpp | 19 +-- src/gui/src/ActivationDialog.h | 5 +- src/gui/src/AppConfig.cpp | 11 +- src/gui/src/AppConfig.h | 2 +- src/gui/src/MainWindow.cpp | 58 ++++----- src/gui/src/MainWindow.h | 10 +- src/gui/src/QUtility.cpp | 3 - src/gui/src/SubscriptionManager.cpp | 150 ++-------------------- src/gui/src/SubscriptionManager.h | 32 ++--- src/gui/src/main.cpp | 6 +- src/{gui/src => lib/shared}/EditionType.h | 4 +- src/lib/shared/SerialKey.cpp | 25 ++-- src/lib/shared/SerialKey.h | 35 +++-- 14 files changed, 117 insertions(+), 248 deletions(-) rename src/{gui/src => lib/shared}/EditionType.h (93%) diff --git a/src/gui/gui.pro b/src/gui/gui.pro index 836e5ec5..e56926bc 100644 --- a/src/gui/gui.pro +++ b/src/gui/gui.pro @@ -7,7 +7,8 @@ DEFINES += VERSION_REVISION=\\\"$$QMAKE_VERSION_REVISION\\\" DEPENDPATH += . \ res INCLUDEPATH += . \ - src + src \ + ../lib/shared/ FORMS += res/MainWindowBase.ui \ res/AboutDialogBase.ui \ res/ServerConfigDialogBase.ui \ @@ -102,7 +103,6 @@ HEADERS += src/MainWindow.h \ src/DataDownloader.h \ src/AddClientDialog.h \ src/CommandProcess.h \ - src/EditionType.h \ src/ProcessorArch.h \ src/CoreInterface.h \ src/Fingerprint.h \ @@ -114,6 +114,7 @@ HEADERS += src/MainWindow.h \ src/ActivationDialog.h \ src/CancelActivationDialog.h \ src/FailedLoginDialog.h \ + ../lib/shared/EditionType.h \ ../lib/shared/SerialKey.h RESOURCES += res/Synergy.qrc RC_FILE = res/win/Synergy.rc diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index fc671501..42489726 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -14,10 +14,12 @@ #include #include -ActivationDialog::ActivationDialog(QWidget* parent, AppConfig& appConfig) : +ActivationDialog::ActivationDialog(QWidget* parent, AppConfig& appConfig, + SubscriptionManager& subscriptionManager) : QDialog(parent), ui(new Ui::ActivationDialog), - m_appConfig(&appConfig) + m_appConfig(&appConfig), + m_subscriptionManager (&subscriptionManager) { ui->setupUi(this); ui->m_pTextEditSerialKey->setFocus(); @@ -67,17 +69,8 @@ void ActivationDialog::accept() try { QString serialKey = ui->m_pTextEditSerialKey->toPlainText(); - - if (!m_appConfig->setSerialKey(serialKey, error)) { - message.critical(this, "Invalid Serial Key", tr("%1").arg(error)); - return; - } - - SubscriptionManager subscriptionManager(this, *m_appConfig, edition); - if (!subscriptionManager.activateSerial(serialKey)) { - return; - } - + SubscriptionManager subscriptionManager (m_appConfig); + subscriptionManager.setSerialKey (serialKey); notifyActivation("serial:" + m_appConfig->serialKey()); } diff --git a/src/gui/src/ActivationDialog.h b/src/gui/src/ActivationDialog.h index 0f3328ac..a1ece9a3 100644 --- a/src/gui/src/ActivationDialog.h +++ b/src/gui/src/ActivationDialog.h @@ -2,6 +2,7 @@ #define ACTIVATIONDIALOG_H #include +#include namespace Ui { class ActivationDialog; @@ -14,7 +15,8 @@ class ActivationDialog : public QDialog Q_OBJECT public: - explicit ActivationDialog(QWidget *parent, AppConfig& appConfig); + ActivationDialog(QWidget *parent, AppConfig& appConfig, + SubscriptionManager& subscriptionManager); ~ActivationDialog(); public slots: @@ -27,6 +29,7 @@ protected: private: Ui::ActivationDialog *ui; AppConfig* m_appConfig; + SubscriptionManager* m_subscriptionManager; }; #endif // ACTIVATIONDIALOG_H diff --git a/src/gui/src/AppConfig.cpp b/src/gui/src/AppConfig.cpp index 059cca59..22aec72c 100644 --- a/src/gui/src/AppConfig.cpp +++ b/src/gui/src/AppConfig.cpp @@ -244,13 +244,10 @@ void AppConfig::setEdition(int e) { int AppConfig::edition() const { return m_Edition; } -bool AppConfig::setSerialKey(QString serial, QString& errorOut) { - if (serial.isEmpty()) { - errorOut = "Your serial key cannot be blank."; - return false; - } - m_Serialkey = serial; - return true; +QString AppConfig::setSerialKey(QString serial) { + using std::swap; + swap (serial, m_Serialkey); + return serial; } void AppConfig::clearSerialKey() diff --git a/src/gui/src/AppConfig.h b/src/gui/src/AppConfig.h index 9ebf2c99..f62af1d2 100644 --- a/src/gui/src/AppConfig.h +++ b/src/gui/src/AppConfig.h @@ -79,7 +79,7 @@ class AppConfig: public QObject void setAutoConfigPrompted(bool prompted); void setEdition(int e); int edition() const; - bool setSerialKey(QString serial, QString& error); + QString setSerialKey(QString serial); void clearSerialKey(); QString serialKey(); int lastExpiringWarningTime() const; diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index c927839f..8165a43e 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -76,12 +76,14 @@ static const char* synergyIconFiles[] = ":/res/icons/16x16/synergy-transfering.png" }; -MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig) : +MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig, + SubscriptionManager& subscriptionManager) : m_Settings(settings), - m_AppConfig(appConfig), + m_AppConfig(&appConfig), + m_SubscriptionManager(&subscriptionManager), m_pSynergy(NULL), m_SynergyState(synergyDisconnected), - m_ServerConfig(&m_Settings, 5, 3, m_AppConfig.screenName(), this), + m_ServerConfig(&m_Settings, 5, 3, m_AppConfig->screenName(), this), m_pTempConfigFile(NULL), m_pTrayIcon(NULL), m_pTrayIconMenu(NULL), @@ -135,12 +137,12 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig) : m_pComboServerList->hide(); - setEdition(m_AppConfig.edition()); + setEdition(m_AppConfig->edition()); m_pLabelPadlock->hide(); connect (this, SIGNAL(windowShown()), this, SLOT(on_windowShown()), Qt::QueuedConnection); - connect (&m_AppConfig, SIGNAL(editionSet(int)), this, SLOT(setEdition(int)), Qt::QueuedConnection); - connect (&m_AppConfig, SIGNAL(sslToggled(bool)), this, SLOT(sslToggled(bool)), Qt::QueuedConnection); + connect (m_AppConfig, SIGNAL(editionSet(int)), this, SLOT(setEdition(int)), Qt::QueuedConnection); + connect (m_AppConfig, SIGNAL(sslToggled(bool)), this, SLOT(sslToggled(bool)), Qt::QueuedConnection); } MainWindow::~MainWindow() @@ -498,7 +500,7 @@ void MainWindow::restartSynergy() void MainWindow::proofreadInfo() { - setEdition(m_AppConfig.edition()); // Why is this here? + setEdition(m_AppConfig->edition()); // Why is this here? int oldState = m_SynergyState; m_SynergyState = synergyDisconnected; @@ -566,7 +568,7 @@ void MainWindow::startSynergy() #endif - if (m_AppConfig.getCryptoEnabled()) { + if (m_AppConfig->getCryptoEnabled()) { args << "--enable-crypto"; } @@ -734,18 +736,6 @@ QString MainWindow::appPath(const QString& name) bool MainWindow::serverArgs(QStringList& args, QString& app) { - int edition; - SubscriptionManager subscriptionManager(this, appConfig(), edition); - if (subscriptionManager.fileExists()) - { - if (!subscriptionManager.checkSubscription()) { - return false; - } - else { - setEdition(edition); - } - } - app = appPath(appConfig().synergysName()); if (!QFile::exists(app)) @@ -894,7 +884,7 @@ void MainWindow::setSynergyState(qSynergyState state) switch (state) { case synergyConnected: { - if (m_AppConfig.getCryptoEnabled()) { + if (m_AppConfig->getCryptoEnabled()) { m_pLabelPadlock->show(); } else { @@ -1009,13 +999,13 @@ void MainWindow::updateZeroconfService() QMutexLocker locker(&m_UpdateZeroconfMutex); if (isBonjourRunning()) { - if (!m_AppConfig.wizardShouldRun()) { + if (!m_AppConfig->wizardShouldRun()) { if (m_pZeroconfService) { delete m_pZeroconfService; m_pZeroconfService = NULL; } - if (m_AppConfig.autoConfig() || synergyType() == synergyServer) { + if (m_AppConfig->autoConfig() || synergyType() == synergyServer) { m_pZeroconfService = new ZeroconfService(this); } } @@ -1037,7 +1027,7 @@ void MainWindow::serverDetected(const QString name) void MainWindow::setEdition(int edition) { setWindowTitle(getEditionName(edition)); - if (m_AppConfig.getCryptoEnabled()) { + if (m_AppConfig->getCryptoEnabled()) { m_pSslCertificate = new SslCertificate(this); m_pSslCertificate->generateCertificate(); } @@ -1047,7 +1037,7 @@ void MainWindow::setEdition(int edition) void MainWindow::updateLocalFingerprint() { - if (m_AppConfig.getCryptoEnabled() && Fingerprint::local().fileExists()) { + if (m_AppConfig->getCryptoEnabled() && Fingerprint::local().fileExists()) { m_pLabelFingerprint->setVisible(true); m_pLabelLocalFingerprint->setVisible(true); m_pLabelLocalFingerprint->setText(Fingerprint::local().readFirst()); @@ -1058,6 +1048,12 @@ void MainWindow::updateLocalFingerprint() } } +SubscriptionManager& +MainWindow::subscriptionManager() const +{ + return *m_SubscriptionManager; +} + void MainWindow::on_m_pGroupClient_toggled(bool on) { m_pGroupServer->setChecked(!on); @@ -1159,7 +1155,7 @@ void MainWindow::on_m_pButtonConfigureServer_clicked() void MainWindow::on_m_pActivate_triggered() { - ActivationDialog activationDialog (this, this->appConfig()); + ActivationDialog activationDialog(this, appConfig(), subscriptionManager()); activationDialog.exec(); } @@ -1320,16 +1316,16 @@ void MainWindow::promptAutoConfig() QMessageBox::Yes | QMessageBox::No); if (r == QMessageBox::Yes) { - m_AppConfig.setAutoConfig(true); + m_AppConfig->setAutoConfig(true); downloadBonjour(); } else { - m_AppConfig.setAutoConfig(false); + m_AppConfig->setAutoConfig(false); m_pCheckBoxAutoConfig->setChecked(false); } } - m_AppConfig.setAutoConfigPrompted(true); + m_AppConfig->setAutoConfigPrompted(true); } void MainWindow::on_m_pComboServerList_currentIndexChanged(QString ) @@ -1377,8 +1373,8 @@ void MainWindow::bonjourInstallFinished() void MainWindow::on_windowShown() { - if (!m_AppConfig.activationHasRun() && (m_AppConfig.edition() == Unregistered)) { - ActivationDialog activationDialog (this, m_AppConfig); + if (!m_AppConfig->activationHasRun() && (m_AppConfig->edition() == Unregistered)) { + ActivationDialog activationDialog (this, appConfig(), subscriptionManager()); activationDialog.exec(); } } diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index efd83dcc..1dd678ba 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -58,6 +58,7 @@ class ZeroconfService; class DataDownloader; class CommandProcess; class SslCertificate; +class SubscriptionManager; class MainWindow : public QMainWindow, public Ui::MainWindowBase { @@ -94,7 +95,8 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase }; public: - MainWindow(QSettings& settings, AppConfig& appConfig); + MainWindow(QSettings& settings, AppConfig& appConfig, + SubscriptionManager& subscriptionManager); ~MainWindow(); public: @@ -116,6 +118,7 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase void updateZeroconfService(); void serverDetected(const QString name); void updateLocalFingerprint(); + SubscriptionManager& subscriptionManager() const; public slots: void setEdition(int edition); @@ -145,7 +148,7 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase protected: QSettings& settings() { return m_Settings; } - AppConfig& appConfig() { return m_AppConfig; } + AppConfig& appConfig() { return *m_AppConfig; } QProcess* synergyProcess() { return m_pSynergy; } void setSynergyProcess(QProcess* p) { m_pSynergy = p; } void initConnections(); @@ -188,7 +191,8 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase private: QSettings& m_Settings; - AppConfig& m_AppConfig; + AppConfig* m_AppConfig; + SubscriptionManager* m_SubscriptionManager; QProcess* m_pSynergy; int m_SynergyState; ServerConfig m_ServerConfig; diff --git a/src/gui/src/QUtility.cpp b/src/gui/src/QUtility.cpp index f463f1d2..322b2fd1 100644 --- a/src/gui/src/QUtility.cpp +++ b/src/gui/src/QUtility.cpp @@ -51,9 +51,6 @@ getEditionName (int edition) { else if (edition == Pro) { return "Synergy Pro"; } - else if (edition == Trial) { - return "Synergy Trial"; - } else { return "Synergy (UNREGISTERED)"; } diff --git a/src/gui/src/SubscriptionManager.cpp b/src/gui/src/SubscriptionManager.cpp index cc842be3..0f3728af 100644 --- a/src/gui/src/SubscriptionManager.cpp +++ b/src/gui/src/SubscriptionManager.cpp @@ -16,152 +16,20 @@ */ #include "SubscriptionManager.h" - - -#include "CoreInterface.h" #include "EditionType.h" #include "AppConfig.h" +#include -#include -#include -#include -#include -#include - -static const char purchaseURL[] = "https://symless.com/account/"; - -SubscriptionManager::SubscriptionManager(QWidget* parent, AppConfig& appConfig, int& edition) : - m_pParent(parent), - m_AppConfig(appConfig), - m_Edition(edition) -{ +SubscriptionManager::SubscriptionManager(AppConfig* appConfig) : + m_AppConfig(appConfig) { } -bool SubscriptionManager::activateSerial(const QString& serial) +void +SubscriptionManager::setSerialKey(QString serialKeyString) { - m_Edition = Unregistered; - persistDirectory(); - CoreInterface coreInterface; - QString output; - - try - { - output = coreInterface.activateSerial(serial); - } - catch (std::exception& e) - { - m_ErrorMessage = e.what(); - checkError(m_ErrorMessage); - return false; - } - - checkOutput(output); - - return true; -} - -bool SubscriptionManager::checkSubscription() -{ - m_Edition = Unregistered; - persistDirectory(); - CoreInterface coreInterface; - QString output; - try - { - output = coreInterface.checkSubscription(); - } - catch (std::exception& e) - { - m_ErrorMessage = e.what(); - checkError(m_ErrorMessage); - return false; - } - - checkOutput(output); - - return true; -} - -bool SubscriptionManager::fileExists() -{ - CoreInterface coreInterface; - QString serialKeyFilePath = coreInterface.getSerialKeyFilePath(); - - return QFile::exists(serialKeyFilePath); -} - -void SubscriptionManager::checkError(QString& error) -{ - if (error.contains("trial has expired")) { - QMessageBox::warning(m_pParent, tr("Subscription warning"), - tr("Your trial has expired. Click here to purchase").arg(purchaseURL)); - } - else { - QMessageBox::warning(m_pParent, tr("Subscription error"), - tr("An error occurred while trying to activate Synergy using your serial key. " - "Please contact the helpdesk, and provide the " - "following details.\n\n%1").arg(error)); - } -} - -void SubscriptionManager::checkOutput(QString& output) -{ - getEditionType(output); - checkExpiring(output); -} - -void SubscriptionManager::getEditionType(QString& output) -{ - if (output.contains("pro subscription valid")) { - m_Edition = Pro; - } - else if (output.contains("basic subscription valid")) { - m_Edition = Basic; - } - else if (output.contains("trial subscription valid")) { - m_Edition = Trial; - } -} - -void SubscriptionManager::checkExpiring(QString& output) -{ - if (output.contains("trial will end in") && shouldWarnExpiring()) { - QRegExp dayLeftRegex(".*trial will end in ([0-9]+) day.*"); - if (dayLeftRegex.exactMatch(output)) { - QString dayLeft = dayLeftRegex.cap(1); - - QMessageBox::warning(m_pParent, tr("Subscription warning"), - tr("Your trial will end in %1 %2. Click here to purchase") - .arg(dayLeft) - .arg(dayLeft == "1" ? "day" : "days") - .arg(purchaseURL)); - } - } -} - -bool SubscriptionManager::shouldWarnExpiring() -{ - // warn users about expiring subscription once a day - int lastExpiringWarningTime = m_AppConfig.lastExpiringWarningTime(); - QDateTime currentDateTime = QDateTime::currentDateTime(); - int currentTime = currentDateTime.toTime_t(); - const int secondPerDay = 60 * 60 * 24; - bool result = false; - if ((currentTime - lastExpiringWarningTime) > secondPerDay) { - result = true; - m_AppConfig.setLastExpiringWarningTime(currentTime); - } - - return result; -} - -void SubscriptionManager::persistDirectory() -{ - CoreInterface coreInterface; - QString profileDir = coreInterface.getProfileDir(); - - QDir dir(profileDir); - if (!dir.exists()) { - dir.mkpath("."); + SerialKey serialKey (serialKeyString.toStdString()); + if (serialKey.isValid (::time(0)) && (serialKey != m_serialKey)) { + m_AppConfig->setSerialKey (serialKeyString); + emit serialKeyChanged (serialKey); } } diff --git a/src/gui/src/SubscriptionManager.h b/src/gui/src/SubscriptionManager.h index 59497352..29b9cd95 100644 --- a/src/gui/src/SubscriptionManager.h +++ b/src/gui/src/SubscriptionManager.h @@ -17,31 +17,23 @@ #pragma once -#include +#include +#include class AppConfig; -class SubscriptionManager : public QWidget +class SubscriptionManager: public QObject { + Q_OBJECT + public: - SubscriptionManager(QWidget* parent, AppConfig& appConfig, int& edition); - - bool activateSerial(const QString& serial); - bool checkSubscription(); - bool fileExists(); - QString getLastError(){ return m_ErrorMessage; } + SubscriptionManager (AppConfig* appConfig); + void setSerialKey (QString serialKey); private: - void checkError(QString& error); - void checkOutput(QString& output); - void getEditionType(QString& output); - void checkExpiring(QString& output); - bool shouldWarnExpiring(); - void persistDirectory(); - -private: - QString m_ErrorMessage; - QWidget* m_pParent; - AppConfig& m_AppConfig; - int& m_Edition; + AppConfig* m_AppConfig; + SerialKey m_serialKey; + +signals: + void serialKeyChanged (SerialKey); }; diff --git a/src/gui/src/main.cpp b/src/gui/src/main.cpp index 0ea2cbfe..79131f36 100644 --- a/src/gui/src/main.cpp +++ b/src/gui/src/main.cpp @@ -20,6 +20,7 @@ #define TRAY_RETRY_WAIT 2000 #include "QSynergyApplication.h" +#include "SubscriptionManager.h" #include "MainWindow.h" #include "AppConfig.h" #include "SetupWizard.h" @@ -82,11 +83,12 @@ int main(int argc, char* argv[]) #endif QSettings settings; - AppConfig appConfig(&settings); + AppConfig appConfig (&settings); + SubscriptionManager subscriptionManager (&appConfig); app.switchTranslator(appConfig.language()); - MainWindow mainWindow(settings, appConfig); + MainWindow mainWindow(settings, appConfig, subscriptionManager); SetupWizard setupWizard(mainWindow, true); if (appConfig.wizardShouldRun()) diff --git a/src/gui/src/EditionType.h b/src/lib/shared/EditionType.h similarity index 93% rename from src/gui/src/EditionType.h rename to src/lib/shared/EditionType.h index 5869a32b..ddd27ccf 100644 --- a/src/gui/src/EditionType.h +++ b/src/lib/shared/EditionType.h @@ -20,10 +20,10 @@ /* Do not reorder these! */ -enum EditionType { +enum Edition { Basic, Pro, - Trial, + Trial_DO_NOT_USE_OR_THERE_WILL_BE_PAIN, Unregistered }; diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index 54a45f86..52dd2743 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -24,11 +24,18 @@ using namespace std; +SerialKey::SerialKey(): + m_warnTime(1), + m_expireTime(1), + m_trial(true) +{ +} + SerialKey::SerialKey(std::string serial) : m_userLimit(1), m_warnTime(0), m_expireTime(0), - m_edition(kBasic), + m_edition(Edition::Basic), m_trial(true), m_valid(false) { @@ -39,7 +46,7 @@ SerialKey::SerialKey(std::string serial) : } bool -SerialKey::isValid(unsigned long long currentTime) const +SerialKey::isValid(time_t currentTime) const { bool result = false; @@ -58,7 +65,7 @@ SerialKey::isValid(unsigned long long currentTime) const } bool -SerialKey::isExpiring(unsigned long long currentTime) const +SerialKey::isExpiring(time_t currentTime) const { bool result = false; @@ -72,7 +79,7 @@ SerialKey::isExpiring(unsigned long long currentTime) const } bool -SerialKey::isExpired(unsigned long long currentTime) const +SerialKey::isExpired(time_t currentTime) const { bool result = false; @@ -91,14 +98,14 @@ SerialKey::isTrial() const return m_trial; } -int +Edition SerialKey::edition() const { return m_edition; } -unsigned long long -SerialKey::dayLeft(unsigned long long currentTime) const +time_t +SerialKey::daysLeft(time_t currentTime) const { unsigned long long timeLeft = 0; if (m_expireTime > currentTime) { @@ -195,9 +202,9 @@ SerialKey::parse(std::string plainSerial) Edition SerialKey::getEdition(std::string editionStr) { - Edition e = kBasic; + Edition e = Edition::Basic; if (editionStr == "pro") { - e = kPro; + e = Edition::Pro; } return e; diff --git a/src/lib/shared/SerialKey.h b/src/lib/shared/SerialKey.h index d920ba53..df8df479 100644 --- a/src/lib/shared/SerialKey.h +++ b/src/lib/shared/SerialKey.h @@ -18,31 +18,29 @@ #pragma once #include +#include +#include "EditionType.h" #ifdef TEST_ENV #include "gtest/gtest_prod.h" #endif -enum Edition{ - kBasic, - kPro -}; - class SerialKey { public: + SerialKey(); SerialKey(std::string serial); - bool isValid(unsigned long long currentTime) const; - bool isExpiring(unsigned long long currentTime) const; - bool isExpired(unsigned long long currentTime) const; + bool isValid(time_t currentTime) const; + bool isExpiring(time_t currentTime) const; + bool isExpired(time_t currentTime) const; bool isTrial() const; - int edition() const; - unsigned long long dayLeft(unsigned long long currentTime) const; + time_t daysLeft(time_t currentTime) const; + Edition edition() const; private: std::string decode(const std::string& serial) const; void parse(std::string plainSerial); - Edition getEdition(std::string editionStr); + Edition getEdition(std::string editionStr); #ifdef TEST_ENV private: @@ -59,10 +57,21 @@ private: std::string m_name; std::string m_email; std::string m_company; - int m_userLimit; + unsigned m_userLimit; unsigned long long m_warnTime; unsigned long long m_expireTime; - Edition m_edition; + Edition m_edition; bool m_trial; bool m_valid; }; + + +inline bool +operator== (SerialKey const& lhs, SerialKey const& rhs) { + return (lhs.edition() == rhs.edition()); +} + +inline bool +operator!= (SerialKey const& lhs, SerialKey const& rhs) { + return !(lhs == rhs); +} From 727fc5c220415127e5e25b3a20c78093224d6e3e Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Fri, 14 Oct 2016 12:16:23 +0100 Subject: [PATCH 21/88] #5659 Move activation notification to SubscriptionManager --- src/gui/src/ActivationDialog.cpp | 23 ++--------------------- src/gui/src/ActivationDialog.h | 3 --- src/gui/src/SubscriptionManager.cpp | 23 +++++++++++++++++++++++ src/gui/src/SubscriptionManager.h | 6 ++++++ src/lib/shared/SerialKey.h | 6 +++--- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 42489726..f070007c 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -31,27 +31,11 @@ ActivationDialog::~ActivationDialog() delete ui; } -void ActivationDialog::notifyActivation(QString identity) -{ - ActivationNotifier* notifier = new ActivationNotifier(); - notifier->setIdentity(identity); - - QThread* thread = new QThread(); - connect(notifier, SIGNAL(finished()), thread, SLOT(quit())); - connect(notifier, SIGNAL(finished()), notifier, SLOT(deleteLater())); - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - - notifier->moveToThread(thread); - thread->start(); - - QMetaObject::invokeMethod(notifier, "notify", Qt::QueuedConnection); -} - void ActivationDialog::reject() { CancelActivationDialog cancelActivationDialog(this); if (QDialog::Accepted == cancelActivationDialog.exec()) { - notifyActivation("skip:unknown"); + m_subscriptionManager->notifySkip(); m_appConfig->activationHasRun(true); m_appConfig->saveSettings(); QDialog::reject(); @@ -69,10 +53,7 @@ void ActivationDialog::accept() try { QString serialKey = ui->m_pTextEditSerialKey->toPlainText(); - SubscriptionManager subscriptionManager (m_appConfig); - subscriptionManager.setSerialKey (serialKey); - notifyActivation("serial:" + m_appConfig->serialKey()); - + m_subscriptionManager->setSerialKey(serialKey); } catch (std::exception& e) { message.critical(this, "Unknown Error", diff --git a/src/gui/src/ActivationDialog.h b/src/gui/src/ActivationDialog.h index a1ece9a3..09577ed5 100644 --- a/src/gui/src/ActivationDialog.h +++ b/src/gui/src/ActivationDialog.h @@ -22,9 +22,6 @@ public: public slots: void reject(); void accept(); - -protected: - void notifyActivation (QString identity); private: Ui::ActivationDialog *ui; diff --git a/src/gui/src/SubscriptionManager.cpp b/src/gui/src/SubscriptionManager.cpp index 0f3728af..fd2a77bb 100644 --- a/src/gui/src/SubscriptionManager.cpp +++ b/src/gui/src/SubscriptionManager.cpp @@ -19,6 +19,7 @@ #include "EditionType.h" #include "AppConfig.h" #include +#include SubscriptionManager::SubscriptionManager(AppConfig* appConfig) : m_AppConfig(appConfig) { @@ -30,6 +31,28 @@ SubscriptionManager::setSerialKey(QString serialKeyString) SerialKey serialKey (serialKeyString.toStdString()); if (serialKey.isValid (::time(0)) && (serialKey != m_serialKey)) { m_AppConfig->setSerialKey (serialKeyString); + notifyActivation ("serial:" + serialKeyString); emit serialKeyChanged (serialKey); } } + +void SubscriptionManager::notifySkip() +{ + notifyActivation ("skip:unknown"); +} + +void SubscriptionManager::notifyActivation(QString identity) +{ + ActivationNotifier* notifier = new ActivationNotifier(); + notifier->setIdentity(identity); + + QThread* thread = new QThread(); + connect(notifier, SIGNAL(finished()), thread, SLOT(quit())); + connect(notifier, SIGNAL(finished()), notifier, SLOT(deleteLater())); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + + notifier->moveToThread(thread); + thread->start(); + + QMetaObject::invokeMethod(notifier, "notify", Qt::QueuedConnection); +} diff --git a/src/gui/src/SubscriptionManager.h b/src/gui/src/SubscriptionManager.h index 29b9cd95..2c79f6bd 100644 --- a/src/gui/src/SubscriptionManager.h +++ b/src/gui/src/SubscriptionManager.h @@ -19,6 +19,7 @@ #include #include +#include class AppConfig; @@ -29,11 +30,16 @@ class SubscriptionManager: public QObject public: SubscriptionManager (AppConfig* appConfig); void setSerialKey (QString serialKey); + void notifySkip (); +private: + void notifyActivation (QString identity); + private: AppConfig* m_AppConfig; SerialKey m_serialKey; signals: void serialKeyChanged (SerialKey); + void editionChanged (Edition); }; diff --git a/src/lib/shared/SerialKey.h b/src/lib/shared/SerialKey.h index df8df479..2423cf55 100644 --- a/src/lib/shared/SerialKey.h +++ b/src/lib/shared/SerialKey.h @@ -35,12 +35,12 @@ public: bool isExpired(time_t currentTime) const; bool isTrial() const; time_t daysLeft(time_t currentTime) const; - Edition edition() const; + Edition edition() const; private: std::string decode(const std::string& serial) const; void parse(std::string plainSerial); - Edition getEdition(std::string editionStr); + Edition getEdition(std::string editionStr); #ifdef TEST_ENV private: @@ -60,7 +60,7 @@ private: unsigned m_userLimit; unsigned long long m_warnTime; unsigned long long m_expireTime; - Edition m_edition; + Edition m_edition; bool m_trial; bool m_valid; }; From 33ebe61ef21d13fea7a6c6c9858ad2303d595540 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Fri, 14 Oct 2016 12:43:01 +0100 Subject: [PATCH 22/88] #5657 Counted expiring within 1 day as 1 day left --- src/lib/shared/SerialKey.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index 54a45f86..38ca8725 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -101,13 +101,16 @@ unsigned long long SerialKey::dayLeft(unsigned long long currentTime) const { unsigned long long timeLeft = 0; + unsigned long long day = 60 * 60 * 24; + if (m_expireTime > currentTime) { timeLeft = m_expireTime - currentTime; } - unsigned long long day = 60 * 60 * 24; + unsigned long long dayLeft = 0; + dayLeft = timeLeft % day != 0 ? 1 : 0; - return timeLeft / day; + return timeLeft / day + dayLeft; } std::string From dce4b382e676ff5df2810d57b79bb968f3f67e32 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Fri, 14 Oct 2016 12:43:33 +0100 Subject: [PATCH 23/88] #5657 Fixed passing in the raw serial key --- src/lib/shared/SerialKey.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index 38ca8725..23d92b2e 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -34,7 +34,7 @@ SerialKey::SerialKey(std::string serial) : { string plainText = decode(serial); if (!plainText.empty()) { - parse(serial); + parse(plainText); } } From 1e5dfd3cb5f267874520289591a36b068846e0e3 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Fri, 14 Oct 2016 12:43:58 +0100 Subject: [PATCH 24/88] #5657 Added more unit tests for member functions in SerialKey --- src/test/unittests/shared/SerialKeyTests.cpp | 121 +++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/src/test/unittests/shared/SerialKeyTests.cpp b/src/test/unittests/shared/SerialKeyTests.cpp index 59199435..40fb479a 100644 --- a/src/test/unittests/shared/SerialKeyTests.cpp +++ b/src/test/unittests/shared/SerialKeyTests.cpp @@ -78,3 +78,124 @@ TEST(SerialKeyTests, parse_validV2Serial_valid) EXPECT_EQ(true, serial.isExpiring(1)); EXPECT_EQ(true, serial.isTrial()); } + +TEST(SerialKeyTests, isValid_validV1BasicSerial_valid) +{ + // {v1;basic;Bob;1;email;company name;0;86400} + SerialKey serial("7B76313B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(true, serial.isValid(0)); + EXPECT_EQ(kBasic, serial.edition()); +} + +TEST(SerialKeyTests, isValid_expiredV1ProSerial_valid) +{ + // {v1;pro;Bob;1;email;company name;0;86400} + SerialKey serial("7B76313B70726F3B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(true, serial.isValid(0)); + EXPECT_EQ(kPro, serial.edition()); +} + +TEST(SerialKeyTests, isValid_validV2LifetimeBasicSerial_valid) +{ + // {v2;lifetime;basic;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B6C69666574696D653B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(true, serial.isValid(0)); + EXPECT_EQ(kBasic, serial.edition()); +} + +TEST(SerialKeyTests, isValid_validV2LifetimeProSerial_valid) +{ + // {v2;lifetime;pro;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B6C69666574696D653B70726F3B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(true, serial.isValid(0)); + EXPECT_EQ(kPro, serial.edition()); +} + +TEST(SerialKeyTests, isValid_validV2TrialBasicSerial_valid) +{ + // {v2;trial;basic;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(true, serial.isTrial()); + EXPECT_EQ(true, serial.isValid(0)); + EXPECT_EQ(kBasic, serial.edition()); + +} + +TEST(SerialKeyTests, isValid_expiredV2TrialProSerial_invalid) +{ + // {v2;trial;pro;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B747269616C3B70726F3B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(true, serial.isTrial()); + EXPECT_FALSE(serial.isValid(86401)); + EXPECT_EQ(kPro, serial.edition()); +} + +TEST(SerialKeyTests, isExpiring_validV2TrialBasicSerial_returnFalse) +{ + // {v2;trial;basic;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(true, serial.isTrial()); + EXPECT_FALSE(serial.isExpiring(0)); + EXPECT_EQ(kBasic, serial.edition()); +} + +TEST(SerialKeyTests, isExpiring_expiringV2TrialBasicSerial_returnTrue) +{ + // {v2;trial;basic;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(true, serial.isTrial()); + EXPECT_EQ(true, serial.isExpiring(1)); +} + +TEST(SerialKeyTests, isExpiring_expiredV2TrialBasicSerial_returnFalse) +{ + // {v2;trial;basic;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(true, serial.isTrial()); + EXPECT_FALSE(serial.isExpiring(86401)); +} + +TEST(SerialKeyTests, isExpired_validV2TrialBasicSerial_returnFalse) +{ + // {v2;trial;basic;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(true, serial.isTrial()); + EXPECT_FALSE(serial.isExpired(0)); +} + +TEST(SerialKeyTests, isExpired_expiringV2TrialBasicSerial_returnFalse) +{ + // {v2;trial;basic;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(true, serial.isTrial()); + EXPECT_FALSE(serial.isExpired(1)); +} + +TEST(SerialKeyTests, isExpired_expiredV2TrialBasicSerial_returnTrue) +{ + // {v2;trial;basic;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(true, serial.isTrial()); + EXPECT_EQ(true, serial.isExpired(86401)); +} + +TEST(SerialKeyTests, dayLeft_validExactlyOneDayV2TrialBasicSerial_returnOne) +{ + // {v2;trial;basic;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(1, serial.dayLeft(0)); +} + +TEST(SerialKeyTests, dayLeft_validWithinOneDayV2TrialBasicSerial_returnOne) +{ + // {v2;trial;basic;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(1, serial.dayLeft(1)); +} + +TEST(SerialKeyTests, dayLeft_expiredV2TrialBasicSerial_returnZero) +{ + // {v2;trial;basic;Bob;1;email;company name;0;86400} + SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + EXPECT_EQ(0, serial.dayLeft(86401)); +} From 92b29276d0ab586c03c94b57733419028c035098 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Fri, 14 Oct 2016 13:51:27 +0100 Subject: [PATCH 25/88] #5657 Added serial argument in core --- src/lib/synergy/ArgParser.cpp | 3 +++ src/lib/synergy/ServerArgs.cpp | 1 + src/lib/synergy/ServerArgs.h | 1 + 3 files changed, 5 insertions(+) diff --git a/src/lib/synergy/ArgParser.cpp b/src/lib/synergy/ArgParser.cpp index 11adc2d5..d16a6fa9 100644 --- a/src/lib/synergy/ArgParser.cpp +++ b/src/lib/synergy/ArgParser.cpp @@ -70,6 +70,9 @@ ArgParser::parseServerArgs(ServerArgs& args, int argc, const char* const* argv) else if (isArg(i, argc, argv, "", "--prm-hc", 1)) { DpiHelper::s_primaryHeightCenter = synergy::string::stringToSizeType(argv[++i]); } + else if (isArg(i, argc, argv, "", "--serial-key", 1)) { + args.m_serial = argv[++i]; + } else { LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, args.m_pname, argv[i], args.m_pname)); return false; diff --git a/src/lib/synergy/ServerArgs.cpp b/src/lib/synergy/ServerArgs.cpp index f56f6e8d..52166f2a 100644 --- a/src/lib/synergy/ServerArgs.cpp +++ b/src/lib/synergy/ServerArgs.cpp @@ -19,6 +19,7 @@ ServerArgs::ServerArgs() : m_configFile(), + m_serial(), m_config(NULL) { } diff --git a/src/lib/synergy/ServerArgs.h b/src/lib/synergy/ServerArgs.h index 54310f8e..7c69fae6 100644 --- a/src/lib/synergy/ServerArgs.h +++ b/src/lib/synergy/ServerArgs.h @@ -28,5 +28,6 @@ public: public: String m_configFile; + String m_serial; Config* m_config; }; From 2b1b0640eac3aa5f9a20b4aa1a98be72cb759937 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Fri, 14 Oct 2016 13:51:46 +0100 Subject: [PATCH 26/88] #5657 Added serial argument parsing unit test --- .../unittests/synergy/ServerArgsParsingTests.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/test/unittests/synergy/ServerArgsParsingTests.cpp b/src/test/unittests/synergy/ServerArgsParsingTests.cpp index 5ae18b94..92db8c0d 100644 --- a/src/test/unittests/synergy/ServerArgsParsingTests.cpp +++ b/src/test/unittests/synergy/ServerArgsParsingTests.cpp @@ -64,3 +64,17 @@ TEST(ServerArgsParsingTests, parseServerArgs_configArg_setConfigFile) EXPECT_EQ("mock_configFile", serverArgs.m_configFile); } + +TEST(ServerArgsParsingTests, parseServerArgs_serialArg_setSerial) +{ + NiceMock argParser; + ON_CALL(argParser, parseGenericArgs(_, _, _)).WillByDefault(Invoke(server_stubParseGenericArgs)); + ON_CALL(argParser, checkUnexpectedArgs()).WillByDefault(Invoke(server_stubCheckUnexpectedArgs)); + ServerArgs serverArgs; + const int argc = 3; + const char* kSerialCmd[argc] = { "stub", "--serial-key", "mock_serial" }; + + argParser.parseServerArgs(serverArgs, argc, kSerialCmd); + + EXPECT_EQ("mock_serial", serverArgs.m_serial); +} From 89851fddc32b181129e0db229c117302890ba9c8 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Fri, 14 Oct 2016 17:14:21 +0100 Subject: [PATCH 27/88] #5657 Exited server if trial is expired --- src/lib/server/CMakeLists.txt | 2 ++ src/lib/server/Server.cpp | 27 ++++++++++++++++++++------- src/lib/server/Server.h | 5 +++-- src/lib/synergy/ServerApp.cpp | 2 +- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/lib/server/CMakeLists.txt b/src/lib/server/CMakeLists.txt index 2c34af07..3cb582ec 100644 --- a/src/lib/server/CMakeLists.txt +++ b/src/lib/server/CMakeLists.txt @@ -35,6 +35,8 @@ endif() add_library(server STATIC ${sources}) +target_link_libraries(server shared) + if (UNIX) target_link_libraries(server synergy) endif() diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index d9394ed5..a202648e 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -45,11 +45,13 @@ #include "base/Log.h" #include "base/TMethodEventJob.h" #include "common/stdexcept.h" +#include "shared/SerialKey.h" #include #include #include #include +#include // // Server @@ -60,7 +62,7 @@ Server::Server( PrimaryClient* primaryClient, synergy::Screen* screen, IEventQueue* events, - bool enableDragDrop) : + ServerArgs const& args) : m_mock(false), m_primaryClient(primaryClient), m_active(primaryClient), @@ -91,10 +93,10 @@ Server::Server( m_sendFileThread(NULL), m_writeToDropDirThread(NULL), m_ignoreFileTransfer(false), - m_enableDragDrop(enableDragDrop), m_enableClipboard(true), m_sendDragInfoThread(NULL), - m_waitDragInfoThread(true) + m_waitDragInfoThread(true), + m_args(args) { // must have a primary client and it must have a canonical name assert(m_primaryClient != NULL); @@ -184,7 +186,7 @@ Server::Server( new TMethodEventJob(this, &Server::handleFakeInputEndEvent)); - if (m_enableDragDrop) { + if (m_args.m_enableDragDrop) { m_events->adoptHandler(m_events->forFile().fileChunkSending(), this, new TMethodEventJob(this, @@ -451,6 +453,17 @@ Server::switchScreen(BaseClientProxy* dst, SInt32 x, SInt32 y, bool forScreensaver) { assert(dst != NULL); + + // if trial is expired, exit the process + if (!m_args.m_serial.empty()) { + SerialKey serial(m_args.m_serial); + if (!serial.isValid(std::time(0))) { + LOG((CLOG_ERR "trial is expired, aborting server")); + exit(kExitSuccess); + return; + } + } + #ifndef NDEBUG { SInt32 dx, dy, dw, dh; @@ -1706,7 +1719,7 @@ Server::onMouseUp(ButtonID id) return; } - if (m_enableDragDrop) { + if (m_args.m_enableDragDrop) { if (!m_screen->isOnScreen()) { String& file = m_screen->getDraggingFilename(); if (!file.empty()) { @@ -1791,7 +1804,7 @@ Server::onMouseMovePrimary(SInt32 x, SInt32 y) // should we switch or not? if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) { - if (m_enableDragDrop + if (m_args.m_enableDragDrop && m_screen->isDraggingStarted() && m_active != newScreen && m_waitDragInfoThread) { @@ -2393,7 +2406,7 @@ Server::sendFileThread(void* data) void Server::dragInfoReceived(UInt32 fileNum, String content) { - if (!m_enableDragDrop) { + if (!m_args.m_enableDragDrop) { LOG((CLOG_DEBUG "drag drop not enabled, ignoring drag info.")); return; } diff --git a/src/lib/server/Server.h b/src/lib/server/Server.h index 7681487a..d1e48bd5 100644 --- a/src/lib/server/Server.h +++ b/src/lib/server/Server.h @@ -25,6 +25,7 @@ #include "synergy/mouse_types.h" #include "synergy/INode.h" #include "synergy/DragInformation.h" +#include "synergy/ServerArgs.h" #include "base/Event.h" #include "base/Stopwatch.h" #include "base/EventTypes.h" @@ -106,7 +107,7 @@ public: ownership of \p primaryClient. */ Server(Config& config, PrimaryClient* primaryClient, - synergy::Screen* screen, IEventQueue* events, bool enableDragDrop); + synergy::Screen* screen, IEventQueue* events, ServerArgs const& args); ~Server(); #ifdef TEST_ENV @@ -472,11 +473,11 @@ private: Thread* m_writeToDropDirThread; String m_dragFileExt; bool m_ignoreFileTransfer; - bool m_enableDragDrop; bool m_enableClipboard; Thread* m_sendDragInfoThread; bool m_waitDragInfoThread; ClientListener* m_clientListener; + ServerArgs m_args; }; diff --git a/src/lib/synergy/ServerApp.cpp b/src/lib/synergy/ServerApp.cpp index 23884aec..ae1a2077 100644 --- a/src/lib/synergy/ServerApp.cpp +++ b/src/lib/synergy/ServerApp.cpp @@ -647,7 +647,7 @@ ServerApp::openClientListener(const NetworkAddress& address) Server* ServerApp::openServer(Config& config, PrimaryClient* primaryClient) { - Server* server = new Server(config, primaryClient, m_serverScreen, m_events, args().m_enableDragDrop); + Server* server = new Server(config, primaryClient, m_serverScreen, m_events, args()); try { m_events->adoptHandler( m_events->forServer().disconnected(), server, From 55414e458100881ff9ada66afaa2b998cb4aeff0 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Fri, 14 Oct 2016 17:38:31 +0100 Subject: [PATCH 28/88] #5657 Make SubscriptionManager backward compatible --- src/gui/res/ActivationDialog.ui | 6 +-- src/gui/res/MainWindowBase.ui | 36 +++++++++++++++ src/gui/res/ServerConfigDialogBase.ui | 2 +- src/gui/src/ActivationDialog.cpp | 36 +++++++++------ src/gui/src/ActivationDialog.h | 3 ++ src/gui/src/AppConfig.cpp | 9 ++-- src/gui/src/AppConfig.h | 8 ++-- src/gui/src/MainWindow.cpp | 41 +++++++++++++---- src/gui/src/MainWindow.h | 4 +- src/gui/src/QUtility.cpp | 4 +- src/gui/src/SettingsDialog.cpp | 2 +- src/gui/src/SubscriptionManager.cpp | 66 +++++++++++++++++++++------ src/gui/src/SubscriptionManager.h | 12 +++-- src/gui/src/main.cpp | 1 + src/lib/shared/EditionType.h | 6 +-- src/lib/shared/SerialKey.cpp | 29 +++++++----- src/lib/shared/SerialKey.h | 15 ++++-- 17 files changed, 205 insertions(+), 75 deletions(-) diff --git a/src/gui/res/ActivationDialog.ui b/src/gui/res/ActivationDialog.ui index d11a1a9f..0a980eb2 100644 --- a/src/gui/res/ActivationDialog.ui +++ b/src/gui/res/ActivationDialog.ui @@ -30,7 +30,7 @@ - Found on your <a href="https://symless.com/account/?source=gui">account</a> page. + <html><head/><body><p>This can be found on your <a href="https://symless.com/account/?source=gui"><span style=" text-decoration: underline; color:#0000ff;">account</span></a> page.</p></body></html> true @@ -46,8 +46,8 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'.SF NS Text'; font-size:13pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"><br /></p></body></html> +</style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> false diff --git a/src/gui/res/MainWindowBase.ui b/src/gui/res/MainWindowBase.ui index 87144cd1..644107bf 100644 --- a/src/gui/res/MainWindowBase.ui +++ b/src/gui/res/MainWindowBase.ui @@ -27,6 +27,42 @@ + + + + + + + + + + :/res/icons/16x16/warning.png + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + diff --git a/src/gui/res/ServerConfigDialogBase.ui b/src/gui/res/ServerConfigDialogBase.ui index 1cc4d2b5..5478f360 100644 --- a/src/gui/res/ServerConfigDialogBase.ui +++ b/src/gui/res/ServerConfigDialogBase.ui @@ -17,7 +17,7 @@ - 2 + 0 diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index f070007c..5162dd11 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -22,8 +22,14 @@ ActivationDialog::ActivationDialog(QWidget* parent, AppConfig& appConfig, m_subscriptionManager (&subscriptionManager) { ui->setupUi(this); + refreshSerialKey(); +} + +void ActivationDialog::refreshSerialKey() +{ + ui->m_pTextEditSerialKey->setText(m_appConfig->serialKey()); ui->m_pTextEditSerialKey->setFocus(); - ui->m_pTextEditSerialKey->moveCursor(QTextCursor::End); + ui->m_pTextEditSerialKey->moveCursor(QTextCursor::End); } ActivationDialog::~ActivationDialog() @@ -33,21 +39,22 @@ ActivationDialog::~ActivationDialog() void ActivationDialog::reject() { - CancelActivationDialog cancelActivationDialog(this); - if (QDialog::Accepted == cancelActivationDialog.exec()) { - m_subscriptionManager->notifySkip(); - m_appConfig->activationHasRun(true); - m_appConfig->saveSettings(); - QDialog::reject(); + if (m_subscriptionManager->edition() == Edition::kUnregistered) { + CancelActivationDialog cancelActivationDialog(this); + if (QDialog::Accepted == cancelActivationDialog.exec()) { + m_subscriptionManager->skipActivation(); + m_appConfig->activationHasRun(true); + m_appConfig->saveSettings(); + } } + QDialog::reject(); } void ActivationDialog::accept() { QMessageBox message; QString error; - int edition = Unregistered; - + m_appConfig->activationHasRun(true); m_appConfig->saveSettings(); @@ -60,13 +67,14 @@ void ActivationDialog::accept() tr("An error occurred while trying to activate Synergy. " "Please contact the helpdesk, and provide the " "following details.\n\n%1").arg(e.what())); + refreshSerialKey(); return; } - m_appConfig->setEdition(edition); - m_appConfig->saveSettings(); - - message.information(this, "Activated!", - tr("Thanks for activating %1!").arg(getEditionName(edition))); + if (m_subscriptionManager->edition() != Edition::kUnregistered) { + message.information(this, "Activated!", + tr("Thanks for activating %1!").arg + (getEditionName(m_subscriptionManager->edition()))); + } QDialog::accept(); } diff --git a/src/gui/src/ActivationDialog.h b/src/gui/src/ActivationDialog.h index 09577ed5..b0e9aa94 100644 --- a/src/gui/src/ActivationDialog.h +++ b/src/gui/src/ActivationDialog.h @@ -23,6 +23,9 @@ public slots: void reject(); void accept(); +protected: + void refreshSerialKey(); + private: Ui::ActivationDialog *ui; AppConfig* m_appConfig; diff --git a/src/gui/src/AppConfig.cpp b/src/gui/src/AppConfig.cpp index 22aec72c..cdb26e14 100644 --- a/src/gui/src/AppConfig.cpp +++ b/src/gui/src/AppConfig.cpp @@ -157,7 +157,7 @@ void AppConfig::loadSettings() } m_ElevateMode = static_cast(elevateMode.toInt()); m_AutoConfigPrompted = settings().value("autoConfigPrompted", false).toBool(); - m_Edition = settings().value("edition", Unregistered).toInt(); + m_Edition = static_cast(settings().value("edition", kUnregistered).toInt()); m_ActivateEmail = settings().value("activateEmail", "").toString(); m_CryptoEnabled = settings().value("cryptoEnabled", true).toBool(); m_AutoHide = settings().value("autoHide", false).toBool(); @@ -237,12 +237,11 @@ void AppConfig::setAutoConfigPrompted(bool prompted) m_AutoConfigPrompted = prompted; } -void AppConfig::setEdition(int e) { +void AppConfig::setEdition(Edition e) { m_Edition = e; - emit editionSet (e); } -int AppConfig::edition() const { return m_Edition; } +Edition AppConfig::edition() const { return m_Edition; } QString AppConfig::setSerialKey(QString serial) { using std::swap; @@ -276,7 +275,7 @@ void AppConfig::setCryptoEnabled(bool e) { } bool AppConfig::getCryptoEnabled() const { - return (edition() == Pro) && m_CryptoEnabled; + return (edition() == kPro) && m_CryptoEnabled; } void AppConfig::setAutoHide(bool b) { m_AutoHide = b; } diff --git a/src/gui/src/AppConfig.h b/src/gui/src/AppConfig.h index f62af1d2..b7eacf61 100644 --- a/src/gui/src/AppConfig.h +++ b/src/gui/src/AppConfig.h @@ -23,6 +23,7 @@ #include #include #include "ElevateMode.h" +#include // this should be incremented each time a new page is added. this is // saved to settings when the user finishes running the wizard. if @@ -77,8 +78,8 @@ class AppConfig: public QObject void setAutoConfig(bool autoConfig); bool autoConfigPrompted(); void setAutoConfigPrompted(bool prompted); - void setEdition(int e); - int edition() const; + void setEdition(Edition); + Edition edition() const; QString setSerialKey(QString serial); void clearSerialKey(); QString serialKey(); @@ -134,7 +135,7 @@ class AppConfig: public QObject bool m_AutoConfig; ElevateMode m_ElevateMode; bool m_AutoConfigPrompted; - int m_Edition; + Edition m_Edition; QString m_ActivateEmail; bool m_CryptoEnabled; bool m_AutoHide; @@ -147,7 +148,6 @@ class AppConfig: public QObject static const char m_SynergyLogDir[]; signals: - void editionSet(int); void sslToggled(bool enabled); }; diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 8165a43e..5adeab50 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -136,13 +136,28 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig, m_SuppressAutoConfigWarning = false; m_pComboServerList->hide(); - - setEdition(m_AppConfig->edition()); - m_pLabelPadlock->hide(); - connect (this, SIGNAL(windowShown()), this, SLOT(on_windowShown()), Qt::QueuedConnection); - connect (m_AppConfig, SIGNAL(editionSet(int)), this, SLOT(setEdition(int)), Qt::QueuedConnection); - connect (m_AppConfig, SIGNAL(sslToggled(bool)), this, SLOT(sslToggled(bool)), Qt::QueuedConnection); + setEdition (m_SubscriptionManager->edition()); + + this->m_trialWidget->hide(); + if (m_SubscriptionManager->isTrial()) { + beginTrial(); + } + + connect (this, SIGNAL(windowShown()), + this, SLOT(on_windowShown()), Qt::QueuedConnection); + + connect (m_SubscriptionManager, SIGNAL(editionChanged(Edition)), + this, SLOT(setEdition(Edition)), Qt::QueuedConnection); + + connect (m_SubscriptionManager, SIGNAL(beginTrial()), + this, SLOT(beginTrial()), Qt::QueuedConnection); + + connect (m_SubscriptionManager, SIGNAL(endTrial()), + this, SLOT(endTrial()), Qt::QueuedConnection); + + connect (m_AppConfig, SIGNAL(sslToggled(bool)), + this, SLOT(sslToggled(bool)), Qt::QueuedConnection); } MainWindow::~MainWindow() @@ -1024,7 +1039,7 @@ void MainWindow::serverDetected(const QString name) } } -void MainWindow::setEdition(int edition) +void MainWindow::setEdition(Edition edition) { setWindowTitle(getEditionName(edition)); if (m_AppConfig->getCryptoEnabled()) { @@ -1035,6 +1050,16 @@ void MainWindow::setEdition(int edition) saveSettings(); } +void MainWindow::beginTrial() +{ + this->m_trialWidget->show(); +} + +void MainWindow::endTrial() +{ + this->m_trialWidget->hide(); +} + void MainWindow::updateLocalFingerprint() { if (m_AppConfig->getCryptoEnabled() && Fingerprint::local().fileExists()) { @@ -1373,7 +1398,7 @@ void MainWindow::bonjourInstallFinished() void MainWindow::on_windowShown() { - if (!m_AppConfig->activationHasRun() && (m_AppConfig->edition() == Unregistered)) { + if (!m_AppConfig->activationHasRun() && (m_AppConfig->edition() == kUnregistered)) { ActivationDialog activationDialog (this, appConfig(), subscriptionManager()); activationDialog.exec(); } diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index 1dd678ba..22cddabe 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -121,7 +121,9 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase SubscriptionManager& subscriptionManager() const; public slots: - void setEdition(int edition); + void setEdition(Edition edition); + void beginTrial(); + void endTrial(); void appendLogRaw(const QString& text); void appendLogInfo(const QString& text); void appendLogDebug(const QString& text); diff --git a/src/gui/src/QUtility.cpp b/src/gui/src/QUtility.cpp index 322b2fd1..c35f9638 100644 --- a/src/gui/src/QUtility.cpp +++ b/src/gui/src/QUtility.cpp @@ -45,10 +45,10 @@ void setIndexFromItemData(QComboBox* comboBox, const QVariant& itemData) QString getEditionName (int edition) { - if (edition == Basic) { + if (edition == kBasic) { return "Synergy Basic"; } - else if (edition == Pro) { + else if (edition == kPro) { return "Synergy Pro"; } else { diff --git a/src/gui/src/SettingsDialog.cpp b/src/gui/src/SettingsDialog.cpp index 9b1d1b0e..ddbeae4d 100644 --- a/src/gui/src/SettingsDialog.cpp +++ b/src/gui/src/SettingsDialog.cpp @@ -64,7 +64,7 @@ SettingsDialog::SettingsDialog(QWidget* parent, AppConfig& config) : #endif m_pCheckBoxEnableCrypto->setChecked(m_appConfig.getCryptoEnabled()); - m_pCheckBoxEnableCrypto->setEnabled(m_appConfig.edition() == Pro); + m_pCheckBoxEnableCrypto->setEnabled(m_appConfig.edition() == kPro); } void SettingsDialog::accept() diff --git a/src/gui/src/SubscriptionManager.cpp b/src/gui/src/SubscriptionManager.cpp index fd2a77bb..86d42536 100644 --- a/src/gui/src/SubscriptionManager.cpp +++ b/src/gui/src/SubscriptionManager.cpp @@ -22,21 +22,59 @@ #include SubscriptionManager::SubscriptionManager(AppConfig* appConfig) : - m_AppConfig(appConfig) { -} - -void -SubscriptionManager::setSerialKey(QString serialKeyString) -{ - SerialKey serialKey (serialKeyString.toStdString()); - if (serialKey.isValid (::time(0)) && (serialKey != m_serialKey)) { - m_AppConfig->setSerialKey (serialKeyString); - notifyActivation ("serial:" + serialKeyString); - emit serialKeyChanged (serialKey); + m_AppConfig(appConfig), + m_serialKey(appConfig->edition()) { + try { + setSerialKey(m_AppConfig->serialKey()); + } catch (...) { + m_AppConfig->setSerialKey(""); } } -void SubscriptionManager::notifySkip() +SerialKey +SubscriptionManager::setSerialKey(QString serialKeyString) +{ + SerialKey serialKey (serialKeyString.toStdString()); + if (!serialKey.isValid (::time(0))) { + throw std::runtime_error ("Invalid serial key"); + } + + if (serialKey != m_serialKey) { + using std::swap; + swap (serialKey, m_serialKey); + + m_AppConfig->setSerialKey (serialKeyString); + notifyActivation ("serial:" + serialKeyString); + emit serialKeyChanged (m_serialKey); + + if (m_serialKey.edition() != serialKey.edition()) { + m_AppConfig->setEdition (m_serialKey.edition()); + emit editionChanged (m_serialKey.edition()); + } + + if (m_serialKey.isTrial() != serialKey.isTrial()) { + if (m_serialKey.isTrial()) { + emit beginTrial(); + } else { + emit endTrial(); + } + } + } + + return serialKey; +} + +Edition SubscriptionManager::edition() const +{ + return m_serialKey.edition(); +} + +bool SubscriptionManager::isTrial() const +{ + return m_serialKey.isTrial(); +} + +void SubscriptionManager::skipActivation() { notifyActivation ("skip:unknown"); } @@ -45,7 +83,7 @@ void SubscriptionManager::notifyActivation(QString identity) { ActivationNotifier* notifier = new ActivationNotifier(); notifier->setIdentity(identity); - + QThread* thread = new QThread(); connect(notifier, SIGNAL(finished()), thread, SLOT(quit())); connect(notifier, SIGNAL(finished()), notifier, SLOT(deleteLater())); @@ -54,5 +92,5 @@ void SubscriptionManager::notifyActivation(QString identity) notifier->moveToThread(thread); thread->start(); - QMetaObject::invokeMethod(notifier, "notify", Qt::QueuedConnection); + QMetaObject::invokeMethod(notifier, "notify", Qt::QueuedConnection); } diff --git a/src/gui/src/SubscriptionManager.h b/src/gui/src/SubscriptionManager.h index 2c79f6bd..eff9112d 100644 --- a/src/gui/src/SubscriptionManager.h +++ b/src/gui/src/SubscriptionManager.h @@ -28,12 +28,14 @@ class SubscriptionManager: public QObject Q_OBJECT public: - SubscriptionManager (AppConfig* appConfig); - void setSerialKey (QString serialKey); - void notifySkip (); + SubscriptionManager(AppConfig* appConfig); + SerialKey setSerialKey(QString serialKey); + Edition edition() const; + bool isTrial() const; + void skipActivation(); private: - void notifyActivation (QString identity); + void notifyActivation(QString identity); private: AppConfig* m_AppConfig; @@ -42,4 +44,6 @@ private: signals: void serialKeyChanged (SerialKey); void editionChanged (Edition); + void beginTrial (); + void endTrial (); }; diff --git a/src/gui/src/main.cpp b/src/gui/src/main.cpp index 79131f36..18febc8e 100644 --- a/src/gui/src/main.cpp +++ b/src/gui/src/main.cpp @@ -84,6 +84,7 @@ int main(int argc, char* argv[]) QSettings settings; AppConfig appConfig (&settings); + qRegisterMetaType("Edition"); SubscriptionManager subscriptionManager (&appConfig); app.switchTranslator(appConfig.language()); diff --git a/src/lib/shared/EditionType.h b/src/lib/shared/EditionType.h index ddd27ccf..66f30aa9 100644 --- a/src/lib/shared/EditionType.h +++ b/src/lib/shared/EditionType.h @@ -21,10 +21,10 @@ /* Do not reorder these! */ enum Edition { - Basic, - Pro, + kBasic, + kPro, Trial_DO_NOT_USE_OR_THERE_WILL_BE_PAIN, - Unregistered + kUnregistered }; #endif // EDITIONTYPE_H diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index 52dd2743..be8cbc35 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -21,13 +21,17 @@ #include #include #include +#include using namespace std; -SerialKey::SerialKey(): - m_warnTime(1), - m_expireTime(1), - m_trial(true) +SerialKey::SerialKey(Edition edition): + m_userLimit(1), + m_warnTime(ULLONG_MAX), + m_expireTime(ULLONG_MAX), + m_edition(edition), + m_trial(false), + m_valid(true) { } @@ -35,13 +39,13 @@ SerialKey::SerialKey(std::string serial) : m_userLimit(1), m_warnTime(0), m_expireTime(0), - m_edition(Edition::Basic), + m_edition(Edition::kBasic), m_trial(true), m_valid(false) { string plainText = decode(serial); if (!plainText.empty()) { - parse(serial); + parse(plainText); } } @@ -70,7 +74,7 @@ SerialKey::isExpiring(time_t currentTime) const bool result = false; if (m_valid) { - if (m_warnTime < currentTime && currentTime < m_expireTime) { + if (m_warnTime <= currentTime && currentTime < m_expireTime) { result = true; } } @@ -84,7 +88,7 @@ SerialKey::isExpired(time_t currentTime) const bool result = false; if (m_valid) { - if (currentTime > m_expireTime) { + if (m_expireTime <= currentTime) { result = true; } } @@ -108,7 +112,7 @@ time_t SerialKey::daysLeft(time_t currentTime) const { unsigned long long timeLeft = 0; - if (m_expireTime > currentTime) { + if (currentTime < m_expireTime) { timeLeft = m_expireTime - currentTime; } @@ -170,12 +174,13 @@ SerialKey::parse(std::string plainSerial) parts.push_back(plainSerial.substr(start, pos - start)); pos += 1; } - + if ((parts.size() == 8) && (parts.at(0).find("v1") != string::npos)) { // e.g.: {v1;basic;Bob;1;email;company name;1398297600;1398384000} m_edition = getEdition(parts.at(1)); m_name = parts.at(2); + m_trial = false; sscanf(parts.at(3).c_str(), "%d", &m_userLimit); m_email = parts.at(4); m_company = parts.at(5); @@ -202,9 +207,9 @@ SerialKey::parse(std::string plainSerial) Edition SerialKey::getEdition(std::string editionStr) { - Edition e = Edition::Basic; + Edition e = Edition::kBasic; if (editionStr == "pro") { - e = Edition::Pro; + e = Edition::kPro; } return e; diff --git a/src/lib/shared/SerialKey.h b/src/lib/shared/SerialKey.h index 2423cf55..03d0ab98 100644 --- a/src/lib/shared/SerialKey.h +++ b/src/lib/shared/SerialKey.h @@ -26,9 +26,10 @@ #endif class SerialKey { + friend bool operator== (SerialKey const&, SerialKey const&); public: - SerialKey(); - SerialKey(std::string serial); + explicit SerialKey(Edition edition = Edition::kUnregistered); + explicit SerialKey(std::string serial); bool isValid(time_t currentTime) const; bool isExpiring(time_t currentTime) const; @@ -68,7 +69,15 @@ private: inline bool operator== (SerialKey const& lhs, SerialKey const& rhs) { - return (lhs.edition() == rhs.edition()); + return (lhs.m_name == rhs.m_name) && + (lhs.m_email == rhs.m_email) && + (lhs.m_company == rhs.m_company) && + (lhs.m_userLimit == rhs.m_userLimit) && + (lhs.m_warnTime == rhs.m_warnTime) && + (lhs.m_expireTime == rhs.m_expireTime) && + (lhs.m_edition == rhs.m_edition) && + (lhs.m_trial == rhs.m_trial) && + (lhs.m_valid == rhs.m_valid); } inline bool From dc31f395cc87c06eef3e6313a9c449e7a9b566ad Mon Sep 17 00:00:00 2001 From: XinyuHou Date: Fri, 14 Oct 2016 09:44:02 -0700 Subject: [PATCH 29/88] #5657 Fixed integtests using the old server constructor --- src/test/integtests/net/NetworkTests.cpp | 48 ++++++++++++++---------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/test/integtests/net/NetworkTests.cpp b/src/test/integtests/net/NetworkTests.cpp index 79ef7c99..dbb3dd1e 100644 --- a/src/test/integtests/net/NetworkTests.cpp +++ b/src/test/integtests/net/NetworkTests.cpp @@ -129,7 +129,9 @@ TEST_F(NetworkTests, sendToClient_mockData) ON_CALL(serverConfig, isScreen(_)).WillByDefault(Return(true)); ON_CALL(serverConfig, getInputFilter()).WillByDefault(Return(&serverInputFilter)); - Server server(serverConfig, &primaryClient, &serverScreen, &m_events, true); + ServerArgs serverArgs; + serverArgs.m_enableDragDrop = true; + Server server(serverConfig, &primaryClient, &serverScreen, &m_events, serverArgs); server.m_mock = true; listener.setServer(&server); @@ -142,10 +144,10 @@ TEST_F(NetworkTests, sendToClient_mockData) ON_CALL(clientScreen, getCursorPos(_, _)).WillByDefault(Invoke(getCursorPos)); - ClientArgs args; - args.m_enableDragDrop = true; - args.m_enableCrypto = false; - Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, args); + ClientArgs clientArgs; + clientArgs.m_enableDragDrop = true; + clientArgs.m_enableCrypto = false; + Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, clientArgs); m_events.adoptHandler( m_events.forFile().fileRecieveCompleted(), &client, @@ -185,7 +187,9 @@ TEST_F(NetworkTests, sendToClient_mockFile) ON_CALL(serverConfig, isScreen(_)).WillByDefault(Return(true)); ON_CALL(serverConfig, getInputFilter()).WillByDefault(Return(&serverInputFilter)); - Server server(serverConfig, &primaryClient, &serverScreen, &m_events, true); + ServerArgs serverArgs; + serverArgs.m_enableDragDrop = true; + Server server(serverConfig, &primaryClient, &serverScreen, &m_events, serverArgs); server.m_mock = true; listener.setServer(&server); @@ -198,10 +202,10 @@ TEST_F(NetworkTests, sendToClient_mockFile) ON_CALL(clientScreen, getCursorPos(_, _)).WillByDefault(Invoke(getCursorPos)); - ClientArgs args; - args.m_enableDragDrop = true; - args.m_enableCrypto = false; - Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, args); + ClientArgs clientArgs; + clientArgs.m_enableDragDrop = true; + clientArgs.m_enableCrypto = false; + Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, clientArgs); m_events.adoptHandler( m_events.forFile().fileRecieveCompleted(), &client, @@ -235,7 +239,9 @@ TEST_F(NetworkTests, sendToServer_mockData) ON_CALL(serverConfig, isScreen(_)).WillByDefault(Return(true)); ON_CALL(serverConfig, getInputFilter()).WillByDefault(Return(&serverInputFilter)); - Server server(serverConfig, &primaryClient, &serverScreen, &m_events, true); + ServerArgs serverArgs; + serverArgs.m_enableDragDrop = true; + Server server(serverConfig, &primaryClient, &serverScreen, &m_events, serverArgs); server.m_mock = true; listener.setServer(&server); @@ -247,10 +253,10 @@ TEST_F(NetworkTests, sendToServer_mockData) ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape)); ON_CALL(clientScreen, getCursorPos(_, _)).WillByDefault(Invoke(getCursorPos)); - ClientArgs args; - args.m_enableDragDrop = true; - args.m_enableCrypto = false; - Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, args); + ClientArgs clientArgs; + clientArgs.m_enableDragDrop = true; + clientArgs.m_enableCrypto = false; + Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, clientArgs); m_events.adoptHandler( m_events.forClientListener().connected(), &listener, @@ -290,7 +296,9 @@ TEST_F(NetworkTests, sendToServer_mockFile) ON_CALL(serverConfig, isScreen(_)).WillByDefault(Return(true)); ON_CALL(serverConfig, getInputFilter()).WillByDefault(Return(&serverInputFilter)); - Server server(serverConfig, &primaryClient, &serverScreen, &m_events, true); + ServerArgs serverArgs; + serverArgs.m_enableDragDrop = true; + Server server(serverConfig, &primaryClient, &serverScreen, &m_events, serverArgs); server.m_mock = true; listener.setServer(&server); @@ -302,10 +310,10 @@ TEST_F(NetworkTests, sendToServer_mockFile) ON_CALL(clientScreen, getShape(_, _, _, _)).WillByDefault(Invoke(getScreenShape)); ON_CALL(clientScreen, getCursorPos(_, _)).WillByDefault(Invoke(getCursorPos)); - ClientArgs args; - args.m_enableDragDrop = true; - args.m_enableCrypto = false; - Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, args); + ClientArgs clientArgs; + clientArgs.m_enableDragDrop = true; + clientArgs.m_enableCrypto = false; + Client client(&m_events, "stub", serverAddress, clientSocketFactory, &clientScreen, clientArgs); m_events.adoptHandler( m_events.forClientListener().connected(), &listener, From 1f93b4a918a4b4c4b21a219b38f5c3996fba12cb Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Sat, 15 Oct 2016 12:37:00 +0100 Subject: [PATCH 30/88] #5657 Rename dayLeft to daysLeft in unit tests --- src/test/unittests/shared/SerialKeyTests.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/unittests/shared/SerialKeyTests.cpp b/src/test/unittests/shared/SerialKeyTests.cpp index 40fb479a..9535e15d 100644 --- a/src/test/unittests/shared/SerialKeyTests.cpp +++ b/src/test/unittests/shared/SerialKeyTests.cpp @@ -63,7 +63,7 @@ TEST(SerialKeyTests, parse_validV1Serial_valid) EXPECT_EQ(true, serial.isValid(0)); EXPECT_EQ(kBasic, serial.edition()); EXPECT_FALSE(serial.isExpired(0)); - EXPECT_EQ(true, serial.dayLeft(0)); + EXPECT_EQ(true, serial.daysLeft(0)); EXPECT_EQ(true, serial.isExpiring(1)); } @@ -74,7 +74,7 @@ TEST(SerialKeyTests, parse_validV2Serial_valid) EXPECT_EQ(true, serial.isValid(0)); EXPECT_EQ(kPro, serial.edition()); EXPECT_FALSE(serial.isExpired(0)); - EXPECT_EQ(true, serial.dayLeft(0)); + EXPECT_EQ(true, serial.daysLeft(0)); EXPECT_EQ(true, serial.isExpiring(1)); EXPECT_EQ(true, serial.isTrial()); } @@ -179,23 +179,23 @@ TEST(SerialKeyTests, isExpired_expiredV2TrialBasicSerial_returnTrue) EXPECT_EQ(true, serial.isExpired(86401)); } -TEST(SerialKeyTests, dayLeft_validExactlyOneDayV2TrialBasicSerial_returnOne) +TEST(SerialKeyTests, daysLeft_validExactlyOneDayV2TrialBasicSerial_returnOne) { // {v2;trial;basic;Bob;1;email;company name;0;86400} SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); - EXPECT_EQ(1, serial.dayLeft(0)); + EXPECT_EQ(1, serial.daysLeft(0)); } -TEST(SerialKeyTests, dayLeft_validWithinOneDayV2TrialBasicSerial_returnOne) +TEST(SerialKeyTests, daysLeft_validWithinOneDayV2TrialBasicSerial_returnOne) { // {v2;trial;basic;Bob;1;email;company name;0;86400} SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); - EXPECT_EQ(1, serial.dayLeft(1)); + EXPECT_EQ(1, serial.daysLeft(1)); } -TEST(SerialKeyTests, dayLeft_expiredV2TrialBasicSerial_returnZero) +TEST(SerialKeyTests, daysLeft_expiredV2TrialBasicSerial_returnZero) { // {v2;trial;basic;Bob;1;email;company name;0;86400} SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); - EXPECT_EQ(0, serial.dayLeft(86401)); + EXPECT_EQ(0, serial.daysLeft(86401)); } From 3b98a7b785be7616709ae80420e2c5bc57d2016b Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Sat, 15 Oct 2016 14:58:03 +0100 Subject: [PATCH 31/88] #5657 Add trial label to main window --- src/gui/res/MainWindowBase.ui | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/gui/res/MainWindowBase.ui b/src/gui/res/MainWindowBase.ui index 644107bf..ba00f2d2 100644 --- a/src/gui/res/MainWindowBase.ui +++ b/src/gui/res/MainWindowBase.ui @@ -30,6 +30,18 @@ + + 2 + + + 0 + + + 0 + + + 8 + @@ -41,9 +53,9 @@ - + - + <html><head/><body><p><span style=" font-weight:600;">6</span> days of your Synergy Pro trial remain. <a href="http://symless.com/pricing?src=gui"><span style=" text-decoration: underline; color:#0000ff;">Buy now!</span></a></p></body></html> @@ -76,7 +88,7 @@ 0 - 7 + 8 From 4ad7c7fe39eeb96a051691d7a08d11389625448f Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Sat, 15 Oct 2016 15:30:28 +0100 Subject: [PATCH 32/88] #5657 Rename edition to activeLicense. WSFs --- src/gui/src/ActivationDialog.cpp | 88 +- src/gui/src/MainWindow.cpp | 1743 ++++++++++++++------------- src/gui/src/MainWindow.h | 300 ++--- src/gui/src/SubscriptionManager.cpp | 97 +- src/gui/src/SubscriptionManager.h | 30 +- src/lib/shared/SerialKey.cpp | 284 ++--- src/lib/shared/SerialKey.h | 84 +- 7 files changed, 1317 insertions(+), 1309 deletions(-) diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 5162dd11..9d3b43f3 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -14,67 +14,67 @@ #include #include -ActivationDialog::ActivationDialog(QWidget* parent, AppConfig& appConfig, - SubscriptionManager& subscriptionManager) : - QDialog(parent), - ui(new Ui::ActivationDialog), - m_appConfig(&appConfig), - m_subscriptionManager (&subscriptionManager) +ActivationDialog::ActivationDialog(QWidget* parent, AppConfig& appConfig, + SubscriptionManager& subscriptionManager) : + QDialog(parent), + ui(new Ui::ActivationDialog), + m_appConfig(&appConfig), + m_subscriptionManager (&subscriptionManager) { - ui->setupUi(this); - refreshSerialKey(); + ui->setupUi(this); + refreshSerialKey(); } void ActivationDialog::refreshSerialKey() { - ui->m_pTextEditSerialKey->setText(m_appConfig->serialKey()); - ui->m_pTextEditSerialKey->setFocus(); - ui->m_pTextEditSerialKey->moveCursor(QTextCursor::End); + ui->m_pTextEditSerialKey->setText(m_appConfig->serialKey()); + ui->m_pTextEditSerialKey->setFocus(); + ui->m_pTextEditSerialKey->moveCursor(QTextCursor::End); } ActivationDialog::~ActivationDialog() { - delete ui; + delete ui; } void ActivationDialog::reject() { - if (m_subscriptionManager->edition() == Edition::kUnregistered) { - CancelActivationDialog cancelActivationDialog(this); - if (QDialog::Accepted == cancelActivationDialog.exec()) { - m_subscriptionManager->skipActivation(); - m_appConfig->activationHasRun(true); - m_appConfig->saveSettings(); - } - } - QDialog::reject(); + if (m_subscriptionManager->activeLicense() == Edition::kUnregistered) { + CancelActivationDialog cancelActivationDialog(this); + if (QDialog::Accepted == cancelActivationDialog.exec()) { + m_subscriptionManager->skipActivation(); + m_appConfig->activationHasRun(true); + m_appConfig->saveSettings(); + } + } + QDialog::reject(); } void ActivationDialog::accept() { - QMessageBox message; - QString error; - - m_appConfig->activationHasRun(true); - m_appConfig->saveSettings(); + QMessageBox message; + QString error; - try { - QString serialKey = ui->m_pTextEditSerialKey->toPlainText(); - m_subscriptionManager->setSerialKey(serialKey); - } - catch (std::exception& e) { - message.critical(this, "Unknown Error", - tr("An error occurred while trying to activate Synergy. " - "Please contact the helpdesk, and provide the " - "following details.\n\n%1").arg(e.what())); - refreshSerialKey(); - return; - } + m_appConfig->activationHasRun(true); + m_appConfig->saveSettings(); - if (m_subscriptionManager->edition() != Edition::kUnregistered) { - message.information(this, "Activated!", - tr("Thanks for activating %1!").arg - (getEditionName(m_subscriptionManager->edition()))); - } - QDialog::accept(); + try { + QString serialKey = ui->m_pTextEditSerialKey->toPlainText(); + m_subscriptionManager->setSerialKey(serialKey); + } + catch (std::exception& e) { + message.critical(this, "Unknown Error", + tr("An error occurred while trying to activate Synergy. " + "Please contact the helpdesk, and provide the " + "following details.\n\n%1").arg(e.what())); + refreshSerialKey(); + return; + } + + if (m_subscriptionManager->activeLicense() != Edition::kUnregistered) { + message.information(this, "Activated!", + tr("Thanks for activating %1!").arg + (getEditionName(m_subscriptionManager->activeLicense()))); + } + QDialog::accept(); } diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 5adeab50..ad9d37d0 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -70,1351 +70,1352 @@ static const QString synergyConfigFilter(QObject::tr("Synergy Configurations (*. static const char* synergyIconFiles[] = { - ":/res/icons/16x16/synergy-disconnected.png", - ":/res/icons/16x16/synergy-disconnected.png", - ":/res/icons/16x16/synergy-connected.png", - ":/res/icons/16x16/synergy-transfering.png" + ":/res/icons/16x16/synergy-disconnected.png", + ":/res/icons/16x16/synergy-disconnected.png", + ":/res/icons/16x16/synergy-connected.png", + ":/res/icons/16x16/synergy-transfering.png" }; MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig, - SubscriptionManager& subscriptionManager) : - m_Settings(settings), - m_AppConfig(&appConfig), - m_SubscriptionManager(&subscriptionManager), - m_pSynergy(NULL), - m_SynergyState(synergyDisconnected), - m_ServerConfig(&m_Settings, 5, 3, m_AppConfig->screenName(), this), - m_pTempConfigFile(NULL), - m_pTrayIcon(NULL), - m_pTrayIconMenu(NULL), - m_AlreadyHidden(false), - m_pMenuBar(NULL), - m_pMenuFile(NULL), - m_pMenuEdit(NULL), - m_pMenuWindow(NULL), - m_pMenuHelp(NULL), - m_pZeroconfService(NULL), - m_pDataDownloader(NULL), - m_DownloadMessageBox(NULL), - m_pCancelButton(NULL), - m_SuppressAutoConfigWarning(false), - m_BonjourInstall(NULL), - m_SuppressEmptyServerWarning(false), - m_ExpectedRunningState(kStopped), - m_pSslCertificate(NULL) + SubscriptionManager& subscriptionManager) : + m_Settings(settings), + m_AppConfig(&appConfig), + m_SubscriptionManager(&subscriptionManager), + m_pSynergy(NULL), + m_SynergyState(synergyDisconnected), + m_ServerConfig(&m_Settings, 5, 3, m_AppConfig->screenName(), this), + m_pTempConfigFile(NULL), + m_pTrayIcon(NULL), + m_pTrayIconMenu(NULL), + m_AlreadyHidden(false), + m_pMenuBar(NULL), + m_pMenuFile(NULL), + m_pMenuEdit(NULL), + m_pMenuWindow(NULL), + m_pMenuHelp(NULL), + m_pZeroconfService(NULL), + m_pDataDownloader(NULL), + m_DownloadMessageBox(NULL), + m_pCancelButton(NULL), + m_SuppressAutoConfigWarning(false), + m_BonjourInstall(NULL), + m_SuppressEmptyServerWarning(false), + m_ExpectedRunningState(kStopped), + m_pSslCertificate(NULL) { - setupUi(this); + setupUi(this); - createMenuBar(); - loadSettings(); - initConnections(); + createMenuBar(); + loadSettings(); + initConnections(); - m_pWidgetUpdate->hide(); - m_VersionChecker.setApp(appPath(appConfig.synergycName())); - m_pLabelScreenName->setText(getScreenName()); - m_pLabelIpAddresses->setText(getIPAddresses()); + m_pWidgetUpdate->hide(); + m_VersionChecker.setApp(appPath(appConfig.synergycName())); + m_pLabelScreenName->setText(getScreenName()); + m_pLabelIpAddresses->setText(getIPAddresses()); #if defined(Q_OS_WIN) - // ipc must always be enabled, so that we can disable command when switching to desktop mode. - connect(&m_IpcClient, SIGNAL(readLogLine(const QString&)), this, SLOT(appendLogRaw(const QString&))); - connect(&m_IpcClient, SIGNAL(errorMessage(const QString&)), this, SLOT(appendLogError(const QString&))); - connect(&m_IpcClient, SIGNAL(infoMessage(const QString&)), this, SLOT(appendLogNote(const QString&))); - m_IpcClient.connectToHost(); + // ipc must always be enabled, so that we can disable command when switching to desktop mode. + connect(&m_IpcClient, SIGNAL(readLogLine(const QString&)), this, SLOT(appendLogRaw(const QString&))); + connect(&m_IpcClient, SIGNAL(errorMessage(const QString&)), this, SLOT(appendLogError(const QString&))); + connect(&m_IpcClient, SIGNAL(infoMessage(const QString&)), this, SLOT(appendLogNote(const QString&))); + m_IpcClient.connectToHost(); #endif - // change default size based on os + // change default size based on os #if defined(Q_OS_MAC) - resize(720, 550); - setMinimumSize(size()); + resize(720, 550); + setMinimumSize(size()); #elif defined(Q_OS_LINUX) - resize(700, 530); - setMinimumSize(size()); + resize(700, 530); + setMinimumSize(size()); #endif - m_SuppressAutoConfigWarning = true; - m_pCheckBoxAutoConfig->setChecked(appConfig.autoConfig()); - m_SuppressAutoConfigWarning = false; + m_SuppressAutoConfigWarning = true; + m_pCheckBoxAutoConfig->setChecked(appConfig.autoConfig()); + m_SuppressAutoConfigWarning = false; - m_pComboServerList->hide(); - m_pLabelPadlock->hide(); - setEdition (m_SubscriptionManager->edition()); + m_pComboServerList->hide(); + m_pLabelPadlock->hide(); + m_trialWidget->hide(); - this->m_trialWidget->hide(); - if (m_SubscriptionManager->isTrial()) { - beginTrial(); - } - - connect (this, SIGNAL(windowShown()), - this, SLOT(on_windowShown()), Qt::QueuedConnection); - - connect (m_SubscriptionManager, SIGNAL(editionChanged(Edition)), - this, SLOT(setEdition(Edition)), Qt::QueuedConnection); - - connect (m_SubscriptionManager, SIGNAL(beginTrial()), - this, SLOT(beginTrial()), Qt::QueuedConnection); - - connect (m_SubscriptionManager, SIGNAL(endTrial()), - this, SLOT(endTrial()), Qt::QueuedConnection); - - connect (m_AppConfig, SIGNAL(sslToggled(bool)), - this, SLOT(sslToggled(bool)), Qt::QueuedConnection); + connect (this, SIGNAL(windowShown()), + this, SLOT(on_windowShown()), Qt::QueuedConnection); + + connect (m_SubscriptionManager, SIGNAL(editionChanged(Edition)), + this, SLOT(setEdition(Edition)), Qt::QueuedConnection); + + connect (m_SubscriptionManager, SIGNAL(beginTrial(bool)), + this, SLOT(beginTrial(bool)), Qt::QueuedConnection); + + connect (m_SubscriptionManager, SIGNAL(endTrial(bool)), + this, SLOT(endTrial(bool)), Qt::QueuedConnection); + + connect (m_AppConfig, SIGNAL(sslToggled(bool)), + this, SLOT(sslToggled(bool)), Qt::QueuedConnection); + + m_SubscriptionManager->update(); } MainWindow::~MainWindow() { - if (appConfig().processMode() == Desktop) { - m_ExpectedRunningState = kStopped; - stopDesktop(); - } + if (appConfig().processMode() == Desktop) { + m_ExpectedRunningState = kStopped; + stopDesktop(); + } - saveSettings(); + saveSettings(); - delete m_pZeroconfService; + delete m_pZeroconfService; - if (m_DownloadMessageBox != NULL) { - delete m_DownloadMessageBox; - } + if (m_DownloadMessageBox != NULL) { + delete m_DownloadMessageBox; + } - if (m_BonjourInstall != NULL) { - delete m_BonjourInstall; - } + if (m_BonjourInstall != NULL) { + delete m_BonjourInstall; + } - delete m_pSslCertificate; + delete m_pSslCertificate; } void MainWindow::open() { - createTrayIcon(); + createTrayIcon(); - if (!autoHide()) { - showNormal(); - } + if (!autoHide()) { + showNormal(); + } - m_VersionChecker.checkLatest(); + m_VersionChecker.checkLatest(); - if (!appConfig().autoConfigPrompted()) { - promptAutoConfig(); - } + if (!appConfig().autoConfigPrompted()) { + promptAutoConfig(); + } - // only start if user has previously started. this stops the gui from - // auto hiding before the user has configured synergy (which of course - // confuses first time users, who think synergy has crashed). - if (appConfig().startedBefore() && appConfig().processMode() == Desktop) { - m_SuppressEmptyServerWarning = true; - startSynergy(); - m_SuppressEmptyServerWarning = false; - } + // only start if user has previously started. this stops the gui from + // auto hiding before the user has configured synergy (which of course + // confuses first time users, who think synergy has crashed). + if (appConfig().startedBefore() && appConfig().processMode() == Desktop) { + m_SuppressEmptyServerWarning = true; + startSynergy(); + m_SuppressEmptyServerWarning = false; + } } void MainWindow::onModeChanged(bool startDesktop, bool applyService) { - if (appConfig().processMode() == Service) - { - // ensure that the apply button actually does something, since desktop - // mode screws around with connecting/disconnecting the action. - disconnect(m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); - connect(m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); + if (appConfig().processMode() == Service) + { + // ensure that the apply button actually does something, since desktop + // mode screws around with connecting/disconnecting the action. + disconnect(m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); + connect(m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); - if (applyService) - { - stopDesktop(); - startSynergy(); - } - } - else if ((appConfig().processMode() == Desktop) && startDesktop) - { - stopService(); - startSynergy(); - } + if (applyService) + { + stopDesktop(); + startSynergy(); + } + } + else if ((appConfig().processMode() == Desktop) && startDesktop) + { + stopService(); + startSynergy(); + } } void MainWindow::setStatus(const QString &status) { - m_pStatusLabel->setText(status); + m_pStatusLabel->setText(status); } void MainWindow::createTrayIcon() { - m_pTrayIconMenu = new QMenu(this); + m_pTrayIconMenu = new QMenu(this); - m_pTrayIconMenu->addAction(m_pActionStartSynergy); - m_pTrayIconMenu->addAction(m_pActionStopSynergy); - m_pTrayIconMenu->addSeparator(); + m_pTrayIconMenu->addAction(m_pActionStartSynergy); + m_pTrayIconMenu->addAction(m_pActionStopSynergy); + m_pTrayIconMenu->addSeparator(); - m_pTrayIconMenu->addAction(m_pActionMinimize); - m_pTrayIconMenu->addAction(m_pActionRestore); - m_pTrayIconMenu->addSeparator(); - m_pTrayIconMenu->addAction(m_pActionQuit); + m_pTrayIconMenu->addAction(m_pActionMinimize); + m_pTrayIconMenu->addAction(m_pActionRestore); + m_pTrayIconMenu->addSeparator(); + m_pTrayIconMenu->addAction(m_pActionQuit); - m_pTrayIcon = new QSystemTrayIcon(this); - m_pTrayIcon->setContextMenu(m_pTrayIconMenu); + m_pTrayIcon = new QSystemTrayIcon(this); + m_pTrayIcon->setContextMenu(m_pTrayIconMenu); - connect(m_pTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), - this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason))); + connect(m_pTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason))); - setIcon(synergyDisconnected); + setIcon(synergyDisconnected); - m_pTrayIcon->show(); + m_pTrayIcon->show(); } void MainWindow::retranslateMenuBar() { - m_pMenuFile->setTitle(tr("&File")); - m_pMenuEdit->setTitle(tr("&Edit")); - m_pMenuWindow->setTitle(tr("&Window")); - m_pMenuHelp->setTitle(tr("&Help")); + m_pMenuFile->setTitle(tr("&File")); + m_pMenuEdit->setTitle(tr("&Edit")); + m_pMenuWindow->setTitle(tr("&Window")); + m_pMenuHelp->setTitle(tr("&Help")); } void MainWindow::createMenuBar() { - m_pMenuBar = new QMenuBar(this); - m_pMenuFile = new QMenu("", m_pMenuBar); - m_pMenuEdit = new QMenu("", m_pMenuBar); - m_pMenuWindow = new QMenu("", m_pMenuBar); - m_pMenuHelp = new QMenu("", m_pMenuBar); - retranslateMenuBar(); + m_pMenuBar = new QMenuBar(this); + m_pMenuFile = new QMenu("", m_pMenuBar); + m_pMenuEdit = new QMenu("", m_pMenuBar); + m_pMenuWindow = new QMenu("", m_pMenuBar); + m_pMenuHelp = new QMenu("", m_pMenuBar); + retranslateMenuBar(); - m_pMenuBar->addAction(m_pMenuFile->menuAction()); - m_pMenuBar->addAction(m_pMenuEdit->menuAction()); + m_pMenuBar->addAction(m_pMenuFile->menuAction()); + m_pMenuBar->addAction(m_pMenuEdit->menuAction()); #if !defined(Q_OS_MAC) - m_pMenuBar->addAction(m_pMenuWindow->menuAction()); + m_pMenuBar->addAction(m_pMenuWindow->menuAction()); #endif - m_pMenuBar->addAction(m_pMenuHelp->menuAction()); + m_pMenuBar->addAction(m_pMenuHelp->menuAction()); - m_pMenuFile->addAction(m_pActionStartSynergy); - m_pMenuFile->addAction(m_pActionStopSynergy); - m_pMenuFile->addSeparator(); - m_pMenuFile->addAction(m_pActivate); - m_pMenuFile->addSeparator(); - m_pMenuFile->addAction(m_pActionSave); - m_pMenuFile->addSeparator(); - m_pMenuFile->addAction(m_pActionQuit); - m_pMenuEdit->addAction(m_pActionSettings); - m_pMenuWindow->addAction(m_pActionMinimize); - m_pMenuWindow->addAction(m_pActionRestore); - m_pMenuHelp->addAction(m_pActionAbout); + m_pMenuFile->addAction(m_pActionStartSynergy); + m_pMenuFile->addAction(m_pActionStopSynergy); + m_pMenuFile->addSeparator(); + m_pMenuFile->addAction(m_pActivate); + m_pMenuFile->addSeparator(); + m_pMenuFile->addAction(m_pActionSave); + m_pMenuFile->addSeparator(); + m_pMenuFile->addAction(m_pActionQuit); + m_pMenuEdit->addAction(m_pActionSettings); + m_pMenuWindow->addAction(m_pActionMinimize); + m_pMenuWindow->addAction(m_pActionRestore); + m_pMenuHelp->addAction(m_pActionAbout); - setMenuBar(m_pMenuBar); + setMenuBar(m_pMenuBar); } void MainWindow::loadSettings() { - // the next two must come BEFORE loading groupServerChecked and groupClientChecked or - // disabling and/or enabling the right widgets won't automatically work - m_pRadioExternalConfig->setChecked(settings().value("useExternalConfig", false).toBool()); - m_pRadioInternalConfig->setChecked(settings().value("useInternalConfig", true).toBool()); + // the next two must come BEFORE loading groupServerChecked and groupClientChecked or + // disabling and/or enabling the right widgets won't automatically work + m_pRadioExternalConfig->setChecked(settings().value("useExternalConfig", false).toBool()); + m_pRadioInternalConfig->setChecked(settings().value("useInternalConfig", true).toBool()); - m_pGroupServer->setChecked(settings().value("groupServerChecked", false).toBool()); - m_pLineEditConfigFile->setText(settings().value("configFile", QDir::homePath() + "/" + synergyConfigName).toString()); - m_pGroupClient->setChecked(settings().value("groupClientChecked", true).toBool()); - m_pLineEditHostname->setText(settings().value("serverHostname").toString()); + m_pGroupServer->setChecked(settings().value("groupServerChecked", false).toBool()); + m_pLineEditConfigFile->setText(settings().value("configFile", QDir::homePath() + "/" + synergyConfigName).toString()); + m_pGroupClient->setChecked(settings().value("groupClientChecked", true).toBool()); + m_pLineEditHostname->setText(settings().value("serverHostname").toString()); } void MainWindow::initConnections() { - connect(m_pActionMinimize, SIGNAL(triggered()), this, SLOT(hide())); - connect(m_pActionRestore, SIGNAL(triggered()), this, SLOT(showNormal())); - connect(m_pActionStartSynergy, SIGNAL(triggered()), this, SLOT(startSynergy())); - connect(m_pActionStopSynergy, SIGNAL(triggered()), this, SLOT(stopSynergy())); - connect(m_pActionQuit, SIGNAL(triggered()), qApp, SLOT(quit())); - connect(&m_VersionChecker, SIGNAL(updateFound(const QString&)), this, SLOT(updateFound(const QString&))); + connect(m_pActionMinimize, SIGNAL(triggered()), this, SLOT(hide())); + connect(m_pActionRestore, SIGNAL(triggered()), this, SLOT(showNormal())); + connect(m_pActionStartSynergy, SIGNAL(triggered()), this, SLOT(startSynergy())); + connect(m_pActionStopSynergy, SIGNAL(triggered()), this, SLOT(stopSynergy())); + connect(m_pActionQuit, SIGNAL(triggered()), qApp, SLOT(quit())); + connect(&m_VersionChecker, SIGNAL(updateFound(const QString&)), this, SLOT(updateFound(const QString&))); } void MainWindow::saveSettings() { - // program settings - settings().setValue("groupServerChecked", m_pGroupServer->isChecked()); - settings().setValue("useExternalConfig", m_pRadioExternalConfig->isChecked()); - settings().setValue("configFile", m_pLineEditConfigFile->text()); - settings().setValue("useInternalConfig", m_pRadioInternalConfig->isChecked()); - settings().setValue("groupClientChecked", m_pGroupClient->isChecked()); - settings().setValue("serverHostname", m_pLineEditHostname->text()); + // program settings + settings().setValue("groupServerChecked", m_pGroupServer->isChecked()); + settings().setValue("useExternalConfig", m_pRadioExternalConfig->isChecked()); + settings().setValue("configFile", m_pLineEditConfigFile->text()); + settings().setValue("useInternalConfig", m_pRadioInternalConfig->isChecked()); + settings().setValue("groupClientChecked", m_pGroupClient->isChecked()); + settings().setValue("serverHostname", m_pLineEditHostname->text()); - settings().sync(); + settings().sync(); } void MainWindow::setIcon(qSynergyState state) { - QIcon icon; - icon.addFile(synergyIconFiles[state]); + QIcon icon; + icon.addFile(synergyIconFiles[state]); - if (m_pTrayIcon) - m_pTrayIcon->setIcon(icon); + if (m_pTrayIcon) + m_pTrayIcon->setIcon(icon); } void MainWindow::trayActivated(QSystemTrayIcon::ActivationReason reason) { #ifndef Q_OS_WIN - if (reason == QSystemTrayIcon::DoubleClick) - { - if (isVisible()) - { - hide(); - } - else - { - showNormal(); - activateWindow(); - } - } + if (reason == QSystemTrayIcon::DoubleClick) + { + if (isVisible()) + { + hide(); + } + else + { + showNormal(); + activateWindow(); + } + } #endif } void MainWindow::logOutput() { - if (m_pSynergy) - { - QString text(m_pSynergy->readAllStandardOutput()); - foreach(QString line, text.split(QRegExp("\r|\n|\r\n"))) - { - if (!line.isEmpty()) - { - appendLogRaw(line); - } - } - } + if (m_pSynergy) + { + QString text(m_pSynergy->readAllStandardOutput()); + foreach(QString line, text.split(QRegExp("\r|\n|\r\n"))) + { + if (!line.isEmpty()) + { + appendLogRaw(line); + } + } + } } void MainWindow::logError() { - if (m_pSynergy) - { - appendLogRaw(m_pSynergy->readAllStandardError()); - } + if (m_pSynergy) + { + appendLogRaw(m_pSynergy->readAllStandardError()); + } } void MainWindow::updateFound(const QString &version) { - m_pWidgetUpdate->show(); - m_pLabelUpdate->setText( - tr("

Your version of Synergy is out of date. " - "Version %1 is now available to " - "download.

") - .arg(version).arg(DOWNLOAD_URL)); + m_pWidgetUpdate->show(); + m_pLabelUpdate->setText( + tr("

Your version of Synergy is out of date. " + "Version %1 is now available to " + "download.

") + .arg(version).arg(DOWNLOAD_URL)); } void MainWindow::appendLogInfo(const QString& text) { - appendLogRaw(getTimeStamp() + " INFO: " + text); + appendLogRaw(getTimeStamp() + " INFO: " + text); } void MainWindow::appendLogDebug(const QString& text) { - if (appConfig().logLevel() >= 4) { - appendLogRaw(getTimeStamp() + " DEBUG: " + text); - } + if (appConfig().logLevel() >= 4) { + appendLogRaw(getTimeStamp() + " DEBUG: " + text); + } } void MainWindow::appendLogError(const QString& text) { - appendLogRaw(getTimeStamp() + " ERROR: " + text); + appendLogRaw(getTimeStamp() + " ERROR: " + text); } void MainWindow::appendLogRaw(const QString& text) { - foreach(QString line, text.split(QRegExp("\r|\n|\r\n"))) { - if (!line.isEmpty()) { - m_pLogOutput->append(line); - updateStateFromLogLine(line); - } - } + foreach(QString line, text.split(QRegExp("\r|\n|\r\n"))) { + if (!line.isEmpty()) { + m_pLogOutput->append(line); + updateStateFromLogLine(line); + } + } } void MainWindow::updateStateFromLogLine(const QString &line) { - checkConnected(line); - checkFingerprint(line); + checkConnected(line); + checkFingerprint(line); } void MainWindow::checkConnected(const QString& line) { - // TODO: implement ipc connection state messages to replace this hack. - if (line.contains("started server") || - line.contains("connected to server") || - line.contains("watchdog status: ok")) - { - setSynergyState(synergyConnected); + // TODO: implement ipc connection state messages to replace this hack. + if (line.contains("started server") || + line.contains("connected to server") || + line.contains("watchdog status: ok")) + { + setSynergyState(synergyConnected); - if (!appConfig().startedBefore() && isVisible()) { - QMessageBox::information( - this, "Synergy", - tr("Synergy is now connected. You can close the " - "config window and Synergy will remain connected in " - "the background.")); + if (!appConfig().startedBefore() && isVisible()) { + QMessageBox::information( + this, "Synergy", + tr("Synergy is now connected. You can close the " + "config window and Synergy will remain connected in " + "the background.")); - appConfig().setStartedBefore(true); - appConfig().saveSettings(); - } - } + appConfig().setStartedBefore(true); + appConfig().saveSettings(); + } + } } void MainWindow::checkFingerprint(const QString& line) { - QRegExp fingerprintRegex(".*server fingerprint: ([A-F0-9:]+)"); - if (!fingerprintRegex.exactMatch(line)) { - return; - } + QRegExp fingerprintRegex(".*server fingerprint: ([A-F0-9:]+)"); + if (!fingerprintRegex.exactMatch(line)) { + return; + } - QString fingerprint = fingerprintRegex.cap(1); - if (Fingerprint::trustedServers().isTrusted(fingerprint)) { - return; - } + QString fingerprint = fingerprintRegex.cap(1); + if (Fingerprint::trustedServers().isTrusted(fingerprint)) { + return; + } - static bool messageBoxAlreadyShown = false; + static bool messageBoxAlreadyShown = false; - if (!messageBoxAlreadyShown) { - stopSynergy(); + if (!messageBoxAlreadyShown) { + stopSynergy(); - messageBoxAlreadyShown = true; - QMessageBox::StandardButton fingerprintReply = - QMessageBox::information( - this, tr("Security question"), - tr("Do you trust this fingerprint?\n\n" - "%1\n\n" - "This is a server fingerprint. You should compare this " - "fingerprint to the one on your server's screen. If the " - "two don't match exactly, then it's probably not the server " - "you're expecting (it could be a malicious user).\n\n" - "To automatically trust this fingerprint for future " - "connections, click Yes. To reject this fingerprint and " - "disconnect from the server, click No.") - .arg(fingerprint), - QMessageBox::Yes | QMessageBox::No); + messageBoxAlreadyShown = true; + QMessageBox::StandardButton fingerprintReply = + QMessageBox::information( + this, tr("Security question"), + tr("Do you trust this fingerprint?\n\n" + "%1\n\n" + "This is a server fingerprint. You should compare this " + "fingerprint to the one on your server's screen. If the " + "two don't match exactly, then it's probably not the server " + "you're expecting (it could be a malicious user).\n\n" + "To automatically trust this fingerprint for future " + "connections, click Yes. To reject this fingerprint and " + "disconnect from the server, click No.") + .arg(fingerprint), + QMessageBox::Yes | QMessageBox::No); - if (fingerprintReply == QMessageBox::Yes) { - // restart core process after trusting fingerprint. - Fingerprint::trustedServers().trust(fingerprint); - startSynergy(); - } + if (fingerprintReply == QMessageBox::Yes) { + // restart core process after trusting fingerprint. + Fingerprint::trustedServers().trust(fingerprint); + startSynergy(); + } - messageBoxAlreadyShown = false; - } + messageBoxAlreadyShown = false; + } } bool MainWindow::autoHide() { - if ((appConfig().processMode() == Desktop) && - appConfig().getAutoHide()) { - hide(); - return true; - } + if ((appConfig().processMode() == Desktop) && + appConfig().getAutoHide()) { + hide(); + return true; + } - return false; + return false; } QString MainWindow::getTimeStamp() { - QDateTime current = QDateTime::currentDateTime(); - return '[' + current.toString(Qt::ISODate) + ']'; + QDateTime current = QDateTime::currentDateTime(); + return '[' + current.toString(Qt::ISODate) + ']'; } void MainWindow::restartSynergy() { - stopSynergy(); - startSynergy(); + stopSynergy(); + startSynergy(); } void MainWindow::proofreadInfo() { - setEdition(m_AppConfig->edition()); // Why is this here? + setEdition(m_AppConfig->edition()); // Why is this here? - int oldState = m_SynergyState; - m_SynergyState = synergyDisconnected; - setSynergyState((qSynergyState)oldState); + int oldState = m_SynergyState; + m_SynergyState = synergyDisconnected; + setSynergyState((qSynergyState)oldState); } void MainWindow::showEvent(QShowEvent* event) { - QMainWindow::showEvent(event); - emit windowShown(); + QMainWindow::showEvent(event); + emit windowShown(); } void MainWindow::clearLog() { - m_pLogOutput->clear(); + m_pLogOutput->clear(); } void MainWindow::startSynergy() { - bool desktopMode = appConfig().processMode() == Desktop; - bool serviceMode = appConfig().processMode() == Service; + bool desktopMode = appConfig().processMode() == Desktop; + bool serviceMode = appConfig().processMode() == Service; - appendLogDebug("starting process"); - m_ExpectedRunningState = kStarted; - setSynergyState(synergyConnecting); + appendLogDebug("starting process"); + m_ExpectedRunningState = kStarted; + setSynergyState(synergyConnecting); - QString app; - QStringList args; + QString app; + QStringList args; - args << "-f" << "--no-tray" << "--debug" << appConfig().logLevelText(); + args << "-f" << "--no-tray" << "--debug" << appConfig().logLevelText(); - args << "--name" << getScreenName(); + args << "--name" << getScreenName(); - if (desktopMode) - { - setSynergyProcess(new QProcess(this)); - } - else - { - // tell client/server to talk to daemon through ipc. - args << "--ipc"; + if (desktopMode) + { + setSynergyProcess(new QProcess(this)); + } + else + { + // tell client/server to talk to daemon through ipc. + args << "--ipc"; #if defined(Q_OS_WIN) - // tell the client/server to shut down when a ms windows desk - // is switched; this is because we may need to elevate or not - // based on which desk the user is in (login always needs - // elevation, where as default desk does not). - // Note that this is only enabled when synergy is set to elevate - // 'as needed' (e.g. on a UAC dialog popup) in order to prevent - // unnecessary restarts when synergy was started elevated or - // when it is not allowed to elevate. In these cases restarting - // the server is fruitless. - if (appConfig().elevateMode() == ElevateAsNeeded) { - args << "--stop-on-desk-switch"; - } + // tell the client/server to shut down when a ms windows desk + // is switched; this is because we may need to elevate or not + // based on which desk the user is in (login always needs + // elevation, where as default desk does not). + // Note that this is only enabled when synergy is set to elevate + // 'as needed' (e.g. on a UAC dialog popup) in order to prevent + // unnecessary restarts when synergy was started elevated or + // when it is not allowed to elevate. In these cases restarting + // the server is fruitless. + if (appConfig().elevateMode() == ElevateAsNeeded) { + args << "--stop-on-desk-switch"; + } #endif - } + } #ifndef Q_OS_LINUX - if (m_ServerConfig.enableDragAndDrop()) { - args << "--enable-drag-drop"; - } + if (m_ServerConfig.enableDragAndDrop()) { + args << "--enable-drag-drop"; + } #endif - if (m_AppConfig->getCryptoEnabled()) { - args << "--enable-crypto"; - } + if (m_AppConfig->getCryptoEnabled()) { + args << "--enable-crypto"; + } #if defined(Q_OS_WIN) - // on windows, the profile directory changes depending on the user that - // launched the process (e.g. when launched with elevation). setting the - // profile dir on launch ensures it uses the same profile dir is used - // no matter how its relaunched. - args << "--profile-dir" << getProfileRootForArg(); + // on windows, the profile directory changes depending on the user that + // launched the process (e.g. when launched with elevation). setting the + // profile dir on launch ensures it uses the same profile dir is used + // no matter how its relaunched. + args << "--profile-dir" << getProfileRootForArg(); #endif - if ((synergyType() == synergyClient && !clientArgs(args, app)) - || (synergyType() == synergyServer && !serverArgs(args, app))) - { - stopSynergy(); - return; - } + if ((synergyType() == synergyClient && !clientArgs(args, app)) + || (synergyType() == synergyServer && !serverArgs(args, app))) + { + stopSynergy(); + return; + } - if (desktopMode) - { - connect(synergyProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(synergyFinished(int, QProcess::ExitStatus))); - connect(synergyProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(logOutput())); - connect(synergyProcess(), SIGNAL(readyReadStandardError()), this, SLOT(logError())); - } + if (desktopMode) + { + connect(synergyProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(synergyFinished(int, QProcess::ExitStatus))); + connect(synergyProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(logOutput())); + connect(synergyProcess(), SIGNAL(readyReadStandardError()), this, SLOT(logError())); + } - // put a space between last log output and new instance. - if (!m_pLogOutput->toPlainText().isEmpty()) - appendLogRaw(""); + // put a space between last log output and new instance. + if (!m_pLogOutput->toPlainText().isEmpty()) + appendLogRaw(""); - appendLogInfo("starting " + QString(synergyType() == synergyServer ? "server" : "client")); + appendLogInfo("starting " + QString(synergyType() == synergyServer ? "server" : "client")); - qDebug() << args; + qDebug() << args; - // show command if debug log level... - if (appConfig().logLevel() >= 4) { - appendLogInfo(QString("command: %1 %2").arg(app, args.join(" "))); - } + // show command if debug log level... + if (appConfig().logLevel() >= 4) { + appendLogInfo(QString("command: %1 %2").arg(app, args.join(" "))); + } - appendLogInfo("config file: " + configFilename()); - appendLogInfo("log level: " + appConfig().logLevelText()); + appendLogInfo("config file: " + configFilename()); + appendLogInfo("log level: " + appConfig().logLevelText()); - if (appConfig().logToFile()) - appendLogInfo("log file: " + appConfig().logFilename()); + if (appConfig().logToFile()) + appendLogInfo("log file: " + appConfig().logFilename()); - if (desktopMode) - { - synergyProcess()->start(app, args); - if (!synergyProcess()->waitForStarted()) - { - show(); - QMessageBox::warning(this, tr("Program can not be started"), QString(tr("The executable

%1

could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.").arg(app))); - return; - } - } + if (desktopMode) + { + synergyProcess()->start(app, args); + if (!synergyProcess()->waitForStarted()) + { + show(); + QMessageBox::warning(this, tr("Program can not be started"), QString(tr("The executable

%1

could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.").arg(app))); + return; + } + } - if (serviceMode) - { - QString command(app + " " + args.join(" ")); - m_IpcClient.sendCommand(command, appConfig().elevateMode()); - } + if (serviceMode) + { + QString command(app + " " + args.join(" ")); + m_IpcClient.sendCommand(command, appConfig().elevateMode()); + } } void MainWindow::sslToggled (bool enabled) { - if (enabled) { - m_pSslCertificate = new SslCertificate(this); - m_pSslCertificate->generateCertificate(); - } - updateLocalFingerprint(); + if (enabled) { + m_pSslCertificate = new SslCertificate(this); + m_pSslCertificate->generateCertificate(); + } + updateLocalFingerprint(); } bool MainWindow::clientArgs(QStringList& args, QString& app) { - app = appPath(appConfig().synergycName()); + app = appPath(appConfig().synergycName()); - if (!QFile::exists(app)) - { - show(); - QMessageBox::warning(this, tr("Synergy client not found"), - tr("The executable for the synergy client does not exist.")); - return false; - } + if (!QFile::exists(app)) + { + show(); + QMessageBox::warning(this, tr("Synergy client not found"), + tr("The executable for the synergy client does not exist.")); + return false; + } #if defined(Q_OS_WIN) - // wrap in quotes so a malicious user can't start \Program.exe as admin. - app = QString("\"%1\"").arg(app); + // wrap in quotes so a malicious user can't start \Program.exe as admin. + app = QString("\"%1\"").arg(app); #endif - if (appConfig().logToFile()) - { - appConfig().persistLogDir(); - args << "--log" << appConfig().logFilenameCmd(); - } + if (appConfig().logToFile()) + { + appConfig().persistLogDir(); + args << "--log" << appConfig().logFilenameCmd(); + } - // check auto config first, if it is disabled or no server detected, - // use line edit host name if it is not empty - if (m_pCheckBoxAutoConfig->isChecked()) { - if (m_pComboServerList->count() != 0) { - QString serverIp = m_pComboServerList->currentText(); - args << serverIp + ":" + QString::number(appConfig().port()); - return true; - } - } + // check auto config first, if it is disabled or no server detected, + // use line edit host name if it is not empty + if (m_pCheckBoxAutoConfig->isChecked()) { + if (m_pComboServerList->count() != 0) { + QString serverIp = m_pComboServerList->currentText(); + args << serverIp + ":" + QString::number(appConfig().port()); + return true; + } + } - if (m_pLineEditHostname->text().isEmpty()) { - show(); - if (!m_SuppressEmptyServerWarning) { - QMessageBox::warning(this, tr("Hostname is empty"), - tr("Please fill in a hostname for the synergy client to connect to.")); - } - return false; - } + if (m_pLineEditHostname->text().isEmpty()) { + show(); + if (!m_SuppressEmptyServerWarning) { + QMessageBox::warning(this, tr("Hostname is empty"), + tr("Please fill in a hostname for the synergy client to connect to.")); + } + return false; + } - args << m_pLineEditHostname->text() + ":" + QString::number(appConfig().port()); + args << m_pLineEditHostname->text() + ":" + QString::number(appConfig().port()); - return true; + return true; } QString MainWindow::configFilename() { - QString filename; - if (m_pRadioInternalConfig->isChecked()) - { - // TODO: no need to use a temporary file, since we need it to - // be permenant (since it'll be used for Windows services, etc). - m_pTempConfigFile = new QTemporaryFile(); - if (!m_pTempConfigFile->open()) - { - QMessageBox::critical(this, tr("Cannot write configuration file"), tr("The temporary configuration file required to start synergy can not be written.")); - return ""; - } + QString filename; + if (m_pRadioInternalConfig->isChecked()) + { + // TODO: no need to use a temporary file, since we need it to + // be permenant (since it'll be used for Windows services, etc). + m_pTempConfigFile = new QTemporaryFile(); + if (!m_pTempConfigFile->open()) + { + QMessageBox::critical(this, tr("Cannot write configuration file"), tr("The temporary configuration file required to start synergy can not be written.")); + return ""; + } - serverConfig().save(*m_pTempConfigFile); - filename = m_pTempConfigFile->fileName(); + serverConfig().save(*m_pTempConfigFile); + filename = m_pTempConfigFile->fileName(); - m_pTempConfigFile->close(); - } - else - { - if (!QFile::exists(m_pLineEditConfigFile->text())) - { - if (QMessageBox::warning(this, tr("Configuration filename invalid"), - tr("You have not filled in a valid configuration file for the synergy server. " - "Do you want to browse for the configuration file now?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes - || !on_m_pButtonBrowseConfigFile_clicked()) - return ""; - } + m_pTempConfigFile->close(); + } + else + { + if (!QFile::exists(m_pLineEditConfigFile->text())) + { + if (QMessageBox::warning(this, tr("Configuration filename invalid"), + tr("You have not filled in a valid configuration file for the synergy server. " + "Do you want to browse for the configuration file now?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes + || !on_m_pButtonBrowseConfigFile_clicked()) + return ""; + } - filename = m_pLineEditConfigFile->text(); - } - return filename; + filename = m_pLineEditConfigFile->text(); + } + return filename; } QString MainWindow::address() { - QString i = appConfig().interface(); - return (!i.isEmpty() ? i : "") + ":" + QString::number(appConfig().port()); + QString i = appConfig().interface(); + return (!i.isEmpty() ? i : "") + ":" + QString::number(appConfig().port()); } QString MainWindow::appPath(const QString& name) { - return appConfig().synergyProgramDir() + name; + return appConfig().synergyProgramDir() + name; } bool MainWindow::serverArgs(QStringList& args, QString& app) { - app = appPath(appConfig().synergysName()); + app = appPath(appConfig().synergysName()); - if (!QFile::exists(app)) - { - QMessageBox::warning(this, tr("Synergy server not found"), - tr("The executable for the synergy server does not exist.")); - return false; - } + if (!QFile::exists(app)) + { + QMessageBox::warning(this, tr("Synergy server not found"), + tr("The executable for the synergy server does not exist.")); + return false; + } #if defined(Q_OS_WIN) - // wrap in quotes so a malicious user can't start \Program.exe as admin. - app = QString("\"%1\"").arg(app); + // wrap in quotes so a malicious user can't start \Program.exe as admin. + app = QString("\"%1\"").arg(app); #endif - if (appConfig().logToFile()) - { - appConfig().persistLogDir(); + if (appConfig().logToFile()) + { + appConfig().persistLogDir(); - args << "--log" << appConfig().logFilenameCmd(); - } + args << "--log" << appConfig().logFilenameCmd(); + } - QString configFilename = this->configFilename(); + QString configFilename = this->configFilename(); #if defined(Q_OS_WIN) - // wrap in quotes in case username contains spaces. - configFilename = QString("\"%1\"").arg(configFilename); + // wrap in quotes in case username contains spaces. + configFilename = QString("\"%1\"").arg(configFilename); #endif - args << "-c" << configFilename << "--address" << address(); + args << "-c" << configFilename << "--address" << address(); #if defined(Q_OS_WIN) - // pass in physical resolution and primary screen center - // TODO: get this information in the core binary even when - // high DPI is used - int height = QApplication::desktop()->height(); - int width = QApplication::desktop()->width(); + // pass in physical resolution and primary screen center + // TODO: get this information in the core binary even when + // high DPI is used + int height = QApplication::desktop()->height(); + int width = QApplication::desktop()->width(); - QRect rec = QApplication::desktop()->screenGeometry(); - int heightCenter = rec.height() / 2; - int widthCenter = rec.width() / 2; + QRect rec = QApplication::desktop()->screenGeometry(); + int heightCenter = rec.height() / 2; + int widthCenter = rec.width() / 2; - appendLogDebug(tr("screen resolution: %1 %2 primary screen center: %3 %4") - .arg(width).arg(height).arg(widthCenter).arg(heightCenter)); + appendLogDebug(tr("screen resolution: %1 %2 primary screen center: %3 %4") + .arg(width).arg(height).arg(widthCenter).arg(heightCenter)); - args << "--res-w" << QString::number(width); - args << "--res-h" << QString::number(height); - args << "--prm-wc" << QString::number(widthCenter); - args << "--prm-hc" << QString::number(heightCenter); + args << "--res-w" << QString::number(width); + args << "--res-h" << QString::number(height); + args << "--prm-wc" << QString::number(widthCenter); + args << "--prm-hc" << QString::number(heightCenter); #endif - return true; + return true; } void MainWindow::stopSynergy() { - appendLogDebug("stopping process"); + appendLogDebug("stopping process"); - m_ExpectedRunningState = kStopped; + m_ExpectedRunningState = kStopped; - if (appConfig().processMode() == Service) - { - stopService(); - } - else if (appConfig().processMode() == Desktop) - { - stopDesktop(); - } + if (appConfig().processMode() == Service) + { + stopService(); + } + else if (appConfig().processMode() == Desktop) + { + stopDesktop(); + } - setSynergyState(synergyDisconnected); + setSynergyState(synergyDisconnected); - // HACK: deleting the object deletes the physical file, which is - // bad, since it could be in use by the Windows service! - //delete m_pTempConfigFile; - m_pTempConfigFile = NULL; + // HACK: deleting the object deletes the physical file, which is + // bad, since it could be in use by the Windows service! + //delete m_pTempConfigFile; + m_pTempConfigFile = NULL; - // reset so that new connects cause auto-hide. - m_AlreadyHidden = false; + // reset so that new connects cause auto-hide. + m_AlreadyHidden = false; } void MainWindow::stopService() { - // send empty command to stop service from laucning anything. - m_IpcClient.sendCommand("", appConfig().elevateMode()); + // send empty command to stop service from laucning anything. + m_IpcClient.sendCommand("", appConfig().elevateMode()); } void MainWindow::stopDesktop() { - QMutexLocker locker(&m_StopDesktopMutex); - if (!synergyProcess()) { - return; - } + QMutexLocker locker(&m_StopDesktopMutex); + if (!synergyProcess()) { + return; + } - appendLogInfo("stopping synergy desktop process"); + appendLogInfo("stopping synergy desktop process"); - if (synergyProcess()->isOpen()) { - synergyProcess()->close(); - } + if (synergyProcess()->isOpen()) { + synergyProcess()->close(); + } - delete synergyProcess(); - setSynergyProcess(NULL); + delete synergyProcess(); + setSynergyProcess(NULL); } void MainWindow::synergyFinished(int exitCode, QProcess::ExitStatus) { - if (exitCode == 0) { - appendLogInfo(QString("process exited normally")); - } - else { - appendLogError(QString("process exited with error code: %1").arg(exitCode)); - } + if (exitCode == 0) { + appendLogInfo(QString("process exited normally")); + } + else { + appendLogError(QString("process exited with error code: %1").arg(exitCode)); + } - if (m_ExpectedRunningState == kStarted) { - QTimer::singleShot(1000, this, SLOT(startSynergy())); - appendLogInfo(QString("detected process not running, auto restarting")); - } - else { - setSynergyState(synergyDisconnected); - } + if (m_ExpectedRunningState == kStarted) { + QTimer::singleShot(1000, this, SLOT(startSynergy())); + appendLogInfo(QString("detected process not running, auto restarting")); + } + else { + setSynergyState(synergyDisconnected); + } } void MainWindow::setSynergyState(qSynergyState state) { - if (synergyState() == state) - return; + if (synergyState() == state) + return; - if (state == synergyConnected || state == synergyConnecting) - { - disconnect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); - connect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStopSynergy, SLOT(trigger())); - m_pButtonToggleStart->setText(tr("&Stop")); - m_pButtonApply->setEnabled(true); - } - else if (state == synergyDisconnected) - { - disconnect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStopSynergy, SLOT(trigger())); - connect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); - m_pButtonToggleStart->setText(tr("&Start")); - m_pButtonApply->setEnabled(false); - } + if (state == synergyConnected || state == synergyConnecting) + { + disconnect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); + connect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStopSynergy, SLOT(trigger())); + m_pButtonToggleStart->setText(tr("&Stop")); + m_pButtonApply->setEnabled(true); + } + else if (state == synergyDisconnected) + { + disconnect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStopSynergy, SLOT(trigger())); + connect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); + m_pButtonToggleStart->setText(tr("&Start")); + m_pButtonApply->setEnabled(false); + } - bool connected = false; - if (state == synergyConnected || state == synergyTransfering) { - connected = true; - } + bool connected = false; + if (state == synergyConnected || state == synergyTransfering) { + connected = true; + } - m_pActionStartSynergy->setEnabled(!connected); - m_pActionStopSynergy->setEnabled(connected); + m_pActionStartSynergy->setEnabled(!connected); + m_pActionStopSynergy->setEnabled(connected); - switch (state) - { - case synergyConnected: { - if (m_AppConfig->getCryptoEnabled()) { - m_pLabelPadlock->show(); - } - else { - m_pLabelPadlock->hide(); - } + switch (state) + { + case synergyConnected: { + if (m_AppConfig->getCryptoEnabled()) { + m_pLabelPadlock->show(); + } + else { + m_pLabelPadlock->hide(); + } - setStatus(tr("Synergy is running.")); + setStatus(tr("Synergy is running.")); - break; - } - case synergyConnecting: - m_pLabelPadlock->hide(); - setStatus(tr("Synergy is starting.")); - break; - case synergyDisconnected: - m_pLabelPadlock->hide(); - setStatus(tr("Synergy is not running.")); - break; - case synergyTransfering: - break; - } + break; + } + case synergyConnecting: + m_pLabelPadlock->hide(); + setStatus(tr("Synergy is starting.")); + break; + case synergyDisconnected: + m_pLabelPadlock->hide(); + setStatus(tr("Synergy is not running.")); + break; + case synergyTransfering: + break; + } - setIcon(state); + setIcon(state); - m_SynergyState = state; + m_SynergyState = state; } void MainWindow::setVisible(bool visible) { - QMainWindow::setVisible(visible); - m_pActionMinimize->setEnabled(visible); - m_pActionRestore->setEnabled(!visible); + QMainWindow::setVisible(visible); + m_pActionMinimize->setEnabled(visible); + m_pActionRestore->setEnabled(!visible); #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 // lion - // dock hide only supported on lion :( - ProcessSerialNumber psn = { 0, kCurrentProcess }; - GetCurrentProcess(&psn); - if (visible) - TransformProcessType(&psn, kProcessTransformToForegroundApplication); - else - TransformProcessType(&psn, kProcessTransformToBackgroundApplication); + // dock hide only supported on lion :( + ProcessSerialNumber psn = { 0, kCurrentProcess }; + GetCurrentProcess(&psn); + if (visible) + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + else + TransformProcessType(&psn, kProcessTransformToBackgroundApplication); #endif } QString MainWindow::getIPAddresses() { - QList addresses = QNetworkInterface::allAddresses(); + QList addresses = QNetworkInterface::allAddresses(); - bool hinted = false; - QString result; - for (int i = 0; i < addresses.size(); i++) { - if (addresses[i].protocol() == QAbstractSocket::IPv4Protocol && - addresses[i] != QHostAddress(QHostAddress::LocalHost)) { + bool hinted = false; + QString result; + for (int i = 0; i < addresses.size(); i++) { + if (addresses[i].protocol() == QAbstractSocket::IPv4Protocol && + addresses[i] != QHostAddress(QHostAddress::LocalHost)) { - QString address = addresses[i].toString(); - QString format = "%1, "; + QString address = addresses[i].toString(); + QString format = "%1, "; - // usually 192.168.x.x is a useful ip for the user, so indicate - // this by making it bold. - if (!hinted && address.startsWith("192.168")) { - hinted = true; - format = "%1, "; - } + // usually 192.168.x.x is a useful ip for the user, so indicate + // this by making it bold. + if (!hinted && address.startsWith("192.168")) { + hinted = true; + format = "%1, "; + } - result += format.arg(address); - } - } + result += format.arg(address); + } + } - if (result == "") { - return tr("Unknown"); - } + if (result == "") { + return tr("Unknown"); + } - // remove trailing comma. - result.chop(2); + // remove trailing comma. + result.chop(2); - return result; + return result; } QString MainWindow::getScreenName() { - if (appConfig().screenName() == "") { - return QHostInfo::localHostName(); - } - else { - return appConfig().screenName(); - } + if (appConfig().screenName() == "") { + return QHostInfo::localHostName(); + } + else { + return appConfig().screenName(); + } } void MainWindow::changeEvent(QEvent* event) { - if (event != 0) - { - switch (event->type()) - { - case QEvent::LanguageChange: - { - retranslateUi(this); - retranslateMenuBar(); + if (event != 0) + { + switch (event->type()) + { + case QEvent::LanguageChange: + { + retranslateUi(this); + retranslateMenuBar(); - proofreadInfo(); + proofreadInfo(); - break; - } - default: - QMainWindow::changeEvent(event); - } - } + break; + } + default: + QMainWindow::changeEvent(event); + } + } } void MainWindow::updateZeroconfService() { - QMutexLocker locker(&m_UpdateZeroconfMutex); + QMutexLocker locker(&m_UpdateZeroconfMutex); - if (isBonjourRunning()) { - if (!m_AppConfig->wizardShouldRun()) { - if (m_pZeroconfService) { - delete m_pZeroconfService; - m_pZeroconfService = NULL; - } + if (isBonjourRunning()) { + if (!m_AppConfig->wizardShouldRun()) { + if (m_pZeroconfService) { + delete m_pZeroconfService; + m_pZeroconfService = NULL; + } - if (m_AppConfig->autoConfig() || synergyType() == synergyServer) { - m_pZeroconfService = new ZeroconfService(this); - } - } - } + if (m_AppConfig->autoConfig() || synergyType() == synergyServer) { + m_pZeroconfService = new ZeroconfService(this); + } + } + } } void MainWindow::serverDetected(const QString name) { - if (m_pComboServerList->findText(name) == -1) { - // Note: the first added item triggers startSynergy - m_pComboServerList->addItem(name); - } + if (m_pComboServerList->findText(name) == -1) { + // Note: the first added item triggers startSynergy + m_pComboServerList->addItem(name); + } - if (m_pComboServerList->count() > 1) { - m_pComboServerList->show(); - } + if (m_pComboServerList->count() > 1) { + m_pComboServerList->show(); + } } void MainWindow::setEdition(Edition edition) { - setWindowTitle(getEditionName(edition)); - if (m_AppConfig->getCryptoEnabled()) { - m_pSslCertificate = new SslCertificate(this); - m_pSslCertificate->generateCertificate(); - } - updateLocalFingerprint(); - saveSettings(); + setWindowTitle(getEditionName(edition)); + if (m_AppConfig->getCryptoEnabled()) { + m_pSslCertificate = new SslCertificate(this); + m_pSslCertificate->generateCertificate(); + } + updateLocalFingerprint(); + saveSettings(); } -void MainWindow::beginTrial() +void MainWindow::beginTrial(bool isExpiring) { - this->m_trialWidget->show(); + if (isExpiring) { + this->m_trialWidget->show(); + } } -void MainWindow::endTrial() +void MainWindow::endTrial(bool isExpired) { - this->m_trialWidget->hide(); + if (!isExpired) { + this->m_trialWidget->hide(); + } } void MainWindow::updateLocalFingerprint() { - if (m_AppConfig->getCryptoEnabled() && Fingerprint::local().fileExists()) { - m_pLabelFingerprint->setVisible(true); - m_pLabelLocalFingerprint->setVisible(true); - m_pLabelLocalFingerprint->setText(Fingerprint::local().readFirst()); - } - else { - m_pLabelFingerprint->setVisible(false); - m_pLabelLocalFingerprint->setVisible(false); - } + if (m_AppConfig->getCryptoEnabled() && Fingerprint::local().fileExists()) { + m_pLabelFingerprint->setVisible(true); + m_pLabelLocalFingerprint->setVisible(true); + m_pLabelLocalFingerprint->setText(Fingerprint::local().readFirst()); + } + else { + m_pLabelFingerprint->setVisible(false); + m_pLabelLocalFingerprint->setVisible(false); + } } SubscriptionManager& MainWindow::subscriptionManager() const { - return *m_SubscriptionManager; + return *m_SubscriptionManager; } void MainWindow::on_m_pGroupClient_toggled(bool on) { - m_pGroupServer->setChecked(!on); - if (on) { - updateZeroconfService(); - } + m_pGroupServer->setChecked(!on); + if (on) { + updateZeroconfService(); + } } void MainWindow::on_m_pGroupServer_toggled(bool on) { - m_pGroupClient->setChecked(!on); - if (on) { - updateZeroconfService(); - } + m_pGroupClient->setChecked(!on); + if (on) { + updateZeroconfService(); + } } bool MainWindow::on_m_pButtonBrowseConfigFile_clicked() { - QString fileName = QFileDialog::getOpenFileName(this, tr("Browse for a synergys config file"), QString(), synergyConfigFilter); + QString fileName = QFileDialog::getOpenFileName(this, tr("Browse for a synergys config file"), QString(), synergyConfigFilter); - if (!fileName.isEmpty()) - { - m_pLineEditConfigFile->setText(fileName); - return true; - } + if (!fileName.isEmpty()) + { + m_pLineEditConfigFile->setText(fileName); + return true; + } - return false; + return false; } bool MainWindow::on_m_pActionSave_triggered() { - QString fileName = QFileDialog::getSaveFileName(this, tr("Save configuration as...")); + QString fileName = QFileDialog::getSaveFileName(this, tr("Save configuration as...")); - if (!fileName.isEmpty() && !serverConfig().save(fileName)) - { - QMessageBox::warning(this, tr("Save failed"), tr("Could not save configuration to file.")); - return true; - } + if (!fileName.isEmpty() && !serverConfig().save(fileName)) + { + QMessageBox::warning(this, tr("Save failed"), tr("Could not save configuration to file.")); + return true; + } - return false; + return false; } void MainWindow::on_m_pActionAbout_triggered() { - AboutDialog dlg(this, appPath(appConfig().synergycName())); - dlg.exec(); + AboutDialog dlg(this, appPath(appConfig().synergycName())); + dlg.exec(); } void MainWindow::on_m_pActionSettings_triggered() { - ProcessMode lastProcessMode = appConfig().processMode(); + ProcessMode lastProcessMode = appConfig().processMode(); - SettingsDialog dlg(this, appConfig()); - dlg.exec(); + SettingsDialog dlg(this, appConfig()); + dlg.exec(); - if (lastProcessMode != appConfig().processMode()) - { - onModeChanged(true, true); - } + if (lastProcessMode != appConfig().processMode()) + { + onModeChanged(true, true); + } } void MainWindow::autoAddScreen(const QString name) { - if (!m_ServerConfig.ignoreAutoConfigClient()) { - int r = m_ServerConfig.autoAddScreen(name); - if (r != kAutoAddScreenOk) { - switch (r) { - case kAutoAddScreenManualServer: - showConfigureServer( - tr("Please add the server (%1) to the grid.") - .arg(appConfig().screenName())); - break; + if (!m_ServerConfig.ignoreAutoConfigClient()) { + int r = m_ServerConfig.autoAddScreen(name); + if (r != kAutoAddScreenOk) { + switch (r) { + case kAutoAddScreenManualServer: + showConfigureServer( + tr("Please add the server (%1) to the grid.") + .arg(appConfig().screenName())); + break; - case kAutoAddScreenManualClient: - showConfigureServer( - tr("Please drag the new client screen (%1) " - "to the desired position on the grid.") - .arg(name)); - break; - } - } - else { - restartSynergy(); - } - } + case kAutoAddScreenManualClient: + showConfigureServer( + tr("Please drag the new client screen (%1) " + "to the desired position on the grid.") + .arg(name)); + break; + } + } + else { + restartSynergy(); + } + } } void MainWindow::showConfigureServer(const QString& message) { - ServerConfigDialog dlg(this, serverConfig(), appConfig().screenName()); - dlg.message(message); - dlg.exec(); + ServerConfigDialog dlg(this, serverConfig(), appConfig().screenName()); + dlg.message(message); + dlg.exec(); } void MainWindow::on_m_pButtonConfigureServer_clicked() { - showConfigureServer(); + showConfigureServer(); } void MainWindow::on_m_pActivate_triggered() { - ActivationDialog activationDialog(this, appConfig(), subscriptionManager()); - activationDialog.exec(); + ActivationDialog activationDialog(this, appConfig(), subscriptionManager()); + activationDialog.exec(); } void MainWindow::on_m_pButtonApply_clicked() { - restartSynergy(); + restartSynergy(); } #if defined(Q_OS_WIN) bool MainWindow::isServiceRunning(QString name) { - SC_HANDLE hSCManager; - hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); - if (hSCManager == NULL) { - appendLogError("failed to open a service controller manager, error: " + - GetLastError()); - return false; - } + SC_HANDLE hSCManager; + hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + if (hSCManager == NULL) { + appendLogError("failed to open a service controller manager, error: " + + GetLastError()); + return false; + } - SC_HANDLE hService; - int length = name.length(); - wchar_t* array = new wchar_t[length + 1]; - name.toWCharArray(array); - array[length] = '\0'; + SC_HANDLE hService; + int length = name.length(); + wchar_t* array = new wchar_t[length + 1]; + name.toWCharArray(array); + array[length] = '\0'; - hService = OpenService(hSCManager, array, SERVICE_QUERY_STATUS); + hService = OpenService(hSCManager, array, SERVICE_QUERY_STATUS); - delete[] array; + delete[] array; - if (hService == NULL) { - appendLogDebug("failed to open service: " + name); - return false; - } + if (hService == NULL) { + appendLogDebug("failed to open service: " + name); + return false; + } - SERVICE_STATUS status; - if (QueryServiceStatus(hService, &status)) { - if (status.dwCurrentState == SERVICE_RUNNING) { - return true; - } - } + SERVICE_STATUS status; + if (QueryServiceStatus(hService, &status)) { + if (status.dwCurrentState == SERVICE_RUNNING) { + return true; + } + } #else bool MainWindow::isServiceRunning() { #endif - return false; + return false; } bool MainWindow::isBonjourRunning() { - bool result = false; + bool result = false; #if defined(Q_OS_WIN) - result = isServiceRunning("Bonjour Service"); + result = isServiceRunning("Bonjour Service"); #else - result = true; + result = true; #endif - return result; + return result; } void MainWindow::downloadBonjour() { #if defined(Q_OS_WIN) - QUrl url; - int arch = getProcessorArch(); - if (arch == kProcessorArchWin32) { - url.setUrl(bonjourBaseUrl + bonjourFilename32); - appendLogInfo("downloading 32-bit Bonjour"); - } - else if (arch == kProcessorArchWin64) { - url.setUrl(bonjourBaseUrl + bonjourFilename64); - appendLogInfo("downloading 64-bit Bonjour"); - } - else { - QMessageBox::critical( - this, tr("Synergy"), - tr("Failed to detect system architecture.")); - return; - } + QUrl url; + int arch = getProcessorArch(); + if (arch == kProcessorArchWin32) { + url.setUrl(bonjourBaseUrl + bonjourFilename32); + appendLogInfo("downloading 32-bit Bonjour"); + } + else if (arch == kProcessorArchWin64) { + url.setUrl(bonjourBaseUrl + bonjourFilename64); + appendLogInfo("downloading 64-bit Bonjour"); + } + else { + QMessageBox::critical( + this, tr("Synergy"), + tr("Failed to detect system architecture.")); + return; + } - if (m_pDataDownloader == NULL) { - m_pDataDownloader = new DataDownloader(this); - connect(m_pDataDownloader, SIGNAL(isComplete()), SLOT(installBonjour())); - } + if (m_pDataDownloader == NULL) { + m_pDataDownloader = new DataDownloader(this); + connect(m_pDataDownloader, SIGNAL(isComplete()), SLOT(installBonjour())); + } - m_pDataDownloader->download(url); + m_pDataDownloader->download(url); - if (m_DownloadMessageBox == NULL) { - m_DownloadMessageBox = new QMessageBox(this); - m_DownloadMessageBox->setWindowTitle("Synergy"); - m_DownloadMessageBox->setIcon(QMessageBox::Information); - m_DownloadMessageBox->setText("Installing Bonjour, please wait..."); - m_DownloadMessageBox->setStandardButtons(0); - m_pCancelButton = m_DownloadMessageBox->addButton( - tr("Cancel"), QMessageBox::RejectRole); - } + if (m_DownloadMessageBox == NULL) { + m_DownloadMessageBox = new QMessageBox(this); + m_DownloadMessageBox->setWindowTitle("Synergy"); + m_DownloadMessageBox->setIcon(QMessageBox::Information); + m_DownloadMessageBox->setText("Installing Bonjour, please wait..."); + m_DownloadMessageBox->setStandardButtons(0); + m_pCancelButton = m_DownloadMessageBox->addButton( + tr("Cancel"), QMessageBox::RejectRole); + } - m_DownloadMessageBox->exec(); + m_DownloadMessageBox->exec(); - if (m_DownloadMessageBox->clickedButton() == m_pCancelButton) { - m_pDataDownloader->cancel(); - } + if (m_DownloadMessageBox->clickedButton() == m_pCancelButton) { + m_pDataDownloader->cancel(); + } #endif } void MainWindow::installBonjour() { #if defined(Q_OS_WIN) - QString tempLocation = QDesktopServices::storageLocation( - QDesktopServices::TempLocation); - QString filename = tempLocation; - filename.append("\\").append(bonjourTargetFilename); - QFile file(filename); - if (!file.open(QIODevice::WriteOnly)) { - m_DownloadMessageBox->hide(); + QString tempLocation = QDesktopServices::storageLocation( + QDesktopServices::TempLocation); + QString filename = tempLocation; + filename.append("\\").append(bonjourTargetFilename); + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) { + m_DownloadMessageBox->hide(); - QMessageBox::warning( - this, "Synergy", - tr("Failed to download Bonjour installer to location: %1") - .arg(tempLocation)); - return; - } + QMessageBox::warning( + this, "Synergy", + tr("Failed to download Bonjour installer to location: %1") + .arg(tempLocation)); + return; + } - file.write(m_pDataDownloader->data()); - file.close(); + file.write(m_pDataDownloader->data()); + file.close(); - QStringList arguments; - arguments.append("/i"); - QString winFilename = QDir::toNativeSeparators(filename); - arguments.append(winFilename); - arguments.append("/passive"); - if (m_BonjourInstall == NULL) { - m_BonjourInstall = new CommandProcess("msiexec", arguments); - } + QStringList arguments; + arguments.append("/i"); + QString winFilename = QDir::toNativeSeparators(filename); + arguments.append(winFilename); + arguments.append("/passive"); + if (m_BonjourInstall == NULL) { + m_BonjourInstall = new CommandProcess("msiexec", arguments); + } - QThread* thread = new QThread; - connect(m_BonjourInstall, SIGNAL(finished()), this, - SLOT(bonjourInstallFinished())); - connect(m_BonjourInstall, SIGNAL(finished()), thread, SLOT(quit())); - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + QThread* thread = new QThread; + connect(m_BonjourInstall, SIGNAL(finished()), this, + SLOT(bonjourInstallFinished())); + connect(m_BonjourInstall, SIGNAL(finished()), thread, SLOT(quit())); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - m_BonjourInstall->moveToThread(thread); - thread->start(); + m_BonjourInstall->moveToThread(thread); + thread->start(); - QMetaObject::invokeMethod(m_BonjourInstall, "run", Qt::QueuedConnection); + QMetaObject::invokeMethod(m_BonjourInstall, "run", Qt::QueuedConnection); - m_DownloadMessageBox->hide(); + m_DownloadMessageBox->hide(); #endif } void MainWindow::promptAutoConfig() { - if (!isBonjourRunning()) { - int r = QMessageBox::question( - this, tr("Synergy"), - tr("Do you want to enable auto config and install Bonjour?\n\n" - "This feature helps you establish the connection."), - QMessageBox::Yes | QMessageBox::No); + if (!isBonjourRunning()) { + int r = QMessageBox::question( + this, tr("Synergy"), + tr("Do you want to enable auto config and install Bonjour?\n\n" + "This feature helps you establish the connection."), + QMessageBox::Yes | QMessageBox::No); - if (r == QMessageBox::Yes) { - m_AppConfig->setAutoConfig(true); - downloadBonjour(); - } - else { - m_AppConfig->setAutoConfig(false); - m_pCheckBoxAutoConfig->setChecked(false); - } - } + if (r == QMessageBox::Yes) { + m_AppConfig->setAutoConfig(true); + downloadBonjour(); + } + else { + m_AppConfig->setAutoConfig(false); + m_pCheckBoxAutoConfig->setChecked(false); + } + } - m_AppConfig->setAutoConfigPrompted(true); + m_AppConfig->setAutoConfigPrompted(true); } void MainWindow::on_m_pComboServerList_currentIndexChanged(QString ) { - if (m_pComboServerList->count() != 0) { - restartSynergy(); - } + if (m_pComboServerList->count() != 0) { + restartSynergy(); + } } void MainWindow::on_m_pCheckBoxAutoConfig_toggled(bool checked) { - if (!isBonjourRunning() && checked) { - if (!m_SuppressAutoConfigWarning) { - int r = QMessageBox::information( - this, tr("Synergy"), - tr("Auto config feature requires Bonjour.\n\n" - "Do you want to install Bonjour?"), - QMessageBox::Yes | QMessageBox::No); + if (!isBonjourRunning() && checked) { + if (!m_SuppressAutoConfigWarning) { + int r = QMessageBox::information( + this, tr("Synergy"), + tr("Auto config feature requires Bonjour.\n\n" + "Do you want to install Bonjour?"), + QMessageBox::Yes | QMessageBox::No); - if (r == QMessageBox::Yes) { - downloadBonjour(); - } - } + if (r == QMessageBox::Yes) { + downloadBonjour(); + } + } - m_pCheckBoxAutoConfig->setChecked(false); - return; - } + m_pCheckBoxAutoConfig->setChecked(false); + return; + } - m_pLineEditHostname->setDisabled(checked); - appConfig().setAutoConfig(checked); - updateZeroconfService(); + m_pLineEditHostname->setDisabled(checked); + appConfig().setAutoConfig(checked); + updateZeroconfService(); - if (!checked) { - m_pComboServerList->clear(); - m_pComboServerList->hide(); - } + if (!checked) { + m_pComboServerList->clear(); + m_pComboServerList->hide(); + } } void MainWindow::bonjourInstallFinished() { - appendLogInfo("Bonjour install finished"); + appendLogInfo("Bonjour install finished"); - m_pCheckBoxAutoConfig->setChecked(true); + m_pCheckBoxAutoConfig->setChecked(true); } void MainWindow::on_windowShown() { - if (!m_AppConfig->activationHasRun() && (m_AppConfig->edition() == kUnregistered)) { - ActivationDialog activationDialog (this, appConfig(), subscriptionManager()); - activationDialog.exec(); - } + if (!m_AppConfig->activationHasRun() && (m_AppConfig->edition() == kUnregistered)) { + ActivationDialog activationDialog (this, appConfig(), subscriptionManager()); + activationDialog.exec(); + } } QString MainWindow::getProfileRootForArg() { - CoreInterface coreInterface; - QString dir = coreInterface.getProfileDir(); + CoreInterface coreInterface; + QString dir = coreInterface.getProfileDir(); - // HACK: strip our app name since we're returning the root dir. + // HACK: strip our app name since we're returning the root dir. #if defined(Q_OS_WIN) - dir.replace("\\Synergy", ""); + dir.replace("\\Synergy", ""); #else - dir.replace("/.synergy", ""); + dir.replace("/.synergy", ""); #endif - return QString("\"%1\"").arg(dir); + return QString("\"%1\"").arg(dir); } diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index 22cddabe..c00afdba 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -62,174 +62,174 @@ class SubscriptionManager; class MainWindow : public QMainWindow, public Ui::MainWindowBase { - Q_OBJECT + Q_OBJECT - friend class QSynergyApplication; - friend class SetupWizard; - friend class ActivationDialog; - friend class SettingsDialog; - - public: - enum qSynergyState - { - synergyDisconnected, - synergyConnecting, - synergyConnected, - synergyTransfering - }; + friend class QSynergyApplication; + friend class SetupWizard; + friend class ActivationDialog; + friend class SettingsDialog; - enum qSynergyType - { - synergyClient, - synergyServer - }; + public: + enum qSynergyState + { + synergyDisconnected, + synergyConnecting, + synergyConnected, + synergyTransfering + }; - enum qLevel { - Error, - Info - }; + enum qSynergyType + { + synergyClient, + synergyServer + }; - enum qRuningState { - kStarted, - kStopped - }; + enum qLevel { + Error, + Info + }; - public: - MainWindow(QSettings& settings, AppConfig& appConfig, - SubscriptionManager& subscriptionManager); - ~MainWindow(); + enum qRuningState { + kStarted, + kStopped + }; - public: - void setVisible(bool visible); - int synergyType() const { return m_pGroupClient->isChecked() ? synergyClient : synergyServer; } - int synergyState() const { return m_SynergyState; } - QString hostname() const { return m_pLineEditHostname->text(); } - QString configFilename(); - QString address(); - QString appPath(const QString& name); - void open(); - void clearLog(); - VersionChecker& versionChecker() { return m_VersionChecker; } - QString getScreenName(); - ServerConfig& serverConfig() { return m_ServerConfig; } - void showConfigureServer(const QString& message); - void showConfigureServer() { showConfigureServer(""); } - void autoAddScreen(const QString name); - void updateZeroconfService(); - void serverDetected(const QString name); - void updateLocalFingerprint(); - SubscriptionManager& subscriptionManager() const; + public: + MainWindow(QSettings& settings, AppConfig& appConfig, + SubscriptionManager& subscriptionManager); + ~MainWindow(); - public slots: - void setEdition(Edition edition); - void beginTrial(); - void endTrial(); - void appendLogRaw(const QString& text); - void appendLogInfo(const QString& text); - void appendLogDebug(const QString& text); - void appendLogError(const QString& text); - void startSynergy(); + public: + void setVisible(bool visible); + int synergyType() const { return m_pGroupClient->isChecked() ? synergyClient : synergyServer; } + int synergyState() const { return m_SynergyState; } + QString hostname() const { return m_pLineEditHostname->text(); } + QString configFilename(); + QString address(); + QString appPath(const QString& name); + void open(); + void clearLog(); + VersionChecker& versionChecker() { return m_VersionChecker; } + QString getScreenName(); + ServerConfig& serverConfig() { return m_ServerConfig; } + void showConfigureServer(const QString& message); + void showConfigureServer() { showConfigureServer(""); } + void autoAddScreen(const QString name); + void updateZeroconfService(); + void serverDetected(const QString name); + void updateLocalFingerprint(); + SubscriptionManager& subscriptionManager() const; - protected slots: - void sslToggled(bool enabled); - void on_m_pGroupClient_toggled(bool on); - void on_m_pGroupServer_toggled(bool on); - bool on_m_pButtonBrowseConfigFile_clicked(); - void on_m_pButtonConfigureServer_clicked(); - bool on_m_pActionSave_triggered(); - void on_m_pActionAbout_triggered(); - void on_m_pActionSettings_triggered(); - void on_m_pActivate_triggered(); - void synergyFinished(int exitCode, QProcess::ExitStatus); - void trayActivated(QSystemTrayIcon::ActivationReason reason); - void stopSynergy(); - void logOutput(); - void logError(); - void updateFound(const QString& version); - void bonjourInstallFinished(); + public slots: + void setEdition(Edition edition); + void beginTrial(bool isExpiring); + void endTrial(bool isExpired); + void appendLogRaw(const QString& text); + void appendLogInfo(const QString& text); + void appendLogDebug(const QString& text); + void appendLogError(const QString& text); + void startSynergy(); - protected: - QSettings& settings() { return m_Settings; } - AppConfig& appConfig() { return *m_AppConfig; } - QProcess* synergyProcess() { return m_pSynergy; } - void setSynergyProcess(QProcess* p) { m_pSynergy = p; } - void initConnections(); - void createMenuBar(); - void createStatusBar(); - void createTrayIcon(); - void loadSettings(); - void saveSettings(); - void setIcon(qSynergyState state); - void setSynergyState(qSynergyState state); - bool checkForApp(int which, QString& app); - bool clientArgs(QStringList& args, QString& app); - bool serverArgs(QStringList& args, QString& app); - void setStatus(const QString& status); - void sendIpcMessage(qIpcMessageType type, const char* buffer, bool showErrors); - void onModeChanged(bool startDesktop, bool applyService); - void updateStateFromLogLine(const QString& line); - QString getIPAddresses(); - void stopService(); - void stopDesktop(); - void changeEvent(QEvent* event); - void retranslateMenuBar(); + protected slots: + void sslToggled(bool enabled); + void on_m_pGroupClient_toggled(bool on); + void on_m_pGroupServer_toggled(bool on); + bool on_m_pButtonBrowseConfigFile_clicked(); + void on_m_pButtonConfigureServer_clicked(); + bool on_m_pActionSave_triggered(); + void on_m_pActionAbout_triggered(); + void on_m_pActionSettings_triggered(); + void on_m_pActivate_triggered(); + void synergyFinished(int exitCode, QProcess::ExitStatus); + void trayActivated(QSystemTrayIcon::ActivationReason reason); + void stopSynergy(); + void logOutput(); + void logError(); + void updateFound(const QString& version); + void bonjourInstallFinished(); + + protected: + QSettings& settings() { return m_Settings; } + AppConfig& appConfig() { return *m_AppConfig; } + QProcess* synergyProcess() { return m_pSynergy; } + void setSynergyProcess(QProcess* p) { m_pSynergy = p; } + void initConnections(); + void createMenuBar(); + void createStatusBar(); + void createTrayIcon(); + void loadSettings(); + void saveSettings(); + void setIcon(qSynergyState state); + void setSynergyState(qSynergyState state); + bool checkForApp(int which, QString& app); + bool clientArgs(QStringList& args, QString& app); + bool serverArgs(QStringList& args, QString& app); + void setStatus(const QString& status); + void sendIpcMessage(qIpcMessageType type, const char* buffer, bool showErrors); + void onModeChanged(bool startDesktop, bool applyService); + void updateStateFromLogLine(const QString& line); + QString getIPAddresses(); + void stopService(); + void stopDesktop(); + void changeEvent(QEvent* event); + void retranslateMenuBar(); #if defined(Q_OS_WIN) - bool isServiceRunning(QString name); + bool isServiceRunning(QString name); #else - bool isServiceRunning(); + bool isServiceRunning(); #endif - bool isBonjourRunning(); - void downloadBonjour(); - void promptAutoConfig(); - QString getProfileRootForArg(); - void checkConnected(const QString& line); - void checkFingerprint(const QString& line); - bool autoHide(); - QString getTimeStamp(); - void restartSynergy(); - void proofreadInfo(); + bool isBonjourRunning(); + void downloadBonjour(); + void promptAutoConfig(); + QString getProfileRootForArg(); + void checkConnected(const QString& line); + void checkFingerprint(const QString& line); + bool autoHide(); + QString getTimeStamp(); + void restartSynergy(); + void proofreadInfo(); - void showEvent (QShowEvent*); + void showEvent (QShowEvent*); - private: - QSettings& m_Settings; - AppConfig* m_AppConfig; - SubscriptionManager* m_SubscriptionManager; - QProcess* m_pSynergy; - int m_SynergyState; - ServerConfig m_ServerConfig; - QTemporaryFile* m_pTempConfigFile; - QSystemTrayIcon* m_pTrayIcon; - QMenu* m_pTrayIconMenu; - bool m_AlreadyHidden; - VersionChecker m_VersionChecker; - IpcClient m_IpcClient; - QMenuBar* m_pMenuBar; - QMenu* m_pMenuFile; - QMenu* m_pMenuEdit; - QMenu* m_pMenuWindow; - QMenu* m_pMenuHelp; - ZeroconfService* m_pZeroconfService; - DataDownloader* m_pDataDownloader; - QMessageBox* m_DownloadMessageBox; - QAbstractButton* m_pCancelButton; - QMutex m_UpdateZeroconfMutex; - bool m_SuppressAutoConfigWarning; - CommandProcess* m_BonjourInstall; - bool m_SuppressEmptyServerWarning; - qRuningState m_ExpectedRunningState; - QMutex m_StopDesktopMutex; - SslCertificate* m_pSslCertificate; + private: + QSettings& m_Settings; + AppConfig* m_AppConfig; + SubscriptionManager* m_SubscriptionManager; + QProcess* m_pSynergy; + int m_SynergyState; + ServerConfig m_ServerConfig; + QTemporaryFile* m_pTempConfigFile; + QSystemTrayIcon* m_pTrayIcon; + QMenu* m_pTrayIconMenu; + bool m_AlreadyHidden; + VersionChecker m_VersionChecker; + IpcClient m_IpcClient; + QMenuBar* m_pMenuBar; + QMenu* m_pMenuFile; + QMenu* m_pMenuEdit; + QMenu* m_pMenuWindow; + QMenu* m_pMenuHelp; + ZeroconfService* m_pZeroconfService; + DataDownloader* m_pDataDownloader; + QMessageBox* m_DownloadMessageBox; + QAbstractButton* m_pCancelButton; + QMutex m_UpdateZeroconfMutex; + bool m_SuppressAutoConfigWarning; + CommandProcess* m_BonjourInstall; + bool m_SuppressEmptyServerWarning; + qRuningState m_ExpectedRunningState; + QMutex m_StopDesktopMutex; + SslCertificate* m_pSslCertificate; private slots: - void on_m_pCheckBoxAutoConfig_toggled(bool checked); - void on_m_pComboServerList_currentIndexChanged(QString ); - void on_m_pButtonApply_clicked(); - void installBonjour(); - void on_windowShown(); + void on_m_pCheckBoxAutoConfig_toggled(bool checked); + void on_m_pComboServerList_currentIndexChanged(QString ); + void on_m_pButtonApply_clicked(); + void installBonjour(); + void on_windowShown(); signals: - void windowShown(); + void windowShown(); }; #endif diff --git a/src/gui/src/SubscriptionManager.cpp b/src/gui/src/SubscriptionManager.cpp index 86d42536..684ac82a 100644 --- a/src/gui/src/SubscriptionManager.cpp +++ b/src/gui/src/SubscriptionManager.cpp @@ -22,75 +22,82 @@ #include SubscriptionManager::SubscriptionManager(AppConfig* appConfig) : - m_AppConfig(appConfig), - m_serialKey(appConfig->edition()) { - try { - setSerialKey(m_AppConfig->serialKey()); - } catch (...) { - m_AppConfig->setSerialKey(""); - } + m_AppConfig(appConfig), + m_serialKey(appConfig->edition()) { + try { + setSerialKey(m_AppConfig->serialKey()); + } catch (...) { + m_AppConfig->setSerialKey(""); + m_AppConfig->saveSettings(); + } } SerialKey SubscriptionManager::setSerialKey(QString serialKeyString) { - SerialKey serialKey (serialKeyString.toStdString()); - if (!serialKey.isValid (::time(0))) { - throw std::runtime_error ("Invalid serial key"); - } + SerialKey serialKey (serialKeyString.toStdString()); + if (!serialKey.isValid (::time(0))) { + throw std::runtime_error ("Invalid serial key"); + } - if (serialKey != m_serialKey) { - using std::swap; - swap (serialKey, m_serialKey); - - m_AppConfig->setSerialKey (serialKeyString); - notifyActivation ("serial:" + serialKeyString); - emit serialKeyChanged (m_serialKey); + if (serialKey != m_serialKey) { + using std::swap; + swap (serialKey, m_serialKey); - if (m_serialKey.edition() != serialKey.edition()) { - m_AppConfig->setEdition (m_serialKey.edition()); - emit editionChanged (m_serialKey.edition()); - } + m_AppConfig->setSerialKey (serialKeyString); + notifyActivation ("serial:" + serialKeyString); + emit serialKeyChanged (m_serialKey); - if (m_serialKey.isTrial() != serialKey.isTrial()) { - if (m_serialKey.isTrial()) { - emit beginTrial(); - } else { - emit endTrial(); - } - } - } + if (m_serialKey.edition() != serialKey.edition()) { + m_AppConfig->setEdition (m_serialKey.edition()); + emit editionChanged (m_serialKey.edition()); + } - return serialKey; + if (serialKey.isTrial()) { + emit endTrial(false); + } + + if (m_serialKey.isTrial()) { + emit beginTrial(m_serialKey.isExpiring(::time(0))); + } + + m_AppConfig->saveSettings(); + } + + return serialKey; } -Edition SubscriptionManager::edition() const +Edition SubscriptionManager::activeLicense() const { - return m_serialKey.edition(); + return m_serialKey.edition(); } -bool SubscriptionManager::isTrial() const +void SubscriptionManager::update() const { - return m_serialKey.isTrial(); + emit serialKeyChanged (m_serialKey); + emit editionChanged (m_serialKey.edition()); + if (m_serialKey.isTrial()) { + emit beginTrial(m_serialKey.isExpiring(::time(0))); + } } void SubscriptionManager::skipActivation() { - notifyActivation ("skip:unknown"); + notifyActivation ("skip:unknown"); } void SubscriptionManager::notifyActivation(QString identity) { - ActivationNotifier* notifier = new ActivationNotifier(); - notifier->setIdentity(identity); + ActivationNotifier* notifier = new ActivationNotifier(); + notifier->setIdentity(identity); - QThread* thread = new QThread(); - connect(notifier, SIGNAL(finished()), thread, SLOT(quit())); - connect(notifier, SIGNAL(finished()), notifier, SLOT(deleteLater())); - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + QThread* thread = new QThread(); + connect(notifier, SIGNAL(finished()), thread, SLOT(quit())); + connect(notifier, SIGNAL(finished()), notifier, SLOT(deleteLater())); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - notifier->moveToThread(thread); - thread->start(); + notifier->moveToThread(thread); + thread->start(); - QMetaObject::invokeMethod(notifier, "notify", Qt::QueuedConnection); + QMetaObject::invokeMethod(notifier, "notify", Qt::QueuedConnection); } diff --git a/src/gui/src/SubscriptionManager.h b/src/gui/src/SubscriptionManager.h index eff9112d..56c3e48c 100644 --- a/src/gui/src/SubscriptionManager.h +++ b/src/gui/src/SubscriptionManager.h @@ -25,25 +25,25 @@ class AppConfig; class SubscriptionManager: public QObject { - Q_OBJECT + Q_OBJECT public: - SubscriptionManager(AppConfig* appConfig); - SerialKey setSerialKey(QString serialKey); - Edition edition() const; - bool isTrial() const; - void skipActivation(); + SubscriptionManager(AppConfig* appConfig); + SerialKey setSerialKey(QString serialKey); + void update() const; + Edition activeLicense() const; + void skipActivation(); private: - void notifyActivation(QString identity); - + void notifyActivation(QString identity); + private: - AppConfig* m_AppConfig; - SerialKey m_serialKey; - + AppConfig* m_AppConfig; + SerialKey m_serialKey; + signals: - void serialKeyChanged (SerialKey); - void editionChanged (Edition); - void beginTrial (); - void endTrial (); + void serialKeyChanged (SerialKey) const; + void editionChanged (Edition) const; + void beginTrial (bool expiring) const; + void endTrial (bool expired) const; }; diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index 7b4f4805..fb4544d4 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -1,11 +1,11 @@ /* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2016 Symless Ltd. - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -26,194 +26,194 @@ using namespace std; SerialKey::SerialKey(Edition edition): - m_userLimit(1), - m_warnTime(ULLONG_MAX), - m_expireTime(ULLONG_MAX), - m_edition(edition), - m_trial(false), - m_valid(true) + m_userLimit(1), + m_warnTime(ULLONG_MAX), + m_expireTime(ULLONG_MAX), + m_edition(edition), + m_trial(false), + m_valid(true) { } SerialKey::SerialKey(std::string serial) : - m_userLimit(1), - m_warnTime(0), - m_expireTime(0), - m_edition(Edition::kBasic), - m_trial(true), - m_valid(false) + m_userLimit(1), + m_warnTime(0), + m_expireTime(0), + m_edition(Edition::kBasic), + m_trial(true), + m_valid(false) { - string plainText = decode(serial); - if (!plainText.empty()) { - parse(plainText); - } + string plainText = decode(serial); + if (!plainText.empty()) { + parse(plainText); + } } bool SerialKey::isValid(time_t currentTime) const { - bool result = false; - - if (m_valid) { - if (m_trial) { - if (currentTime < m_expireTime) { - result = true; - } - } - else { - result = true; - } - } - - return result; + bool result = false; + + if (m_valid) { + if (m_trial) { + if (currentTime < m_expireTime) { + result = true; + } + } + else { + result = true; + } + } + + return result; } bool SerialKey::isExpiring(time_t currentTime) const { - bool result = false; - - if (m_valid) { - if (m_warnTime <= currentTime && currentTime < m_expireTime) { - result = true; - } - } - - return result; + bool result = false; + + if (m_valid) { + if (m_warnTime <= currentTime && currentTime < m_expireTime) { + result = true; + } + } + + return result; } bool SerialKey::isExpired(time_t currentTime) const { - bool result = false; - - if (m_valid) { - if (m_expireTime <= currentTime) { - result = true; - } - } - - return result; + bool result = false; + + if (m_valid) { + if (m_expireTime <= currentTime) { + result = true; + } + } + + return result; } bool SerialKey::isTrial() const { - return m_trial; + return m_trial; } -Edition +Edition SerialKey::edition() const { - return m_edition; + return m_edition; } time_t SerialKey::daysLeft(time_t currentTime) const { - unsigned long long timeLeft = 0; - unsigned long long const day = 60 * 60 * 24; - - if (currentTime < m_expireTime) { - timeLeft = m_expireTime - currentTime; - } + unsigned long long timeLeft = 0; + unsigned long long const day = 60 * 60 * 24; - unsigned long long dayLeft = 0; - dayLeft = timeLeft % day != 0 ? 1 : 0; - - return timeLeft / day + dayLeft; + if (currentTime < m_expireTime) { + timeLeft = m_expireTime - currentTime; + } + + unsigned long long dayLeft = 0; + dayLeft = timeLeft % day != 0 ? 1 : 0; + + return timeLeft / day + dayLeft; } std::string SerialKey::decode(const std::string& serial) const { - static const char* const lut = "0123456789ABCDEF"; - string output; - size_t len = serial.length(); - if (len & 1) { - return output; - } - - output.reserve(len / 2); - for (size_t i = 0; i < len; i += 2) { - - char a = serial[i]; - char b = serial[i + 1]; - - const char* p = std::lower_bound(lut, lut + 16, a); - const char* q = std::lower_bound(lut, lut + 16, b); - - if (*q != b || *p != a) { - return output; - } - - output.push_back(static_cast(((p - lut) << 4) | (q - lut))); - } - - return output; + static const char* const lut = "0123456789ABCDEF"; + string output; + size_t len = serial.length(); + if (len & 1) { + return output; + } + + output.reserve(len / 2); + for (size_t i = 0; i < len; i += 2) { + + char a = serial[i]; + char b = serial[i + 1]; + + const char* p = std::lower_bound(lut, lut + 16, a); + const char* q = std::lower_bound(lut, lut + 16, b); + + if (*q != b || *p != a) { + return output; + } + + output.push_back(static_cast(((p - lut) << 4) | (q - lut))); + } + + return output; } void SerialKey::parse(std::string plainSerial) { - string parityStart = plainSerial.substr(0, 1); - string parityEnd = plainSerial.substr(plainSerial.length() - 1, 1); - - // check for parity chars { and }, record parity result, then remove them. - if (parityStart == "{" && parityEnd == "}") { - plainSerial = plainSerial.substr(1, plainSerial.length() - 2); - - // tokenize serialised subscription. - vector parts; - std::string::size_type pos = 0; - bool look = true; - while (look) { - std::string::size_type start = pos; - pos = plainSerial.find(";", pos); - if (pos == string::npos) { - pos = plainSerial.length(); - look = false; - } - parts.push_back(plainSerial.substr(start, pos - start)); - pos += 1; - } + string parityStart = plainSerial.substr(0, 1); + string parityEnd = plainSerial.substr(plainSerial.length() - 1, 1); - if ((parts.size() == 8) - && (parts.at(0).find("v1") != string::npos)) { - // e.g.: {v1;basic;Bob;1;email;company name;1398297600;1398384000} - m_edition = getEdition(parts.at(1)); - m_name = parts.at(2); - m_trial = false; - sscanf(parts.at(3).c_str(), "%d", &m_userLimit); - m_email = parts.at(4); - m_company = parts.at(5); - sscanf(parts.at(6).c_str(), "%lld", &m_warnTime); - sscanf(parts.at(7).c_str(), "%lld", &m_expireTime); - m_valid = true; - } - else if ((parts.size() == 9) - && (parts.at(0).find("v2") != string::npos)) { - // e.g.: {v2;trial;basic;Bob;1;email;company name;1398297600;1398384000} - m_trial = parts.at(1) == "trial" ? true : false; - m_edition = getEdition(parts.at(2)); - m_name = parts.at(3); - sscanf(parts.at(4).c_str(), "%d", &m_userLimit); - m_email = parts.at(5); - m_company = parts.at(6); - sscanf(parts.at(7).c_str(), "%lld", &m_warnTime); - sscanf(parts.at(8).c_str(), "%lld", &m_expireTime); - m_valid = true; - } - } + // check for parity chars { and }, record parity result, then remove them. + if (parityStart == "{" && parityEnd == "}") { + plainSerial = plainSerial.substr(1, plainSerial.length() - 2); + + // tokenize serialised subscription. + vector parts; + std::string::size_type pos = 0; + bool look = true; + while (look) { + std::string::size_type start = pos; + pos = plainSerial.find(";", pos); + if (pos == string::npos) { + pos = plainSerial.length(); + look = false; + } + parts.push_back(plainSerial.substr(start, pos - start)); + pos += 1; + } + + if ((parts.size() == 8) + && (parts.at(0).find("v1") != string::npos)) { + // e.g.: {v1;basic;Bob;1;email;company name;1398297600;1398384000} + m_edition = parseEdition(parts.at(1)); + m_name = parts.at(2); + m_trial = false; + sscanf(parts.at(3).c_str(), "%d", &m_userLimit); + m_email = parts.at(4); + m_company = parts.at(5); + sscanf(parts.at(6).c_str(), "%lld", &m_warnTime); + sscanf(parts.at(7).c_str(), "%lld", &m_expireTime); + m_valid = true; + } + else if ((parts.size() == 9) + && (parts.at(0).find("v2") != string::npos)) { + // e.g.: {v2;trial;basic;Bob;1;email;company name;1398297600;1398384000} + m_trial = parts.at(1) == "trial" ? true : false; + m_edition = parseEdition(parts.at(2)); + m_name = parts.at(3); + sscanf(parts.at(4).c_str(), "%d", &m_userLimit); + m_email = parts.at(5); + m_company = parts.at(6); + sscanf(parts.at(7).c_str(), "%lld", &m_warnTime); + sscanf(parts.at(8).c_str(), "%lld", &m_expireTime); + m_valid = true; + } + } } Edition -SerialKey::getEdition(std::string editionStr) +SerialKey::parseEdition(std::string editionStr) { - Edition e = Edition::kBasic; - if (editionStr == "pro") { - e = Edition::kPro; - } - - return e; + Edition e = Edition::kBasic; + if (editionStr == "pro") { + e = Edition::kPro; + } + + return e; } diff --git a/src/lib/shared/SerialKey.h b/src/lib/shared/SerialKey.h index 03d0ab98..de9a4450 100644 --- a/src/lib/shared/SerialKey.h +++ b/src/lib/shared/SerialKey.h @@ -1,11 +1,11 @@ /* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2016 Symless Ltd. - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -26,61 +26,61 @@ #endif class SerialKey { - friend bool operator== (SerialKey const&, SerialKey const&); + friend bool operator== (SerialKey const&, SerialKey const&); public: - explicit SerialKey(Edition edition = Edition::kUnregistered); - explicit SerialKey(std::string serial); - - bool isValid(time_t currentTime) const; - bool isExpiring(time_t currentTime) const; - bool isExpired(time_t currentTime) const; - bool isTrial() const; - time_t daysLeft(time_t currentTime) const; - Edition edition() const; + explicit SerialKey(Edition edition = Edition::kUnregistered); + explicit SerialKey(std::string serial); + + bool isValid(time_t currentTime) const; + bool isExpiring(time_t currentTime) const; + bool isExpired(time_t currentTime) const; + bool isTrial() const; + time_t daysLeft(time_t currentTime) const; + Edition edition() const; private: - std::string decode(const std::string& serial) const; - void parse(std::string plainSerial); - Edition getEdition(std::string editionStr); + std::string decode(const std::string& serial) const; + void parse(std::string plainSerial); + Edition parseEdition(std::string editionStr); #ifdef TEST_ENV private: - FRIEND_TEST(SerialKeyTests, decode_empty_returnEmptyString); - FRIEND_TEST(SerialKeyTests, decode_invalidDigit_returnEmptyString); - FRIEND_TEST(SerialKeyTests, decode_validSerial_returnPlainText); - FRIEND_TEST(SerialKeyTests, parse_noParty_invalid); - FRIEND_TEST(SerialKeyTests, parse_invalidPartsLenghth_invalid); - FRIEND_TEST(SerialKeyTests, parse_validV1Serial_valid); - FRIEND_TEST(SerialKeyTests, parse_validV2Serial_valid); + FRIEND_TEST(SerialKeyTests, decode_empty_returnEmptyString); + FRIEND_TEST(SerialKeyTests, decode_invalidDigit_returnEmptyString); + FRIEND_TEST(SerialKeyTests, decode_validSerial_returnPlainText); + FRIEND_TEST(SerialKeyTests, parse_noParty_invalid); + FRIEND_TEST(SerialKeyTests, parse_invalidPartsLenghth_invalid); + FRIEND_TEST(SerialKeyTests, parse_validV1Serial_valid); + FRIEND_TEST(SerialKeyTests, parse_validV2Serial_valid); #endif - + private: - std::string m_name; - std::string m_email; - std::string m_company; - unsigned m_userLimit; - unsigned long long m_warnTime; - unsigned long long m_expireTime; - Edition m_edition; - bool m_trial; - bool m_valid; + std::string m_name; + std::string m_email; + std::string m_company; + unsigned m_userLimit; + unsigned long long m_warnTime; + unsigned long long m_expireTime; + Edition m_edition; + bool m_trial; + bool m_valid; }; inline bool operator== (SerialKey const& lhs, SerialKey const& rhs) { - return (lhs.m_name == rhs.m_name) && - (lhs.m_email == rhs.m_email) && - (lhs.m_company == rhs.m_company) && - (lhs.m_userLimit == rhs.m_userLimit) && - (lhs.m_warnTime == rhs.m_warnTime) && - (lhs.m_expireTime == rhs.m_expireTime) && - (lhs.m_edition == rhs.m_edition) && - (lhs.m_trial == rhs.m_trial) && - (lhs.m_valid == rhs.m_valid); + return (lhs.m_name == rhs.m_name) && + (lhs.m_email == rhs.m_email) && + (lhs.m_company == rhs.m_company) && + (lhs.m_userLimit == rhs.m_userLimit) && + (lhs.m_warnTime == rhs.m_warnTime) && + (lhs.m_expireTime == rhs.m_expireTime) && + (lhs.m_edition == rhs.m_edition) && + (lhs.m_trial == rhs.m_trial) && + (lhs.m_valid == rhs.m_valid); } inline bool operator!= (SerialKey const& lhs, SerialKey const& rhs) { - return !(lhs == rhs); + return !(lhs == rhs); } From 3ee9ac5d49a12f0f33acc6898e42510d59cb7a47 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Sat, 15 Oct 2016 15:48:07 +0100 Subject: [PATCH 33/88] #5657 Remove C++11 enum qualifier --- src/lib/shared/SerialKey.cpp | 6 +++--- src/lib/shared/SerialKey.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index fb4544d4..77b353dd 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -39,7 +39,7 @@ SerialKey::SerialKey(std::string serial) : m_userLimit(1), m_warnTime(0), m_expireTime(0), - m_edition(Edition::kBasic), + m_edition(kBasic), m_trial(true), m_valid(false) { @@ -210,9 +210,9 @@ SerialKey::parse(std::string plainSerial) Edition SerialKey::parseEdition(std::string editionStr) { - Edition e = Edition::kBasic; + Edition e = kBasic; if (editionStr == "pro") { - e = Edition::kPro; + e = kPro; } return e; diff --git a/src/lib/shared/SerialKey.h b/src/lib/shared/SerialKey.h index de9a4450..01884d86 100644 --- a/src/lib/shared/SerialKey.h +++ b/src/lib/shared/SerialKey.h @@ -28,7 +28,7 @@ class SerialKey { friend bool operator== (SerialKey const&, SerialKey const&); public: - explicit SerialKey(Edition edition = Edition::kUnregistered); + explicit SerialKey(Edition edition = kUnregistered); explicit SerialKey(std::string serial); bool isValid(time_t currentTime) const; From b20d04d80c6684a1f829efd7a4283dc28450d093 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Sat, 15 Oct 2016 15:59:27 +0100 Subject: [PATCH 34/88] #5657 Add missing include for runtime_error --- src/gui/src/SubscriptionManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/src/SubscriptionManager.cpp b/src/gui/src/SubscriptionManager.cpp index 684ac82a..17a1b432 100644 --- a/src/gui/src/SubscriptionManager.cpp +++ b/src/gui/src/SubscriptionManager.cpp @@ -19,6 +19,7 @@ #include "EditionType.h" #include "AppConfig.h" #include +#include #include SubscriptionManager::SubscriptionManager(AppConfig* appConfig) : From 0dd0e65e2a100ce8021e82a040cacaa9eb25c423 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Sat, 15 Oct 2016 16:07:05 +0100 Subject: [PATCH 35/88] #5657 Remove more C++11 enum qualifiers --- src/gui/src/ActivationDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 9d3b43f3..62bc0e23 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -39,7 +39,7 @@ ActivationDialog::~ActivationDialog() void ActivationDialog::reject() { - if (m_subscriptionManager->activeLicense() == Edition::kUnregistered) { + if (m_subscriptionManager->activeLicense() == kUnregistered) { CancelActivationDialog cancelActivationDialog(this); if (QDialog::Accepted == cancelActivationDialog.exec()) { m_subscriptionManager->skipActivation(); @@ -71,7 +71,7 @@ void ActivationDialog::accept() return; } - if (m_subscriptionManager->activeLicense() != Edition::kUnregistered) { + if (m_subscriptionManager->activeLicense() != kUnregistered) { message.information(this, "Activated!", tr("Thanks for activating %1!").arg (getEditionName(m_subscriptionManager->activeLicense()))); From e14ff8935b6153db8e56ad8f7e589021050fb54e Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Sat, 15 Oct 2016 16:25:04 +0100 Subject: [PATCH 36/88] #5657 Fix SerialKey unit test --- src/test/unittests/shared/SerialKeyTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/unittests/shared/SerialKeyTests.cpp b/src/test/unittests/shared/SerialKeyTests.cpp index 9535e15d..a16be1e2 100644 --- a/src/test/unittests/shared/SerialKeyTests.cpp +++ b/src/test/unittests/shared/SerialKeyTests.cpp @@ -135,7 +135,7 @@ TEST(SerialKeyTests, isExpiring_validV2TrialBasicSerial_returnFalse) // {v2;trial;basic;Bob;1;email;company name;0;86400} SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); EXPECT_EQ(true, serial.isTrial()); - EXPECT_FALSE(serial.isExpiring(0)); + EXPECT_TRUE(serial.isExpiring(0)); EXPECT_EQ(kBasic, serial.edition()); } From e05ced287cbf980a6cd489b033175b92771438c0 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 11:57:32 +0100 Subject: [PATCH 37/88] #5657 Enable external links on trial label --- src/gui/res/MainWindowBase.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/res/MainWindowBase.ui b/src/gui/res/MainWindowBase.ui index ba00f2d2..ac70cfd9 100644 --- a/src/gui/res/MainWindowBase.ui +++ b/src/gui/res/MainWindowBase.ui @@ -57,6 +57,9 @@ <html><head/><body><p><span style=" font-weight:600;">6</span> days of your Synergy Pro trial remain. <a href="http://symless.com/pricing?src=gui"><span style=" text-decoration: underline; color:#0000ff;">Buy now!</span></a></p></body></html> + + true +
From 714b2f64408f9b019cb2f0e35a1abf3b971c914c Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 15:26:42 +0100 Subject: [PATCH 38/88] #5657 Make trial expiry notification live --- src/gui/res/MainWindowBase.ui | 2 +- src/gui/src/ActivationDialog.cpp | 21 ++++++--- src/gui/src/MainWindow.cpp | 20 ++++++++- src/gui/src/QUtility.cpp | 13 ------ src/gui/src/QUtility.h | 1 - src/gui/src/SubscriptionManager.cpp | 58 +++++++++++++++++++++---- src/gui/src/SubscriptionManager.h | 10 +++-- src/lib/shared/SerialKey.cpp | 66 ++++++++++++++++++++++++++--- src/lib/shared/SerialKey.h | 5 ++- 9 files changed, 154 insertions(+), 42 deletions(-) diff --git a/src/gui/res/MainWindowBase.ui b/src/gui/res/MainWindowBase.ui index ac70cfd9..b3fb0a75 100644 --- a/src/gui/res/MainWindowBase.ui +++ b/src/gui/res/MainWindowBase.ui @@ -55,7 +55,7 @@ - <html><head/><body><p><span style=" font-weight:600;">6</span> days of your Synergy Pro trial remain. <a href="http://symless.com/pricing?src=gui"><span style=" text-decoration: underline; color:#0000ff;">Buy now!</span></a></p></body></html> + <html><head/><body><p><span style=" font-weight:600;">%1</span> days of your Synergy Pro trial remain. <a href="http://symless.com/pricing?src=gui"><span style=" text-decoration: underline; color:#0000ff;">Buy now!</span></a></p></body></html> true diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 62bc0e23..1dbef32e 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -39,7 +39,7 @@ ActivationDialog::~ActivationDialog() void ActivationDialog::reject() { - if (m_subscriptionManager->activeLicense() == kUnregistered) { + if (m_subscriptionManager->activeEdition() == kUnregistered) { CancelActivationDialog cancelActivationDialog(this); if (QDialog::Accepted == cancelActivationDialog.exec()) { m_subscriptionManager->skipActivation(); @@ -53,28 +53,35 @@ void ActivationDialog::reject() void ActivationDialog::accept() { QMessageBox message; - QString error; - m_appConfig->activationHasRun(true); m_appConfig->saveSettings(); + std::pair result; try { QString serialKey = ui->m_pTextEditSerialKey->toPlainText(); - m_subscriptionManager->setSerialKey(serialKey); + result = m_subscriptionManager->setSerialKey(serialKey); } catch (std::exception& e) { message.critical(this, "Unknown Error", tr("An error occurred while trying to activate Synergy. " "Please contact the helpdesk, and provide the " - "following details.\n\n%1").arg(e.what())); + "following information:\n\n%1").arg(e.what())); refreshSerialKey(); return; } - if (m_subscriptionManager->activeLicense() != kUnregistered) { + if (!result.first) { + message.critical(this, "Activation failed", + tr("%1").arg(result.second)); + refreshSerialKey(); + return; + } + + if (m_subscriptionManager->activeEdition() != kUnregistered) { message.information(this, "Activated!", tr("Thanks for activating %1!").arg - (getEditionName(m_subscriptionManager->activeLicense()))); + (m_subscriptionManager->activeEditionName())); } + QDialog::accept(); } diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index ad9d37d0..76244627 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -154,7 +154,7 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig, connect (m_AppConfig, SIGNAL(sslToggled(bool)), this, SLOT(sslToggled(bool)), Qt::QueuedConnection); - m_SubscriptionManager->update(); + m_SubscriptionManager->refresh(); } MainWindow::~MainWindow() @@ -547,6 +547,10 @@ void MainWindow::startSynergy() args << "--name" << getScreenName(); + if (!appConfig().serialKey().isEmpty()) { + args << "--serial-key " << appConfig().serialKey(); + } + if (desktopMode) { setSynergyProcess(new QProcess(this)); @@ -1038,7 +1042,7 @@ void MainWindow::serverDetected(const QString name) void MainWindow::setEdition(Edition edition) { - setWindowTitle(getEditionName(edition)); + setWindowTitle(m_SubscriptionManager->getEditionName (edition)); if (m_AppConfig->getCryptoEnabled()) { m_pSslCertificate = new SslCertificate(this); m_pSslCertificate->generateCertificate(); @@ -1050,8 +1054,19 @@ void MainWindow::setEdition(Edition edition) void MainWindow::beginTrial(bool isExpiring) { if (isExpiring) { + QString expiringNotice = "

%1 days of " + "your Synergy Pro trial remain. " + "Buy now!" + "

"; + expiringNotice = expiringNotice.arg + (m_SubscriptionManager->serialKey().daysLeft(::time(0))); + this->m_trialLabel->setText(expiringNotice); this->m_trialWidget->show(); } + setWindowTitle (m_SubscriptionManager->activeEditionName()); } void MainWindow::endTrial(bool isExpired) @@ -1059,6 +1074,7 @@ void MainWindow::endTrial(bool isExpired) if (!isExpired) { this->m_trialWidget->hide(); } + setWindowTitle (m_SubscriptionManager->activeEditionName()); } void MainWindow::updateLocalFingerprint() diff --git a/src/gui/src/QUtility.cpp b/src/gui/src/QUtility.cpp index c35f9638..589e74cf 100644 --- a/src/gui/src/QUtility.cpp +++ b/src/gui/src/QUtility.cpp @@ -43,19 +43,6 @@ void setIndexFromItemData(QComboBox* comboBox, const QVariant& itemData) } } -QString -getEditionName (int edition) { - if (edition == kBasic) { - return "Synergy Basic"; - } - else if (edition == kPro) { - return "Synergy Pro"; - } - else { - return "Synergy (UNREGISTERED)"; - } -} - QString hash(const QString& string) { QByteArray data = string.toUtf8(); diff --git a/src/gui/src/QUtility.h b/src/gui/src/QUtility.h index ca00d06f..0738d96c 100644 --- a/src/gui/src/QUtility.h +++ b/src/gui/src/QUtility.h @@ -29,4 +29,3 @@ QString hash(const QString& string); QString getFirstMacAddress(); qProcessorArch getProcessorArch(); QString getOSInformation(); -QString getEditionName (int edition); diff --git a/src/gui/src/SubscriptionManager.cpp b/src/gui/src/SubscriptionManager.cpp index 17a1b432..b42cc98d 100644 --- a/src/gui/src/SubscriptionManager.cpp +++ b/src/gui/src/SubscriptionManager.cpp @@ -20,6 +20,7 @@ #include "AppConfig.h" #include #include +#include #include SubscriptionManager::SubscriptionManager(AppConfig* appConfig) : @@ -28,18 +29,24 @@ SubscriptionManager::SubscriptionManager(AppConfig* appConfig) : try { setSerialKey(m_AppConfig->serialKey()); } catch (...) { + /* Remove garbage serial keys from the registry */ m_AppConfig->setSerialKey(""); + m_AppConfig->setEdition(kUnregistered); m_AppConfig->saveSettings(); } } -SerialKey +std::pair SubscriptionManager::setSerialKey(QString serialKeyString) { + std::pair ret (true, ""); + SerialKey serialKey (serialKeyString.toStdString()); - if (!serialKey.isValid (::time(0))) { - throw std::runtime_error ("Invalid serial key"); - } + if (serialKey.isExpired(::time(0))) { + ret.first = false; + ret.second = "Serial key expired"; + return ret; + } if (serialKey != m_serialKey) { using std::swap; @@ -65,15 +72,28 @@ SubscriptionManager::setSerialKey(QString serialKeyString) m_AppConfig->saveSettings(); } - return serialKey; + return ret; } -Edition SubscriptionManager::activeLicense() const +Edition +SubscriptionManager::activeEdition() const { - return m_serialKey.edition(); + return m_serialKey.edition(); } -void SubscriptionManager::update() const +QString +SubscriptionManager::activeEditionName() const +{ + return getEditionName(activeEdition(), m_serialKey.isTrial()); +} + +SerialKey +SubscriptionManager::serialKey() const +{ + return m_serialKey; +} + +void SubscriptionManager::refresh() const { emit serialKeyChanged (m_serialKey); emit editionChanged (m_serialKey.edition()); @@ -84,7 +104,27 @@ void SubscriptionManager::update() const void SubscriptionManager::skipActivation() { - notifyActivation ("skip:unknown"); + notifyActivation ("skip:unknown"); +} + +QString +SubscriptionManager::getEditionName(Edition const edition, bool trial) +{ + std::string name ("Synergy "); + switch (edition) { + case kUnregistered: + name += "(UNREGISTERED)"; + return QString::fromUtf8 (name.c_str(), name.size()); + case kBasic: + name += "Basic"; + break; + default: + name += "Pro"; + } + if (trial) { + name += " (Trial)"; + } + return QString::fromUtf8 (name.c_str(), name.size()); } void SubscriptionManager::notifyActivation(QString identity) diff --git a/src/gui/src/SubscriptionManager.h b/src/gui/src/SubscriptionManager.h index 56c3e48c..5023ddc2 100644 --- a/src/gui/src/SubscriptionManager.h +++ b/src/gui/src/SubscriptionManager.h @@ -20,6 +20,7 @@ #include #include #include +#include class AppConfig; @@ -29,10 +30,13 @@ class SubscriptionManager: public QObject public: SubscriptionManager(AppConfig* appConfig); - SerialKey setSerialKey(QString serialKey); - void update() const; - Edition activeLicense() const; + std::pair setSerialKey(QString serialKey); + void refresh() const; + Edition activeEdition() const; + QString activeEditionName() const; + SerialKey serialKey() const; void skipActivation(); + static QString getEditionName(Edition edition, bool trial = false); private: void notifyActivation(QString identity); diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index 77b353dd..ef3588ef 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include using namespace std; @@ -47,6 +50,9 @@ SerialKey::SerialKey(std::string serial) : if (!plainText.empty()) { parse(plainText); } + if (!m_valid) { + throw std::runtime_error ("Invalid serial key"); + } } bool @@ -105,7 +111,51 @@ SerialKey::isTrial() const Edition SerialKey::edition() const { - return m_edition; + return m_edition; +} + +std::string +SerialKey::editionString() const +{ + switch (edition()) { + case kBasic: + return "basic"; + case kPro: + return "pro"; + default: { + std::ostringstream oss; + oss << static_cast(edition()); + return oss.str(); + } + } +} + +static std::string +hexEncode (std::string const& str) { + std::ostringstream oss; + for (size_t i = 0; i < str.size(); ++i) { + int c = str[i]; + oss << std::setfill('0') << std::hex << std::setw(2) + << std::uppercase; + oss << c; + } + return oss.str(); +} + +std::string +SerialKey::toString() const +{ + std::ostringstream oss; + oss << "v2;"; + oss << (isTrial() ? "trial" : "lifetime") << ";"; + oss << editionString() << ";"; + oss << m_name << ";"; + oss << m_userLimit << ";"; + oss << m_email << ";"; + oss << m_company << ";"; + oss << m_warnTime << ";"; + oss << m_expireTime; + return hexEncode(oss.str()); } time_t @@ -118,10 +168,16 @@ SerialKey::daysLeft(time_t currentTime) const timeLeft = m_expireTime - currentTime; } - unsigned long long dayLeft = 0; - dayLeft = timeLeft % day != 0 ? 1 : 0; + unsigned long long daysLeft = 0; + daysLeft = timeLeft % day != 0 ? 1 : 0; - return timeLeft / day + dayLeft; + return timeLeft / day + daysLeft; +} + +std::string +SerialKey::email() const +{ + return m_email; } std::string @@ -208,7 +264,7 @@ SerialKey::parse(std::string plainSerial) } Edition -SerialKey::parseEdition(std::string editionStr) +SerialKey::parseEdition(std::string const& editionStr) { Edition e = kBasic; if (editionStr == "pro") { diff --git a/src/lib/shared/SerialKey.h b/src/lib/shared/SerialKey.h index 01884d86..dd73ad16 100644 --- a/src/lib/shared/SerialKey.h +++ b/src/lib/shared/SerialKey.h @@ -36,12 +36,15 @@ public: bool isExpired(time_t currentTime) const; bool isTrial() const; time_t daysLeft(time_t currentTime) const; + std::string email() const; Edition edition() const; + std::string toString() const; private: std::string decode(const std::string& serial) const; void parse(std::string plainSerial); - Edition parseEdition(std::string editionStr); + Edition parseEdition(const std::string& editionStr); + std::string editionString() const; #ifdef TEST_ENV private: From 7eefa49c7772aafb230b3533102b65438b444e52 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 16:12:33 +0100 Subject: [PATCH 39/88] #5657 Fix SerialKey construction in unit tests --- src/lib/shared/SerialKey.cpp | 4 +++- src/lib/shared/SerialKey.h | 5 +++-- src/test/unittests/shared/SerialKeyTests.cpp | 17 +++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index ef3588ef..51ee83db 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -181,7 +181,7 @@ SerialKey::email() const } std::string -SerialKey::decode(const std::string& serial) const +SerialKey::decode(const std::string& serial) { static const char* const lut = "0123456789ABCDEF"; string output; @@ -215,6 +215,8 @@ SerialKey::parse(std::string plainSerial) string parityStart = plainSerial.substr(0, 1); string parityEnd = plainSerial.substr(plainSerial.length() - 1, 1); + m_valid = false; + // check for parity chars { and }, record parity result, then remove them. if (parityStart == "{" && parityEnd == "}") { plainSerial = plainSerial.substr(1, plainSerial.length() - 2); diff --git a/src/lib/shared/SerialKey.h b/src/lib/shared/SerialKey.h index dd73ad16..ed0045c8 100644 --- a/src/lib/shared/SerialKey.h +++ b/src/lib/shared/SerialKey.h @@ -40,10 +40,11 @@ public: Edition edition() const; std::string toString() const; + static std::string decode(const std::string& serial); + static Edition parseEdition(const std::string& editionStr); + private: - std::string decode(const std::string& serial) const; void parse(std::string plainSerial); - Edition parseEdition(const std::string& editionStr); std::string editionString() const; #ifdef TEST_ENV diff --git a/src/test/unittests/shared/SerialKeyTests.cpp b/src/test/unittests/shared/SerialKeyTests.cpp index a16be1e2..0f072676 100644 --- a/src/test/unittests/shared/SerialKeyTests.cpp +++ b/src/test/unittests/shared/SerialKeyTests.cpp @@ -23,42 +23,39 @@ TEST(SerialKeyTests, decode_empty_returnEmptyString) { - SerialKey serial(""); - std::string plainText = serial.decode(""); + std::string plainText = SerialKey::decode(""); EXPECT_EQ(0, plainText.size()); } TEST(SerialKeyTests, decode_invalidDigit_returnEmptyString) { - SerialKey serial(""); - std::string plainText = serial.decode("MOCKZ"); + std::string plainText = SerialKey::decode("MOCKZ"); EXPECT_EQ(0, plainText.size()); } TEST(SerialKeyTests, decode_validSerial_returnPlainText) { - SerialKey serial(""); - std::string plainText = serial.decode("53796E6572677920726F636B7321"); + std::string plainText = SerialKey::decode("53796E6572677920726F636B7321"); EXPECT_EQ("Synergy rocks!", plainText); } TEST(SerialKeyTests, parse_noParty_invalid) { - SerialKey serial(""); + SerialKey serial; serial.parse("MOCK"); EXPECT_FALSE(serial.isValid(0)); } TEST(SerialKeyTests, parse_invalidPartsLenghth_invalid) { - SerialKey serial(""); + SerialKey serial; serial.parse("{Synergy;Rocks}"); EXPECT_FALSE(serial.isValid(0)); } TEST(SerialKeyTests, parse_validV1Serial_valid) { - SerialKey serial(""); + SerialKey serial; serial.parse("{v1;basic;Bob;1;email;company name;0;86400}"); EXPECT_EQ(true, serial.isValid(0)); EXPECT_EQ(kBasic, serial.edition()); @@ -69,7 +66,7 @@ TEST(SerialKeyTests, parse_validV1Serial_valid) TEST(SerialKeyTests, parse_validV2Serial_valid) { - SerialKey serial(""); + SerialKey serial; serial.parse("{v2;trial;pro;Bob;1;email;company name;0;86400}"); EXPECT_EQ(true, serial.isValid(0)); EXPECT_EQ(kPro, serial.edition()); From b5a6ae0a944020d0ffe537f711ab83c8aeecf837 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 16:27:40 +0100 Subject: [PATCH 40/88] #5657 Fix SerialKey expiring unit test --- src/test/unittests/shared/SerialKeyTests.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/unittests/shared/SerialKeyTests.cpp b/src/test/unittests/shared/SerialKeyTests.cpp index 0f072676..dfb8685b 100644 --- a/src/test/unittests/shared/SerialKeyTests.cpp +++ b/src/test/unittests/shared/SerialKeyTests.cpp @@ -1,11 +1,11 @@ /* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2016 Symless Inc. - * + * * 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 @@ -129,10 +129,10 @@ TEST(SerialKeyTests, isValid_expiredV2TrialProSerial_invalid) TEST(SerialKeyTests, isExpiring_validV2TrialBasicSerial_returnFalse) { - // {v2;trial;basic;Bob;1;email;company name;0;86400} - SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); + // {v2;trial;basic;Bob;1;email;company name;1;86400} + SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B313B38363430307D"); EXPECT_EQ(true, serial.isTrial()); - EXPECT_TRUE(serial.isExpiring(0)); + EXPECT_FALSE(serial.isExpiring(0)); EXPECT_EQ(kBasic, serial.edition()); } From c7dc198d824153fcf37b78c89646c8f65aeff62a Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 16:34:44 +0100 Subject: [PATCH 41/88] #5657 Fix SerialKey whitespace --- src/lib/shared/SerialKey.cpp | 244 +++++++++++++++++------------------ src/lib/shared/SerialKey.h | 76 +++++------ 2 files changed, 160 insertions(+), 160 deletions(-) diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index 51ee83db..1ee077eb 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -29,27 +29,27 @@ using namespace std; SerialKey::SerialKey(Edition edition): - m_userLimit(1), - m_warnTime(ULLONG_MAX), - m_expireTime(ULLONG_MAX), - m_edition(edition), - m_trial(false), - m_valid(true) + m_userLimit(1), + m_warnTime(ULLONG_MAX), + m_expireTime(ULLONG_MAX), + m_edition(edition), + m_trial(false), + m_valid(true) { } SerialKey::SerialKey(std::string serial) : - m_userLimit(1), - m_warnTime(0), - m_expireTime(0), - m_edition(kBasic), - m_trial(true), - m_valid(false) + m_userLimit(1), + m_warnTime(0), + m_expireTime(0), + m_edition(kBasic), + m_trial(true), + m_valid(false) { - string plainText = decode(serial); - if (!plainText.empty()) { - parse(plainText); - } + string plainText = decode(serial); + if (!plainText.empty()) { + parse(plainText); + } if (!m_valid) { throw std::runtime_error ("Invalid serial key"); } @@ -58,54 +58,54 @@ SerialKey::SerialKey(std::string serial) : bool SerialKey::isValid(time_t currentTime) const { - bool result = false; + bool result = false; - if (m_valid) { - if (m_trial) { - if (currentTime < m_expireTime) { - result = true; - } - } - else { - result = true; - } - } + if (m_valid) { + if (m_trial) { + if (currentTime < m_expireTime) { + result = true; + } + } + else { + result = true; + } + } - return result; + return result; } bool SerialKey::isExpiring(time_t currentTime) const { - bool result = false; + bool result = false; - if (m_valid) { - if (m_warnTime <= currentTime && currentTime < m_expireTime) { - result = true; - } - } + if (m_valid) { + if (m_warnTime <= currentTime && currentTime < m_expireTime) { + result = true; + } + } - return result; + return result; } bool SerialKey::isExpired(time_t currentTime) const { - bool result = false; + bool result = false; - if (m_valid) { - if (m_expireTime <= currentTime) { - result = true; - } - } + if (m_valid) { + if (m_expireTime <= currentTime) { + result = true; + } + } - return result; + return result; } bool SerialKey::isTrial() const { - return m_trial; + return m_trial; } Edition @@ -135,7 +135,7 @@ hexEncode (std::string const& str) { std::ostringstream oss; for (size_t i = 0; i < str.size(); ++i) { int c = str[i]; - oss << std::setfill('0') << std::hex << std::setw(2) + oss << std::setfill('0') << std::hex << std::setw(2) << std::uppercase; oss << c; } @@ -161,20 +161,20 @@ SerialKey::toString() const time_t SerialKey::daysLeft(time_t currentTime) const { - unsigned long long timeLeft = 0; - unsigned long long const day = 60 * 60 * 24; + unsigned long long timeLeft = 0; + unsigned long long const day = 60 * 60 * 24; - if (currentTime < m_expireTime) { - timeLeft = m_expireTime - currentTime; - } + if (currentTime < m_expireTime) { + timeLeft = m_expireTime - currentTime; + } - unsigned long long daysLeft = 0; - daysLeft = timeLeft % day != 0 ? 1 : 0; + unsigned long long daysLeft = 0; + daysLeft = timeLeft % day != 0 ? 1 : 0; return timeLeft / day + daysLeft; } -std::string +std::string SerialKey::email() const { return m_email; @@ -183,95 +183,95 @@ SerialKey::email() const std::string SerialKey::decode(const std::string& serial) { - static const char* const lut = "0123456789ABCDEF"; - string output; - size_t len = serial.length(); - if (len & 1) { - return output; - } + static const char* const lut = "0123456789ABCDEF"; + string output; + size_t len = serial.length(); + if (len & 1) { + return output; + } - output.reserve(len / 2); - for (size_t i = 0; i < len; i += 2) { + output.reserve(len / 2); + for (size_t i = 0; i < len; i += 2) { - char a = serial[i]; - char b = serial[i + 1]; + char a = serial[i]; + char b = serial[i + 1]; - const char* p = std::lower_bound(lut, lut + 16, a); - const char* q = std::lower_bound(lut, lut + 16, b); + const char* p = std::lower_bound(lut, lut + 16, a); + const char* q = std::lower_bound(lut, lut + 16, b); - if (*q != b || *p != a) { - return output; - } + if (*q != b || *p != a) { + return output; + } - output.push_back(static_cast(((p - lut) << 4) | (q - lut))); - } + output.push_back(static_cast(((p - lut) << 4) | (q - lut))); + } - return output; + return output; } void SerialKey::parse(std::string plainSerial) { - string parityStart = plainSerial.substr(0, 1); - string parityEnd = plainSerial.substr(plainSerial.length() - 1, 1); + string parityStart = plainSerial.substr(0, 1); + string parityEnd = plainSerial.substr(plainSerial.length() - 1, 1); - m_valid = false; + m_valid = false; - // check for parity chars { and }, record parity result, then remove them. - if (parityStart == "{" && parityEnd == "}") { - plainSerial = plainSerial.substr(1, plainSerial.length() - 2); + // check for parity chars { and }, record parity result, then remove them. + if (parityStart == "{" && parityEnd == "}") { + plainSerial = plainSerial.substr(1, plainSerial.length() - 2); - // tokenize serialised subscription. - vector parts; - std::string::size_type pos = 0; - bool look = true; - while (look) { - std::string::size_type start = pos; - pos = plainSerial.find(";", pos); - if (pos == string::npos) { - pos = plainSerial.length(); - look = false; - } - parts.push_back(plainSerial.substr(start, pos - start)); - pos += 1; - } + // tokenize serialised subscription. + vector parts; + std::string::size_type pos = 0; + bool look = true; + while (look) { + std::string::size_type start = pos; + pos = plainSerial.find(";", pos); + if (pos == string::npos) { + pos = plainSerial.length(); + look = false; + } + parts.push_back(plainSerial.substr(start, pos - start)); + pos += 1; + } - if ((parts.size() == 8) - && (parts.at(0).find("v1") != string::npos)) { - // e.g.: {v1;basic;Bob;1;email;company name;1398297600;1398384000} - m_edition = parseEdition(parts.at(1)); - m_name = parts.at(2); - m_trial = false; - sscanf(parts.at(3).c_str(), "%d", &m_userLimit); - m_email = parts.at(4); - m_company = parts.at(5); - sscanf(parts.at(6).c_str(), "%lld", &m_warnTime); - sscanf(parts.at(7).c_str(), "%lld", &m_expireTime); - m_valid = true; - } - else if ((parts.size() == 9) - && (parts.at(0).find("v2") != string::npos)) { - // e.g.: {v2;trial;basic;Bob;1;email;company name;1398297600;1398384000} - m_trial = parts.at(1) == "trial" ? true : false; - m_edition = parseEdition(parts.at(2)); - m_name = parts.at(3); - sscanf(parts.at(4).c_str(), "%d", &m_userLimit); - m_email = parts.at(5); - m_company = parts.at(6); - sscanf(parts.at(7).c_str(), "%lld", &m_warnTime); - sscanf(parts.at(8).c_str(), "%lld", &m_expireTime); - m_valid = true; - } - } + if ((parts.size() == 8) + && (parts.at(0).find("v1") != string::npos)) { + // e.g.: {v1;basic;Bob;1;email;company name;1398297600;1398384000} + m_edition = parseEdition(parts.at(1)); + m_name = parts.at(2); + m_trial = false; + sscanf(parts.at(3).c_str(), "%d", &m_userLimit); + m_email = parts.at(4); + m_company = parts.at(5); + sscanf(parts.at(6).c_str(), "%lld", &m_warnTime); + sscanf(parts.at(7).c_str(), "%lld", &m_expireTime); + m_valid = true; + } + else if ((parts.size() == 9) + && (parts.at(0).find("v2") != string::npos)) { + // e.g.: {v2;trial;basic;Bob;1;email;company name;1398297600;1398384000} + m_trial = parts.at(1) == "trial" ? true : false; + m_edition = parseEdition(parts.at(2)); + m_name = parts.at(3); + sscanf(parts.at(4).c_str(), "%d", &m_userLimit); + m_email = parts.at(5); + m_company = parts.at(6); + sscanf(parts.at(7).c_str(), "%lld", &m_warnTime); + sscanf(parts.at(8).c_str(), "%lld", &m_expireTime); + m_valid = true; + } + } } Edition SerialKey::parseEdition(std::string const& editionStr) { - Edition e = kBasic; - if (editionStr == "pro") { - e = kPro; - } + Edition e = kBasic; + if (editionStr == "pro") { + e = kPro; + } - return e; + return e; } diff --git a/src/lib/shared/SerialKey.h b/src/lib/shared/SerialKey.h index ed0045c8..c6ae3f20 100644 --- a/src/lib/shared/SerialKey.h +++ b/src/lib/shared/SerialKey.h @@ -26,65 +26,65 @@ #endif class SerialKey { - friend bool operator== (SerialKey const&, SerialKey const&); + friend bool operator== (SerialKey const&, SerialKey const&); public: - explicit SerialKey(Edition edition = kUnregistered); - explicit SerialKey(std::string serial); + explicit SerialKey(Edition edition = kUnregistered); + explicit SerialKey(std::string serial); - bool isValid(time_t currentTime) const; - bool isExpiring(time_t currentTime) const; - bool isExpired(time_t currentTime) const; - bool isTrial() const; - time_t daysLeft(time_t currentTime) const; + bool isValid(time_t currentTime) const; + bool isExpiring(time_t currentTime) const; + bool isExpired(time_t currentTime) const; + bool isTrial() const; + time_t daysLeft(time_t currentTime) const; std::string email() const; - Edition edition() const; + Edition edition() const; std::string toString() const; - static std::string decode(const std::string& serial); - static Edition parseEdition(const std::string& editionStr); + static std::string decode(const std::string& serial); + static Edition parseEdition(const std::string& editionStr); private: - void parse(std::string plainSerial); + void parse(std::string plainSerial); std::string editionString() const; #ifdef TEST_ENV private: - FRIEND_TEST(SerialKeyTests, decode_empty_returnEmptyString); - FRIEND_TEST(SerialKeyTests, decode_invalidDigit_returnEmptyString); - FRIEND_TEST(SerialKeyTests, decode_validSerial_returnPlainText); - FRIEND_TEST(SerialKeyTests, parse_noParty_invalid); - FRIEND_TEST(SerialKeyTests, parse_invalidPartsLenghth_invalid); - FRIEND_TEST(SerialKeyTests, parse_validV1Serial_valid); - FRIEND_TEST(SerialKeyTests, parse_validV2Serial_valid); + FRIEND_TEST(SerialKeyTests, decode_empty_returnEmptyString); + FRIEND_TEST(SerialKeyTests, decode_invalidDigit_returnEmptyString); + FRIEND_TEST(SerialKeyTests, decode_validSerial_returnPlainText); + FRIEND_TEST(SerialKeyTests, parse_noParty_invalid); + FRIEND_TEST(SerialKeyTests, parse_invalidPartsLenghth_invalid); + FRIEND_TEST(SerialKeyTests, parse_validV1Serial_valid); + FRIEND_TEST(SerialKeyTests, parse_validV2Serial_valid); #endif private: - std::string m_name; - std::string m_email; - std::string m_company; - unsigned m_userLimit; - unsigned long long m_warnTime; - unsigned long long m_expireTime; - Edition m_edition; - bool m_trial; - bool m_valid; + std::string m_name; + std::string m_email; + std::string m_company; + unsigned m_userLimit; + unsigned long long m_warnTime; + unsigned long long m_expireTime; + Edition m_edition; + bool m_trial; + bool m_valid; }; inline bool operator== (SerialKey const& lhs, SerialKey const& rhs) { - return (lhs.m_name == rhs.m_name) && - (lhs.m_email == rhs.m_email) && - (lhs.m_company == rhs.m_company) && - (lhs.m_userLimit == rhs.m_userLimit) && - (lhs.m_warnTime == rhs.m_warnTime) && - (lhs.m_expireTime == rhs.m_expireTime) && - (lhs.m_edition == rhs.m_edition) && - (lhs.m_trial == rhs.m_trial) && - (lhs.m_valid == rhs.m_valid); + return (lhs.m_name == rhs.m_name) && + (lhs.m_email == rhs.m_email) && + (lhs.m_company == rhs.m_company) && + (lhs.m_userLimit == rhs.m_userLimit) && + (lhs.m_warnTime == rhs.m_warnTime) && + (lhs.m_expireTime == rhs.m_expireTime) && + (lhs.m_edition == rhs.m_edition) && + (lhs.m_trial == rhs.m_trial) && + (lhs.m_valid == rhs.m_valid); } inline bool operator!= (SerialKey const& lhs, SerialKey const& rhs) { - return !(lhs == rhs); + return !(lhs == rhs); } From 8b4d7abfb01da7742b0a133670230e526600a90b Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 17:08:26 +0100 Subject: [PATCH 42/88] #5657 Remove SerialKey::m_valid --- src/gui/src/ActivationDialog.cpp | 92 +- src/gui/src/MainWindow.cpp | 1732 +++++++++++++++--------------- src/lib/shared/SerialKey.cpp | 53 +- src/lib/shared/SerialKey.h | 7 +- 4 files changed, 934 insertions(+), 950 deletions(-) diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 1dbef32e..9e7f5e82 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -15,73 +15,81 @@ #include ActivationDialog::ActivationDialog(QWidget* parent, AppConfig& appConfig, - SubscriptionManager& subscriptionManager) : - QDialog(parent), - ui(new Ui::ActivationDialog), - m_appConfig(&appConfig), - m_subscriptionManager (&subscriptionManager) + SubscriptionManager& subscriptionManager) : + QDialog(parent), + ui(new Ui::ActivationDialog), + m_appConfig(&appConfig), + m_subscriptionManager (&subscriptionManager) { - ui->setupUi(this); - refreshSerialKey(); + ui->setupUi(this); + refreshSerialKey(); } void ActivationDialog::refreshSerialKey() { - ui->m_pTextEditSerialKey->setText(m_appConfig->serialKey()); - ui->m_pTextEditSerialKey->setFocus(); - ui->m_pTextEditSerialKey->moveCursor(QTextCursor::End); + ui->m_pTextEditSerialKey->setText(m_appConfig->serialKey()); + ui->m_pTextEditSerialKey->setFocus(); + ui->m_pTextEditSerialKey->moveCursor(QTextCursor::End); } ActivationDialog::~ActivationDialog() { - delete ui; + delete ui; } void ActivationDialog::reject() { - if (m_subscriptionManager->activeEdition() == kUnregistered) { - CancelActivationDialog cancelActivationDialog(this); - if (QDialog::Accepted == cancelActivationDialog.exec()) { - m_subscriptionManager->skipActivation(); - m_appConfig->activationHasRun(true); - m_appConfig->saveSettings(); - } - } - QDialog::reject(); + if (m_subscriptionManager->activeEdition() == kUnregistered) { + CancelActivationDialog cancelActivationDialog(this); + if (QDialog::Accepted == cancelActivationDialog.exec()) { + m_subscriptionManager->skipActivation(); + m_appConfig->activationHasRun(true); + m_appConfig->saveSettings(); + } + } + QDialog::reject(); } void ActivationDialog::accept() { - QMessageBox message; - m_appConfig->activationHasRun(true); - m_appConfig->saveSettings(); + QMessageBox message; + m_appConfig->activationHasRun(true); + m_appConfig->saveSettings(); std::pair result; - try { - QString serialKey = ui->m_pTextEditSerialKey->toPlainText(); - result = m_subscriptionManager->setSerialKey(serialKey); - } - catch (std::exception& e) { - message.critical(this, "Unknown Error", - tr("An error occurred while trying to activate Synergy. " - "Please contact the helpdesk, and provide the " - "following information:\n\n%1").arg(e.what())); - refreshSerialKey(); - return; - } + try { + QString serialKey = ui->m_pTextEditSerialKey->toPlainText(); + result = m_subscriptionManager->setSerialKey(serialKey); + } + catch (std::exception& e) { + message.critical(this, "Unknown Error", + tr("An error occurred while trying to activate Synergy. " + "Please contact the helpdesk, and provide the " + "following information:\n\n%1").arg(e.what())); + refreshSerialKey(); + return; + } if (!result.first) { - message.critical(this, "Activation failed", + message.critical(this, "Activation failed", tr("%1").arg(result.second)); refreshSerialKey(); return; } - if (m_subscriptionManager->activeEdition() != kUnregistered) { - message.information(this, "Activated!", - tr("Thanks for activating %1!").arg - (m_subscriptionManager->activeEditionName())); - } + Edition edition = m_subscriptionManager->activeEdition(); + if (edition != kUnregistered) { + if (m_subscriptionManager->serialKey().isTrial()) { + message.information(this, "Thanks!", + tr("Thanks for trying %1!").arg + (m_subscriptionManager->getEditionName(edition))); + } + else { + message.information(this, "Activated!", + tr("Thanks for activating %1!").arg + (m_subscriptionManager->getEditionName(edition))); + } + } - QDialog::accept(); + QDialog::accept(); } diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 76244627..64a9f50d 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -70,990 +70,990 @@ static const QString synergyConfigFilter(QObject::tr("Synergy Configurations (*. static const char* synergyIconFiles[] = { - ":/res/icons/16x16/synergy-disconnected.png", - ":/res/icons/16x16/synergy-disconnected.png", - ":/res/icons/16x16/synergy-connected.png", - ":/res/icons/16x16/synergy-transfering.png" + ":/res/icons/16x16/synergy-disconnected.png", + ":/res/icons/16x16/synergy-disconnected.png", + ":/res/icons/16x16/synergy-connected.png", + ":/res/icons/16x16/synergy-transfering.png" }; MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig, - SubscriptionManager& subscriptionManager) : - m_Settings(settings), - m_AppConfig(&appConfig), - m_SubscriptionManager(&subscriptionManager), - m_pSynergy(NULL), - m_SynergyState(synergyDisconnected), - m_ServerConfig(&m_Settings, 5, 3, m_AppConfig->screenName(), this), - m_pTempConfigFile(NULL), - m_pTrayIcon(NULL), - m_pTrayIconMenu(NULL), - m_AlreadyHidden(false), - m_pMenuBar(NULL), - m_pMenuFile(NULL), - m_pMenuEdit(NULL), - m_pMenuWindow(NULL), - m_pMenuHelp(NULL), - m_pZeroconfService(NULL), - m_pDataDownloader(NULL), - m_DownloadMessageBox(NULL), - m_pCancelButton(NULL), - m_SuppressAutoConfigWarning(false), - m_BonjourInstall(NULL), - m_SuppressEmptyServerWarning(false), - m_ExpectedRunningState(kStopped), - m_pSslCertificate(NULL) + SubscriptionManager& subscriptionManager) : + m_Settings(settings), + m_AppConfig(&appConfig), + m_SubscriptionManager(&subscriptionManager), + m_pSynergy(NULL), + m_SynergyState(synergyDisconnected), + m_ServerConfig(&m_Settings, 5, 3, m_AppConfig->screenName(), this), + m_pTempConfigFile(NULL), + m_pTrayIcon(NULL), + m_pTrayIconMenu(NULL), + m_AlreadyHidden(false), + m_pMenuBar(NULL), + m_pMenuFile(NULL), + m_pMenuEdit(NULL), + m_pMenuWindow(NULL), + m_pMenuHelp(NULL), + m_pZeroconfService(NULL), + m_pDataDownloader(NULL), + m_DownloadMessageBox(NULL), + m_pCancelButton(NULL), + m_SuppressAutoConfigWarning(false), + m_BonjourInstall(NULL), + m_SuppressEmptyServerWarning(false), + m_ExpectedRunningState(kStopped), + m_pSslCertificate(NULL) { - setupUi(this); + setupUi(this); - createMenuBar(); - loadSettings(); - initConnections(); + createMenuBar(); + loadSettings(); + initConnections(); - m_pWidgetUpdate->hide(); - m_VersionChecker.setApp(appPath(appConfig.synergycName())); - m_pLabelScreenName->setText(getScreenName()); - m_pLabelIpAddresses->setText(getIPAddresses()); + m_pWidgetUpdate->hide(); + m_VersionChecker.setApp(appPath(appConfig.synergycName())); + m_pLabelScreenName->setText(getScreenName()); + m_pLabelIpAddresses->setText(getIPAddresses()); #if defined(Q_OS_WIN) - // ipc must always be enabled, so that we can disable command when switching to desktop mode. - connect(&m_IpcClient, SIGNAL(readLogLine(const QString&)), this, SLOT(appendLogRaw(const QString&))); - connect(&m_IpcClient, SIGNAL(errorMessage(const QString&)), this, SLOT(appendLogError(const QString&))); - connect(&m_IpcClient, SIGNAL(infoMessage(const QString&)), this, SLOT(appendLogNote(const QString&))); - m_IpcClient.connectToHost(); + // ipc must always be enabled, so that we can disable command when switching to desktop mode. + connect(&m_IpcClient, SIGNAL(readLogLine(const QString&)), this, SLOT(appendLogRaw(const QString&))); + connect(&m_IpcClient, SIGNAL(errorMessage(const QString&)), this, SLOT(appendLogError(const QString&))); + connect(&m_IpcClient, SIGNAL(infoMessage(const QString&)), this, SLOT(appendLogNote(const QString&))); + m_IpcClient.connectToHost(); #endif - // change default size based on os + // change default size based on os #if defined(Q_OS_MAC) - resize(720, 550); - setMinimumSize(size()); + resize(720, 550); + setMinimumSize(size()); #elif defined(Q_OS_LINUX) - resize(700, 530); - setMinimumSize(size()); + resize(700, 530); + setMinimumSize(size()); #endif - m_SuppressAutoConfigWarning = true; - m_pCheckBoxAutoConfig->setChecked(appConfig.autoConfig()); - m_SuppressAutoConfigWarning = false; + m_SuppressAutoConfigWarning = true; + m_pCheckBoxAutoConfig->setChecked(appConfig.autoConfig()); + m_SuppressAutoConfigWarning = false; - m_pComboServerList->hide(); - m_pLabelPadlock->hide(); - m_trialWidget->hide(); + m_pComboServerList->hide(); + m_pLabelPadlock->hide(); + m_trialWidget->hide(); - connect (this, SIGNAL(windowShown()), - this, SLOT(on_windowShown()), Qt::QueuedConnection); + connect (this, SIGNAL(windowShown()), + this, SLOT(on_windowShown()), Qt::QueuedConnection); - connect (m_SubscriptionManager, SIGNAL(editionChanged(Edition)), - this, SLOT(setEdition(Edition)), Qt::QueuedConnection); + connect (m_SubscriptionManager, SIGNAL(editionChanged(Edition)), + this, SLOT(setEdition(Edition)), Qt::QueuedConnection); - connect (m_SubscriptionManager, SIGNAL(beginTrial(bool)), - this, SLOT(beginTrial(bool)), Qt::QueuedConnection); + connect (m_SubscriptionManager, SIGNAL(beginTrial(bool)), + this, SLOT(beginTrial(bool)), Qt::QueuedConnection); - connect (m_SubscriptionManager, SIGNAL(endTrial(bool)), - this, SLOT(endTrial(bool)), Qt::QueuedConnection); + connect (m_SubscriptionManager, SIGNAL(endTrial(bool)), + this, SLOT(endTrial(bool)), Qt::QueuedConnection); - connect (m_AppConfig, SIGNAL(sslToggled(bool)), - this, SLOT(sslToggled(bool)), Qt::QueuedConnection); + connect (m_AppConfig, SIGNAL(sslToggled(bool)), + this, SLOT(sslToggled(bool)), Qt::QueuedConnection); - m_SubscriptionManager->refresh(); + m_SubscriptionManager->refresh(); } MainWindow::~MainWindow() { - if (appConfig().processMode() == Desktop) { - m_ExpectedRunningState = kStopped; - stopDesktop(); - } + if (appConfig().processMode() == Desktop) { + m_ExpectedRunningState = kStopped; + stopDesktop(); + } - saveSettings(); + saveSettings(); - delete m_pZeroconfService; + delete m_pZeroconfService; - if (m_DownloadMessageBox != NULL) { - delete m_DownloadMessageBox; - } + if (m_DownloadMessageBox != NULL) { + delete m_DownloadMessageBox; + } - if (m_BonjourInstall != NULL) { - delete m_BonjourInstall; - } + if (m_BonjourInstall != NULL) { + delete m_BonjourInstall; + } - delete m_pSslCertificate; + delete m_pSslCertificate; } void MainWindow::open() { - createTrayIcon(); + createTrayIcon(); - if (!autoHide()) { - showNormal(); - } + if (!autoHide()) { + showNormal(); + } - m_VersionChecker.checkLatest(); + m_VersionChecker.checkLatest(); - if (!appConfig().autoConfigPrompted()) { - promptAutoConfig(); - } + if (!appConfig().autoConfigPrompted()) { + promptAutoConfig(); + } - // only start if user has previously started. this stops the gui from - // auto hiding before the user has configured synergy (which of course - // confuses first time users, who think synergy has crashed). - if (appConfig().startedBefore() && appConfig().processMode() == Desktop) { - m_SuppressEmptyServerWarning = true; - startSynergy(); - m_SuppressEmptyServerWarning = false; - } + // only start if user has previously started. this stops the gui from + // auto hiding before the user has configured synergy (which of course + // confuses first time users, who think synergy has crashed). + if (appConfig().startedBefore() && appConfig().processMode() == Desktop) { + m_SuppressEmptyServerWarning = true; + startSynergy(); + m_SuppressEmptyServerWarning = false; + } } void MainWindow::onModeChanged(bool startDesktop, bool applyService) { - if (appConfig().processMode() == Service) - { - // ensure that the apply button actually does something, since desktop - // mode screws around with connecting/disconnecting the action. - disconnect(m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); - connect(m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); + if (appConfig().processMode() == Service) + { + // ensure that the apply button actually does something, since desktop + // mode screws around with connecting/disconnecting the action. + disconnect(m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); + connect(m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); - if (applyService) - { - stopDesktop(); - startSynergy(); - } - } - else if ((appConfig().processMode() == Desktop) && startDesktop) - { - stopService(); - startSynergy(); - } + if (applyService) + { + stopDesktop(); + startSynergy(); + } + } + else if ((appConfig().processMode() == Desktop) && startDesktop) + { + stopService(); + startSynergy(); + } } void MainWindow::setStatus(const QString &status) { - m_pStatusLabel->setText(status); + m_pStatusLabel->setText(status); } void MainWindow::createTrayIcon() { - m_pTrayIconMenu = new QMenu(this); + m_pTrayIconMenu = new QMenu(this); - m_pTrayIconMenu->addAction(m_pActionStartSynergy); - m_pTrayIconMenu->addAction(m_pActionStopSynergy); - m_pTrayIconMenu->addSeparator(); + m_pTrayIconMenu->addAction(m_pActionStartSynergy); + m_pTrayIconMenu->addAction(m_pActionStopSynergy); + m_pTrayIconMenu->addSeparator(); - m_pTrayIconMenu->addAction(m_pActionMinimize); - m_pTrayIconMenu->addAction(m_pActionRestore); - m_pTrayIconMenu->addSeparator(); - m_pTrayIconMenu->addAction(m_pActionQuit); + m_pTrayIconMenu->addAction(m_pActionMinimize); + m_pTrayIconMenu->addAction(m_pActionRestore); + m_pTrayIconMenu->addSeparator(); + m_pTrayIconMenu->addAction(m_pActionQuit); - m_pTrayIcon = new QSystemTrayIcon(this); - m_pTrayIcon->setContextMenu(m_pTrayIconMenu); + m_pTrayIcon = new QSystemTrayIcon(this); + m_pTrayIcon->setContextMenu(m_pTrayIconMenu); - connect(m_pTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), - this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason))); + connect(m_pTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), + this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason))); - setIcon(synergyDisconnected); + setIcon(synergyDisconnected); - m_pTrayIcon->show(); + m_pTrayIcon->show(); } void MainWindow::retranslateMenuBar() { - m_pMenuFile->setTitle(tr("&File")); - m_pMenuEdit->setTitle(tr("&Edit")); - m_pMenuWindow->setTitle(tr("&Window")); - m_pMenuHelp->setTitle(tr("&Help")); + m_pMenuFile->setTitle(tr("&File")); + m_pMenuEdit->setTitle(tr("&Edit")); + m_pMenuWindow->setTitle(tr("&Window")); + m_pMenuHelp->setTitle(tr("&Help")); } void MainWindow::createMenuBar() { - m_pMenuBar = new QMenuBar(this); - m_pMenuFile = new QMenu("", m_pMenuBar); - m_pMenuEdit = new QMenu("", m_pMenuBar); - m_pMenuWindow = new QMenu("", m_pMenuBar); - m_pMenuHelp = new QMenu("", m_pMenuBar); - retranslateMenuBar(); + m_pMenuBar = new QMenuBar(this); + m_pMenuFile = new QMenu("", m_pMenuBar); + m_pMenuEdit = new QMenu("", m_pMenuBar); + m_pMenuWindow = new QMenu("", m_pMenuBar); + m_pMenuHelp = new QMenu("", m_pMenuBar); + retranslateMenuBar(); - m_pMenuBar->addAction(m_pMenuFile->menuAction()); - m_pMenuBar->addAction(m_pMenuEdit->menuAction()); + m_pMenuBar->addAction(m_pMenuFile->menuAction()); + m_pMenuBar->addAction(m_pMenuEdit->menuAction()); #if !defined(Q_OS_MAC) - m_pMenuBar->addAction(m_pMenuWindow->menuAction()); + m_pMenuBar->addAction(m_pMenuWindow->menuAction()); #endif - m_pMenuBar->addAction(m_pMenuHelp->menuAction()); + m_pMenuBar->addAction(m_pMenuHelp->menuAction()); - m_pMenuFile->addAction(m_pActionStartSynergy); - m_pMenuFile->addAction(m_pActionStopSynergy); - m_pMenuFile->addSeparator(); - m_pMenuFile->addAction(m_pActivate); - m_pMenuFile->addSeparator(); - m_pMenuFile->addAction(m_pActionSave); - m_pMenuFile->addSeparator(); - m_pMenuFile->addAction(m_pActionQuit); - m_pMenuEdit->addAction(m_pActionSettings); - m_pMenuWindow->addAction(m_pActionMinimize); - m_pMenuWindow->addAction(m_pActionRestore); - m_pMenuHelp->addAction(m_pActionAbout); + m_pMenuFile->addAction(m_pActionStartSynergy); + m_pMenuFile->addAction(m_pActionStopSynergy); + m_pMenuFile->addSeparator(); + m_pMenuFile->addAction(m_pActivate); + m_pMenuFile->addSeparator(); + m_pMenuFile->addAction(m_pActionSave); + m_pMenuFile->addSeparator(); + m_pMenuFile->addAction(m_pActionQuit); + m_pMenuEdit->addAction(m_pActionSettings); + m_pMenuWindow->addAction(m_pActionMinimize); + m_pMenuWindow->addAction(m_pActionRestore); + m_pMenuHelp->addAction(m_pActionAbout); - setMenuBar(m_pMenuBar); + setMenuBar(m_pMenuBar); } void MainWindow::loadSettings() { - // the next two must come BEFORE loading groupServerChecked and groupClientChecked or - // disabling and/or enabling the right widgets won't automatically work - m_pRadioExternalConfig->setChecked(settings().value("useExternalConfig", false).toBool()); - m_pRadioInternalConfig->setChecked(settings().value("useInternalConfig", true).toBool()); + // the next two must come BEFORE loading groupServerChecked and groupClientChecked or + // disabling and/or enabling the right widgets won't automatically work + m_pRadioExternalConfig->setChecked(settings().value("useExternalConfig", false).toBool()); + m_pRadioInternalConfig->setChecked(settings().value("useInternalConfig", true).toBool()); - m_pGroupServer->setChecked(settings().value("groupServerChecked", false).toBool()); - m_pLineEditConfigFile->setText(settings().value("configFile", QDir::homePath() + "/" + synergyConfigName).toString()); - m_pGroupClient->setChecked(settings().value("groupClientChecked", true).toBool()); - m_pLineEditHostname->setText(settings().value("serverHostname").toString()); + m_pGroupServer->setChecked(settings().value("groupServerChecked", false).toBool()); + m_pLineEditConfigFile->setText(settings().value("configFile", QDir::homePath() + "/" + synergyConfigName).toString()); + m_pGroupClient->setChecked(settings().value("groupClientChecked", true).toBool()); + m_pLineEditHostname->setText(settings().value("serverHostname").toString()); } void MainWindow::initConnections() { - connect(m_pActionMinimize, SIGNAL(triggered()), this, SLOT(hide())); - connect(m_pActionRestore, SIGNAL(triggered()), this, SLOT(showNormal())); - connect(m_pActionStartSynergy, SIGNAL(triggered()), this, SLOT(startSynergy())); - connect(m_pActionStopSynergy, SIGNAL(triggered()), this, SLOT(stopSynergy())); - connect(m_pActionQuit, SIGNAL(triggered()), qApp, SLOT(quit())); - connect(&m_VersionChecker, SIGNAL(updateFound(const QString&)), this, SLOT(updateFound(const QString&))); + connect(m_pActionMinimize, SIGNAL(triggered()), this, SLOT(hide())); + connect(m_pActionRestore, SIGNAL(triggered()), this, SLOT(showNormal())); + connect(m_pActionStartSynergy, SIGNAL(triggered()), this, SLOT(startSynergy())); + connect(m_pActionStopSynergy, SIGNAL(triggered()), this, SLOT(stopSynergy())); + connect(m_pActionQuit, SIGNAL(triggered()), qApp, SLOT(quit())); + connect(&m_VersionChecker, SIGNAL(updateFound(const QString&)), this, SLOT(updateFound(const QString&))); } void MainWindow::saveSettings() { - // program settings - settings().setValue("groupServerChecked", m_pGroupServer->isChecked()); - settings().setValue("useExternalConfig", m_pRadioExternalConfig->isChecked()); - settings().setValue("configFile", m_pLineEditConfigFile->text()); - settings().setValue("useInternalConfig", m_pRadioInternalConfig->isChecked()); - settings().setValue("groupClientChecked", m_pGroupClient->isChecked()); - settings().setValue("serverHostname", m_pLineEditHostname->text()); + // program settings + settings().setValue("groupServerChecked", m_pGroupServer->isChecked()); + settings().setValue("useExternalConfig", m_pRadioExternalConfig->isChecked()); + settings().setValue("configFile", m_pLineEditConfigFile->text()); + settings().setValue("useInternalConfig", m_pRadioInternalConfig->isChecked()); + settings().setValue("groupClientChecked", m_pGroupClient->isChecked()); + settings().setValue("serverHostname", m_pLineEditHostname->text()); - settings().sync(); + settings().sync(); } void MainWindow::setIcon(qSynergyState state) { - QIcon icon; - icon.addFile(synergyIconFiles[state]); + QIcon icon; + icon.addFile(synergyIconFiles[state]); - if (m_pTrayIcon) - m_pTrayIcon->setIcon(icon); + if (m_pTrayIcon) + m_pTrayIcon->setIcon(icon); } void MainWindow::trayActivated(QSystemTrayIcon::ActivationReason reason) { #ifndef Q_OS_WIN - if (reason == QSystemTrayIcon::DoubleClick) - { - if (isVisible()) - { - hide(); - } - else - { - showNormal(); - activateWindow(); - } - } + if (reason == QSystemTrayIcon::DoubleClick) + { + if (isVisible()) + { + hide(); + } + else + { + showNormal(); + activateWindow(); + } + } #endif } void MainWindow::logOutput() { - if (m_pSynergy) - { - QString text(m_pSynergy->readAllStandardOutput()); - foreach(QString line, text.split(QRegExp("\r|\n|\r\n"))) - { - if (!line.isEmpty()) - { - appendLogRaw(line); - } - } - } + if (m_pSynergy) + { + QString text(m_pSynergy->readAllStandardOutput()); + foreach(QString line, text.split(QRegExp("\r|\n|\r\n"))) + { + if (!line.isEmpty()) + { + appendLogRaw(line); + } + } + } } void MainWindow::logError() { - if (m_pSynergy) - { - appendLogRaw(m_pSynergy->readAllStandardError()); - } + if (m_pSynergy) + { + appendLogRaw(m_pSynergy->readAllStandardError()); + } } void MainWindow::updateFound(const QString &version) { - m_pWidgetUpdate->show(); - m_pLabelUpdate->setText( - tr("

Your version of Synergy is out of date. " - "Version %1 is now available to " - "download.

") - .arg(version).arg(DOWNLOAD_URL)); + m_pWidgetUpdate->show(); + m_pLabelUpdate->setText( + tr("

Your version of Synergy is out of date. " + "Version %1 is now available to " + "download.

") + .arg(version).arg(DOWNLOAD_URL)); } void MainWindow::appendLogInfo(const QString& text) { - appendLogRaw(getTimeStamp() + " INFO: " + text); + appendLogRaw(getTimeStamp() + " INFO: " + text); } void MainWindow::appendLogDebug(const QString& text) { - if (appConfig().logLevel() >= 4) { - appendLogRaw(getTimeStamp() + " DEBUG: " + text); - } + if (appConfig().logLevel() >= 4) { + appendLogRaw(getTimeStamp() + " DEBUG: " + text); + } } void MainWindow::appendLogError(const QString& text) { - appendLogRaw(getTimeStamp() + " ERROR: " + text); + appendLogRaw(getTimeStamp() + " ERROR: " + text); } void MainWindow::appendLogRaw(const QString& text) { - foreach(QString line, text.split(QRegExp("\r|\n|\r\n"))) { - if (!line.isEmpty()) { - m_pLogOutput->append(line); - updateStateFromLogLine(line); - } - } + foreach(QString line, text.split(QRegExp("\r|\n|\r\n"))) { + if (!line.isEmpty()) { + m_pLogOutput->append(line); + updateStateFromLogLine(line); + } + } } void MainWindow::updateStateFromLogLine(const QString &line) { - checkConnected(line); - checkFingerprint(line); + checkConnected(line); + checkFingerprint(line); } void MainWindow::checkConnected(const QString& line) { - // TODO: implement ipc connection state messages to replace this hack. - if (line.contains("started server") || - line.contains("connected to server") || - line.contains("watchdog status: ok")) - { - setSynergyState(synergyConnected); + // TODO: implement ipc connection state messages to replace this hack. + if (line.contains("started server") || + line.contains("connected to server") || + line.contains("watchdog status: ok")) + { + setSynergyState(synergyConnected); - if (!appConfig().startedBefore() && isVisible()) { - QMessageBox::information( - this, "Synergy", - tr("Synergy is now connected. You can close the " - "config window and Synergy will remain connected in " - "the background.")); + if (!appConfig().startedBefore() && isVisible()) { + QMessageBox::information( + this, "Synergy", + tr("Synergy is now connected. You can close the " + "config window and Synergy will remain connected in " + "the background.")); - appConfig().setStartedBefore(true); - appConfig().saveSettings(); - } - } + appConfig().setStartedBefore(true); + appConfig().saveSettings(); + } + } } void MainWindow::checkFingerprint(const QString& line) { - QRegExp fingerprintRegex(".*server fingerprint: ([A-F0-9:]+)"); - if (!fingerprintRegex.exactMatch(line)) { - return; - } + QRegExp fingerprintRegex(".*server fingerprint: ([A-F0-9:]+)"); + if (!fingerprintRegex.exactMatch(line)) { + return; + } - QString fingerprint = fingerprintRegex.cap(1); - if (Fingerprint::trustedServers().isTrusted(fingerprint)) { - return; - } + QString fingerprint = fingerprintRegex.cap(1); + if (Fingerprint::trustedServers().isTrusted(fingerprint)) { + return; + } - static bool messageBoxAlreadyShown = false; + static bool messageBoxAlreadyShown = false; - if (!messageBoxAlreadyShown) { - stopSynergy(); + if (!messageBoxAlreadyShown) { + stopSynergy(); - messageBoxAlreadyShown = true; - QMessageBox::StandardButton fingerprintReply = - QMessageBox::information( - this, tr("Security question"), - tr("Do you trust this fingerprint?\n\n" - "%1\n\n" - "This is a server fingerprint. You should compare this " - "fingerprint to the one on your server's screen. If the " - "two don't match exactly, then it's probably not the server " - "you're expecting (it could be a malicious user).\n\n" - "To automatically trust this fingerprint for future " - "connections, click Yes. To reject this fingerprint and " - "disconnect from the server, click No.") - .arg(fingerprint), - QMessageBox::Yes | QMessageBox::No); + messageBoxAlreadyShown = true; + QMessageBox::StandardButton fingerprintReply = + QMessageBox::information( + this, tr("Security question"), + tr("Do you trust this fingerprint?\n\n" + "%1\n\n" + "This is a server fingerprint. You should compare this " + "fingerprint to the one on your server's screen. If the " + "two don't match exactly, then it's probably not the server " + "you're expecting (it could be a malicious user).\n\n" + "To automatically trust this fingerprint for future " + "connections, click Yes. To reject this fingerprint and " + "disconnect from the server, click No.") + .arg(fingerprint), + QMessageBox::Yes | QMessageBox::No); - if (fingerprintReply == QMessageBox::Yes) { - // restart core process after trusting fingerprint. - Fingerprint::trustedServers().trust(fingerprint); - startSynergy(); - } + if (fingerprintReply == QMessageBox::Yes) { + // restart core process after trusting fingerprint. + Fingerprint::trustedServers().trust(fingerprint); + startSynergy(); + } - messageBoxAlreadyShown = false; - } + messageBoxAlreadyShown = false; + } } bool MainWindow::autoHide() { - if ((appConfig().processMode() == Desktop) && - appConfig().getAutoHide()) { - hide(); - return true; - } + if ((appConfig().processMode() == Desktop) && + appConfig().getAutoHide()) { + hide(); + return true; + } - return false; + return false; } QString MainWindow::getTimeStamp() { - QDateTime current = QDateTime::currentDateTime(); - return '[' + current.toString(Qt::ISODate) + ']'; + QDateTime current = QDateTime::currentDateTime(); + return '[' + current.toString(Qt::ISODate) + ']'; } void MainWindow::restartSynergy() { - stopSynergy(); - startSynergy(); + stopSynergy(); + startSynergy(); } void MainWindow::proofreadInfo() { - setEdition(m_AppConfig->edition()); // Why is this here? + setEdition(m_AppConfig->edition()); // Why is this here? - int oldState = m_SynergyState; - m_SynergyState = synergyDisconnected; - setSynergyState((qSynergyState)oldState); + int oldState = m_SynergyState; + m_SynergyState = synergyDisconnected; + setSynergyState((qSynergyState)oldState); } void MainWindow::showEvent(QShowEvent* event) { - QMainWindow::showEvent(event); - emit windowShown(); + QMainWindow::showEvent(event); + emit windowShown(); } void MainWindow::clearLog() { - m_pLogOutput->clear(); + m_pLogOutput->clear(); } void MainWindow::startSynergy() { - bool desktopMode = appConfig().processMode() == Desktop; - bool serviceMode = appConfig().processMode() == Service; + bool desktopMode = appConfig().processMode() == Desktop; + bool serviceMode = appConfig().processMode() == Service; - appendLogDebug("starting process"); - m_ExpectedRunningState = kStarted; - setSynergyState(synergyConnecting); + appendLogDebug("starting process"); + m_ExpectedRunningState = kStarted; + setSynergyState(synergyConnecting); - QString app; - QStringList args; + QString app; + QStringList args; - args << "-f" << "--no-tray" << "--debug" << appConfig().logLevelText(); + args << "-f" << "--no-tray" << "--debug" << appConfig().logLevelText(); - args << "--name" << getScreenName(); + args << "--name" << getScreenName(); if (!appConfig().serialKey().isEmpty()) { - args << "--serial-key " << appConfig().serialKey(); + args << "--serial-key" << appConfig().serialKey(); } - if (desktopMode) - { - setSynergyProcess(new QProcess(this)); - } - else - { - // tell client/server to talk to daemon through ipc. - args << "--ipc"; + if (desktopMode) + { + setSynergyProcess(new QProcess(this)); + } + else + { + // tell client/server to talk to daemon through ipc. + args << "--ipc"; #if defined(Q_OS_WIN) - // tell the client/server to shut down when a ms windows desk - // is switched; this is because we may need to elevate or not - // based on which desk the user is in (login always needs - // elevation, where as default desk does not). - // Note that this is only enabled when synergy is set to elevate - // 'as needed' (e.g. on a UAC dialog popup) in order to prevent - // unnecessary restarts when synergy was started elevated or - // when it is not allowed to elevate. In these cases restarting - // the server is fruitless. - if (appConfig().elevateMode() == ElevateAsNeeded) { - args << "--stop-on-desk-switch"; - } + // tell the client/server to shut down when a ms windows desk + // is switched; this is because we may need to elevate or not + // based on which desk the user is in (login always needs + // elevation, where as default desk does not). + // Note that this is only enabled when synergy is set to elevate + // 'as needed' (e.g. on a UAC dialog popup) in order to prevent + // unnecessary restarts when synergy was started elevated or + // when it is not allowed to elevate. In these cases restarting + // the server is fruitless. + if (appConfig().elevateMode() == ElevateAsNeeded) { + args << "--stop-on-desk-switch"; + } #endif - } + } #ifndef Q_OS_LINUX - if (m_ServerConfig.enableDragAndDrop()) { - args << "--enable-drag-drop"; - } + if (m_ServerConfig.enableDragAndDrop()) { + args << "--enable-drag-drop"; + } #endif - if (m_AppConfig->getCryptoEnabled()) { - args << "--enable-crypto"; - } + if (m_AppConfig->getCryptoEnabled()) { + args << "--enable-crypto"; + } #if defined(Q_OS_WIN) - // on windows, the profile directory changes depending on the user that - // launched the process (e.g. when launched with elevation). setting the - // profile dir on launch ensures it uses the same profile dir is used - // no matter how its relaunched. - args << "--profile-dir" << getProfileRootForArg(); + // on windows, the profile directory changes depending on the user that + // launched the process (e.g. when launched with elevation). setting the + // profile dir on launch ensures it uses the same profile dir is used + // no matter how its relaunched. + args << "--profile-dir" << getProfileRootForArg(); #endif - if ((synergyType() == synergyClient && !clientArgs(args, app)) - || (synergyType() == synergyServer && !serverArgs(args, app))) - { - stopSynergy(); - return; - } + if ((synergyType() == synergyClient && !clientArgs(args, app)) + || (synergyType() == synergyServer && !serverArgs(args, app))) + { + stopSynergy(); + return; + } - if (desktopMode) - { - connect(synergyProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(synergyFinished(int, QProcess::ExitStatus))); - connect(synergyProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(logOutput())); - connect(synergyProcess(), SIGNAL(readyReadStandardError()), this, SLOT(logError())); - } + if (desktopMode) + { + connect(synergyProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(synergyFinished(int, QProcess::ExitStatus))); + connect(synergyProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(logOutput())); + connect(synergyProcess(), SIGNAL(readyReadStandardError()), this, SLOT(logError())); + } - // put a space between last log output and new instance. - if (!m_pLogOutput->toPlainText().isEmpty()) - appendLogRaw(""); + // put a space between last log output and new instance. + if (!m_pLogOutput->toPlainText().isEmpty()) + appendLogRaw(""); - appendLogInfo("starting " + QString(synergyType() == synergyServer ? "server" : "client")); + appendLogInfo("starting " + QString(synergyType() == synergyServer ? "server" : "client")); - qDebug() << args; + qDebug() << args; - // show command if debug log level... - if (appConfig().logLevel() >= 4) { - appendLogInfo(QString("command: %1 %2").arg(app, args.join(" "))); - } + // show command if debug log level... + if (appConfig().logLevel() >= 4) { + appendLogInfo(QString("command: %1 %2").arg(app, args.join(" "))); + } - appendLogInfo("config file: " + configFilename()); - appendLogInfo("log level: " + appConfig().logLevelText()); + appendLogInfo("config file: " + configFilename()); + appendLogInfo("log level: " + appConfig().logLevelText()); - if (appConfig().logToFile()) - appendLogInfo("log file: " + appConfig().logFilename()); + if (appConfig().logToFile()) + appendLogInfo("log file: " + appConfig().logFilename()); - if (desktopMode) - { - synergyProcess()->start(app, args); - if (!synergyProcess()->waitForStarted()) - { - show(); - QMessageBox::warning(this, tr("Program can not be started"), QString(tr("The executable

%1

could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.").arg(app))); - return; - } - } + if (desktopMode) + { + synergyProcess()->start(app, args); + if (!synergyProcess()->waitForStarted()) + { + show(); + QMessageBox::warning(this, tr("Program can not be started"), QString(tr("The executable

%1

could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.").arg(app))); + return; + } + } - if (serviceMode) - { - QString command(app + " " + args.join(" ")); - m_IpcClient.sendCommand(command, appConfig().elevateMode()); - } + if (serviceMode) + { + QString command(app + " " + args.join(" ")); + m_IpcClient.sendCommand(command, appConfig().elevateMode()); + } } void MainWindow::sslToggled (bool enabled) { - if (enabled) { - m_pSslCertificate = new SslCertificate(this); - m_pSslCertificate->generateCertificate(); - } - updateLocalFingerprint(); + if (enabled) { + m_pSslCertificate = new SslCertificate(this); + m_pSslCertificate->generateCertificate(); + } + updateLocalFingerprint(); } bool MainWindow::clientArgs(QStringList& args, QString& app) { - app = appPath(appConfig().synergycName()); + app = appPath(appConfig().synergycName()); - if (!QFile::exists(app)) - { - show(); - QMessageBox::warning(this, tr("Synergy client not found"), - tr("The executable for the synergy client does not exist.")); - return false; - } + if (!QFile::exists(app)) + { + show(); + QMessageBox::warning(this, tr("Synergy client not found"), + tr("The executable for the synergy client does not exist.")); + return false; + } #if defined(Q_OS_WIN) - // wrap in quotes so a malicious user can't start \Program.exe as admin. - app = QString("\"%1\"").arg(app); + // wrap in quotes so a malicious user can't start \Program.exe as admin. + app = QString("\"%1\"").arg(app); #endif - if (appConfig().logToFile()) - { - appConfig().persistLogDir(); - args << "--log" << appConfig().logFilenameCmd(); - } + if (appConfig().logToFile()) + { + appConfig().persistLogDir(); + args << "--log" << appConfig().logFilenameCmd(); + } - // check auto config first, if it is disabled or no server detected, - // use line edit host name if it is not empty - if (m_pCheckBoxAutoConfig->isChecked()) { - if (m_pComboServerList->count() != 0) { - QString serverIp = m_pComboServerList->currentText(); - args << serverIp + ":" + QString::number(appConfig().port()); - return true; - } - } + // check auto config first, if it is disabled or no server detected, + // use line edit host name if it is not empty + if (m_pCheckBoxAutoConfig->isChecked()) { + if (m_pComboServerList->count() != 0) { + QString serverIp = m_pComboServerList->currentText(); + args << serverIp + ":" + QString::number(appConfig().port()); + return true; + } + } - if (m_pLineEditHostname->text().isEmpty()) { - show(); - if (!m_SuppressEmptyServerWarning) { - QMessageBox::warning(this, tr("Hostname is empty"), - tr("Please fill in a hostname for the synergy client to connect to.")); - } - return false; - } + if (m_pLineEditHostname->text().isEmpty()) { + show(); + if (!m_SuppressEmptyServerWarning) { + QMessageBox::warning(this, tr("Hostname is empty"), + tr("Please fill in a hostname for the synergy client to connect to.")); + } + return false; + } - args << m_pLineEditHostname->text() + ":" + QString::number(appConfig().port()); + args << m_pLineEditHostname->text() + ":" + QString::number(appConfig().port()); - return true; + return true; } QString MainWindow::configFilename() { - QString filename; - if (m_pRadioInternalConfig->isChecked()) - { - // TODO: no need to use a temporary file, since we need it to - // be permenant (since it'll be used for Windows services, etc). - m_pTempConfigFile = new QTemporaryFile(); - if (!m_pTempConfigFile->open()) - { - QMessageBox::critical(this, tr("Cannot write configuration file"), tr("The temporary configuration file required to start synergy can not be written.")); - return ""; - } + QString filename; + if (m_pRadioInternalConfig->isChecked()) + { + // TODO: no need to use a temporary file, since we need it to + // be permenant (since it'll be used for Windows services, etc). + m_pTempConfigFile = new QTemporaryFile(); + if (!m_pTempConfigFile->open()) + { + QMessageBox::critical(this, tr("Cannot write configuration file"), tr("The temporary configuration file required to start synergy can not be written.")); + return ""; + } - serverConfig().save(*m_pTempConfigFile); - filename = m_pTempConfigFile->fileName(); + serverConfig().save(*m_pTempConfigFile); + filename = m_pTempConfigFile->fileName(); - m_pTempConfigFile->close(); - } - else - { - if (!QFile::exists(m_pLineEditConfigFile->text())) - { - if (QMessageBox::warning(this, tr("Configuration filename invalid"), - tr("You have not filled in a valid configuration file for the synergy server. " - "Do you want to browse for the configuration file now?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes - || !on_m_pButtonBrowseConfigFile_clicked()) - return ""; - } + m_pTempConfigFile->close(); + } + else + { + if (!QFile::exists(m_pLineEditConfigFile->text())) + { + if (QMessageBox::warning(this, tr("Configuration filename invalid"), + tr("You have not filled in a valid configuration file for the synergy server. " + "Do you want to browse for the configuration file now?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes + || !on_m_pButtonBrowseConfigFile_clicked()) + return ""; + } - filename = m_pLineEditConfigFile->text(); - } - return filename; + filename = m_pLineEditConfigFile->text(); + } + return filename; } QString MainWindow::address() { - QString i = appConfig().interface(); - return (!i.isEmpty() ? i : "") + ":" + QString::number(appConfig().port()); + QString i = appConfig().interface(); + return (!i.isEmpty() ? i : "") + ":" + QString::number(appConfig().port()); } QString MainWindow::appPath(const QString& name) { - return appConfig().synergyProgramDir() + name; + return appConfig().synergyProgramDir() + name; } bool MainWindow::serverArgs(QStringList& args, QString& app) { - app = appPath(appConfig().synergysName()); + app = appPath(appConfig().synergysName()); - if (!QFile::exists(app)) - { - QMessageBox::warning(this, tr("Synergy server not found"), - tr("The executable for the synergy server does not exist.")); - return false; - } + if (!QFile::exists(app)) + { + QMessageBox::warning(this, tr("Synergy server not found"), + tr("The executable for the synergy server does not exist.")); + return false; + } #if defined(Q_OS_WIN) - // wrap in quotes so a malicious user can't start \Program.exe as admin. - app = QString("\"%1\"").arg(app); + // wrap in quotes so a malicious user can't start \Program.exe as admin. + app = QString("\"%1\"").arg(app); #endif - if (appConfig().logToFile()) - { - appConfig().persistLogDir(); + if (appConfig().logToFile()) + { + appConfig().persistLogDir(); - args << "--log" << appConfig().logFilenameCmd(); - } + args << "--log" << appConfig().logFilenameCmd(); + } - QString configFilename = this->configFilename(); + QString configFilename = this->configFilename(); #if defined(Q_OS_WIN) - // wrap in quotes in case username contains spaces. - configFilename = QString("\"%1\"").arg(configFilename); + // wrap in quotes in case username contains spaces. + configFilename = QString("\"%1\"").arg(configFilename); #endif - args << "-c" << configFilename << "--address" << address(); + args << "-c" << configFilename << "--address" << address(); #if defined(Q_OS_WIN) - // pass in physical resolution and primary screen center - // TODO: get this information in the core binary even when - // high DPI is used - int height = QApplication::desktop()->height(); - int width = QApplication::desktop()->width(); + // pass in physical resolution and primary screen center + // TODO: get this information in the core binary even when + // high DPI is used + int height = QApplication::desktop()->height(); + int width = QApplication::desktop()->width(); - QRect rec = QApplication::desktop()->screenGeometry(); - int heightCenter = rec.height() / 2; - int widthCenter = rec.width() / 2; + QRect rec = QApplication::desktop()->screenGeometry(); + int heightCenter = rec.height() / 2; + int widthCenter = rec.width() / 2; - appendLogDebug(tr("screen resolution: %1 %2 primary screen center: %3 %4") - .arg(width).arg(height).arg(widthCenter).arg(heightCenter)); + appendLogDebug(tr("screen resolution: %1 %2 primary screen center: %3 %4") + .arg(width).arg(height).arg(widthCenter).arg(heightCenter)); - args << "--res-w" << QString::number(width); - args << "--res-h" << QString::number(height); - args << "--prm-wc" << QString::number(widthCenter); - args << "--prm-hc" << QString::number(heightCenter); + args << "--res-w" << QString::number(width); + args << "--res-h" << QString::number(height); + args << "--prm-wc" << QString::number(widthCenter); + args << "--prm-hc" << QString::number(heightCenter); #endif - return true; + return true; } void MainWindow::stopSynergy() { - appendLogDebug("stopping process"); + appendLogDebug("stopping process"); - m_ExpectedRunningState = kStopped; + m_ExpectedRunningState = kStopped; - if (appConfig().processMode() == Service) - { - stopService(); - } - else if (appConfig().processMode() == Desktop) - { - stopDesktop(); - } + if (appConfig().processMode() == Service) + { + stopService(); + } + else if (appConfig().processMode() == Desktop) + { + stopDesktop(); + } - setSynergyState(synergyDisconnected); + setSynergyState(synergyDisconnected); - // HACK: deleting the object deletes the physical file, which is - // bad, since it could be in use by the Windows service! - //delete m_pTempConfigFile; - m_pTempConfigFile = NULL; + // HACK: deleting the object deletes the physical file, which is + // bad, since it could be in use by the Windows service! + //delete m_pTempConfigFile; + m_pTempConfigFile = NULL; - // reset so that new connects cause auto-hide. - m_AlreadyHidden = false; + // reset so that new connects cause auto-hide. + m_AlreadyHidden = false; } void MainWindow::stopService() { - // send empty command to stop service from laucning anything. - m_IpcClient.sendCommand("", appConfig().elevateMode()); + // send empty command to stop service from laucning anything. + m_IpcClient.sendCommand("", appConfig().elevateMode()); } void MainWindow::stopDesktop() { - QMutexLocker locker(&m_StopDesktopMutex); - if (!synergyProcess()) { - return; - } + QMutexLocker locker(&m_StopDesktopMutex); + if (!synergyProcess()) { + return; + } - appendLogInfo("stopping synergy desktop process"); + appendLogInfo("stopping synergy desktop process"); - if (synergyProcess()->isOpen()) { - synergyProcess()->close(); - } + if (synergyProcess()->isOpen()) { + synergyProcess()->close(); + } - delete synergyProcess(); - setSynergyProcess(NULL); + delete synergyProcess(); + setSynergyProcess(NULL); } void MainWindow::synergyFinished(int exitCode, QProcess::ExitStatus) { - if (exitCode == 0) { - appendLogInfo(QString("process exited normally")); - } - else { - appendLogError(QString("process exited with error code: %1").arg(exitCode)); - } + if (exitCode == 0) { + appendLogInfo(QString("process exited normally")); + } + else { + appendLogError(QString("process exited with error code: %1").arg(exitCode)); + } - if (m_ExpectedRunningState == kStarted) { - QTimer::singleShot(1000, this, SLOT(startSynergy())); - appendLogInfo(QString("detected process not running, auto restarting")); - } - else { - setSynergyState(synergyDisconnected); - } + if (m_ExpectedRunningState == kStarted) { + QTimer::singleShot(1000, this, SLOT(startSynergy())); + appendLogInfo(QString("detected process not running, auto restarting")); + } + else { + setSynergyState(synergyDisconnected); + } } void MainWindow::setSynergyState(qSynergyState state) { - if (synergyState() == state) - return; + if (synergyState() == state) + return; - if (state == synergyConnected || state == synergyConnecting) - { - disconnect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); - connect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStopSynergy, SLOT(trigger())); - m_pButtonToggleStart->setText(tr("&Stop")); - m_pButtonApply->setEnabled(true); - } - else if (state == synergyDisconnected) - { - disconnect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStopSynergy, SLOT(trigger())); - connect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); - m_pButtonToggleStart->setText(tr("&Start")); - m_pButtonApply->setEnabled(false); - } + if (state == synergyConnected || state == synergyConnecting) + { + disconnect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); + connect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStopSynergy, SLOT(trigger())); + m_pButtonToggleStart->setText(tr("&Stop")); + m_pButtonApply->setEnabled(true); + } + else if (state == synergyDisconnected) + { + disconnect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStopSynergy, SLOT(trigger())); + connect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger())); + m_pButtonToggleStart->setText(tr("&Start")); + m_pButtonApply->setEnabled(false); + } - bool connected = false; - if (state == synergyConnected || state == synergyTransfering) { - connected = true; - } + bool connected = false; + if (state == synergyConnected || state == synergyTransfering) { + connected = true; + } - m_pActionStartSynergy->setEnabled(!connected); - m_pActionStopSynergy->setEnabled(connected); + m_pActionStartSynergy->setEnabled(!connected); + m_pActionStopSynergy->setEnabled(connected); - switch (state) - { - case synergyConnected: { - if (m_AppConfig->getCryptoEnabled()) { - m_pLabelPadlock->show(); - } - else { - m_pLabelPadlock->hide(); - } + switch (state) + { + case synergyConnected: { + if (m_AppConfig->getCryptoEnabled()) { + m_pLabelPadlock->show(); + } + else { + m_pLabelPadlock->hide(); + } - setStatus(tr("Synergy is running.")); + setStatus(tr("Synergy is running.")); - break; - } - case synergyConnecting: - m_pLabelPadlock->hide(); - setStatus(tr("Synergy is starting.")); - break; - case synergyDisconnected: - m_pLabelPadlock->hide(); - setStatus(tr("Synergy is not running.")); - break; - case synergyTransfering: - break; - } + break; + } + case synergyConnecting: + m_pLabelPadlock->hide(); + setStatus(tr("Synergy is starting.")); + break; + case synergyDisconnected: + m_pLabelPadlock->hide(); + setStatus(tr("Synergy is not running.")); + break; + case synergyTransfering: + break; + } - setIcon(state); + setIcon(state); - m_SynergyState = state; + m_SynergyState = state; } void MainWindow::setVisible(bool visible) { - QMainWindow::setVisible(visible); - m_pActionMinimize->setEnabled(visible); - m_pActionRestore->setEnabled(!visible); + QMainWindow::setVisible(visible); + m_pActionMinimize->setEnabled(visible); + m_pActionRestore->setEnabled(!visible); #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 // lion - // dock hide only supported on lion :( - ProcessSerialNumber psn = { 0, kCurrentProcess }; - GetCurrentProcess(&psn); - if (visible) - TransformProcessType(&psn, kProcessTransformToForegroundApplication); - else - TransformProcessType(&psn, kProcessTransformToBackgroundApplication); + // dock hide only supported on lion :( + ProcessSerialNumber psn = { 0, kCurrentProcess }; + GetCurrentProcess(&psn); + if (visible) + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + else + TransformProcessType(&psn, kProcessTransformToBackgroundApplication); #endif } QString MainWindow::getIPAddresses() { - QList addresses = QNetworkInterface::allAddresses(); + QList addresses = QNetworkInterface::allAddresses(); - bool hinted = false; - QString result; - for (int i = 0; i < addresses.size(); i++) { - if (addresses[i].protocol() == QAbstractSocket::IPv4Protocol && - addresses[i] != QHostAddress(QHostAddress::LocalHost)) { + bool hinted = false; + QString result; + for (int i = 0; i < addresses.size(); i++) { + if (addresses[i].protocol() == QAbstractSocket::IPv4Protocol && + addresses[i] != QHostAddress(QHostAddress::LocalHost)) { - QString address = addresses[i].toString(); - QString format = "%1, "; + QString address = addresses[i].toString(); + QString format = "%1, "; - // usually 192.168.x.x is a useful ip for the user, so indicate - // this by making it bold. - if (!hinted && address.startsWith("192.168")) { - hinted = true; - format = "%1, "; - } + // usually 192.168.x.x is a useful ip for the user, so indicate + // this by making it bold. + if (!hinted && address.startsWith("192.168")) { + hinted = true; + format = "%1, "; + } - result += format.arg(address); - } - } + result += format.arg(address); + } + } - if (result == "") { - return tr("Unknown"); - } + if (result == "") { + return tr("Unknown"); + } - // remove trailing comma. - result.chop(2); + // remove trailing comma. + result.chop(2); - return result; + return result; } QString MainWindow::getScreenName() { - if (appConfig().screenName() == "") { - return QHostInfo::localHostName(); - } - else { - return appConfig().screenName(); - } + if (appConfig().screenName() == "") { + return QHostInfo::localHostName(); + } + else { + return appConfig().screenName(); + } } void MainWindow::changeEvent(QEvent* event) { - if (event != 0) - { - switch (event->type()) - { - case QEvent::LanguageChange: - { - retranslateUi(this); - retranslateMenuBar(); + if (event != 0) + { + switch (event->type()) + { + case QEvent::LanguageChange: + { + retranslateUi(this); + retranslateMenuBar(); - proofreadInfo(); + proofreadInfo(); - break; - } - default: - QMainWindow::changeEvent(event); - } - } + break; + } + default: + QMainWindow::changeEvent(event); + } + } } void MainWindow::updateZeroconfService() { - QMutexLocker locker(&m_UpdateZeroconfMutex); + QMutexLocker locker(&m_UpdateZeroconfMutex); - if (isBonjourRunning()) { - if (!m_AppConfig->wizardShouldRun()) { - if (m_pZeroconfService) { - delete m_pZeroconfService; - m_pZeroconfService = NULL; - } + if (isBonjourRunning()) { + if (!m_AppConfig->wizardShouldRun()) { + if (m_pZeroconfService) { + delete m_pZeroconfService; + m_pZeroconfService = NULL; + } - if (m_AppConfig->autoConfig() || synergyType() == synergyServer) { - m_pZeroconfService = new ZeroconfService(this); - } - } - } + if (m_AppConfig->autoConfig() || synergyType() == synergyServer) { + m_pZeroconfService = new ZeroconfService(this); + } + } + } } void MainWindow::serverDetected(const QString name) { - if (m_pComboServerList->findText(name) == -1) { - // Note: the first added item triggers startSynergy - m_pComboServerList->addItem(name); - } + if (m_pComboServerList->findText(name) == -1) { + // Note: the first added item triggers startSynergy + m_pComboServerList->addItem(name); + } - if (m_pComboServerList->count() > 1) { - m_pComboServerList->show(); - } + if (m_pComboServerList->count() > 1) { + m_pComboServerList->show(); + } } void MainWindow::setEdition(Edition edition) { - setWindowTitle(m_SubscriptionManager->getEditionName (edition)); - if (m_AppConfig->getCryptoEnabled()) { - m_pSslCertificate = new SslCertificate(this); - m_pSslCertificate->generateCertificate(); - } - updateLocalFingerprint(); - saveSettings(); + setWindowTitle(m_SubscriptionManager->getEditionName (edition)); + if (m_AppConfig->getCryptoEnabled()) { + m_pSslCertificate = new SslCertificate(this); + m_pSslCertificate->generateCertificate(); + } + updateLocalFingerprint(); + saveSettings(); } void MainWindow::beginTrial(bool isExpiring) { - if (isExpiring) { + if (isExpiring) { QString expiringNotice = "

%1 days of " "your Synergy Pro trial remain. setText(fileName); - return true; - } + if (!fileName.isEmpty()) + { + m_pLineEditConfigFile->setText(fileName); + return true; + } - return false; + return false; } bool MainWindow::on_m_pActionSave_triggered() { - QString fileName = QFileDialog::getSaveFileName(this, tr("Save configuration as...")); + QString fileName = QFileDialog::getSaveFileName(this, tr("Save configuration as...")); - if (!fileName.isEmpty() && !serverConfig().save(fileName)) - { - QMessageBox::warning(this, tr("Save failed"), tr("Could not save configuration to file.")); - return true; - } + if (!fileName.isEmpty() && !serverConfig().save(fileName)) + { + QMessageBox::warning(this, tr("Save failed"), tr("Could not save configuration to file.")); + return true; + } - return false; + return false; } void MainWindow::on_m_pActionAbout_triggered() { - AboutDialog dlg(this, appPath(appConfig().synergycName())); - dlg.exec(); + AboutDialog dlg(this, appPath(appConfig().synergycName())); + dlg.exec(); } void MainWindow::on_m_pActionSettings_triggered() { - ProcessMode lastProcessMode = appConfig().processMode(); + ProcessMode lastProcessMode = appConfig().processMode(); - SettingsDialog dlg(this, appConfig()); - dlg.exec(); + SettingsDialog dlg(this, appConfig()); + dlg.exec(); - if (lastProcessMode != appConfig().processMode()) - { - onModeChanged(true, true); - } + if (lastProcessMode != appConfig().processMode()) + { + onModeChanged(true, true); + } } void MainWindow::autoAddScreen(const QString name) { - if (!m_ServerConfig.ignoreAutoConfigClient()) { - int r = m_ServerConfig.autoAddScreen(name); - if (r != kAutoAddScreenOk) { - switch (r) { - case kAutoAddScreenManualServer: - showConfigureServer( - tr("Please add the server (%1) to the grid.") - .arg(appConfig().screenName())); - break; + if (!m_ServerConfig.ignoreAutoConfigClient()) { + int r = m_ServerConfig.autoAddScreen(name); + if (r != kAutoAddScreenOk) { + switch (r) { + case kAutoAddScreenManualServer: + showConfigureServer( + tr("Please add the server (%1) to the grid.") + .arg(appConfig().screenName())); + break; - case kAutoAddScreenManualClient: - showConfigureServer( - tr("Please drag the new client screen (%1) " - "to the desired position on the grid.") - .arg(name)); - break; - } - } - else { - restartSynergy(); - } - } + case kAutoAddScreenManualClient: + showConfigureServer( + tr("Please drag the new client screen (%1) " + "to the desired position on the grid.") + .arg(name)); + break; + } + } + else { + restartSynergy(); + } + } } void MainWindow::showConfigureServer(const QString& message) { - ServerConfigDialog dlg(this, serverConfig(), appConfig().screenName()); - dlg.message(message); - dlg.exec(); + ServerConfigDialog dlg(this, serverConfig(), appConfig().screenName()); + dlg.message(message); + dlg.exec(); } void MainWindow::on_m_pButtonConfigureServer_clicked() { - showConfigureServer(); + showConfigureServer(); } void MainWindow::on_m_pActivate_triggered() { - ActivationDialog activationDialog(this, appConfig(), subscriptionManager()); - activationDialog.exec(); + ActivationDialog activationDialog(this, appConfig(), subscriptionManager()); + activationDialog.exec(); } void MainWindow::on_m_pButtonApply_clicked() { - restartSynergy(); + restartSynergy(); } #if defined(Q_OS_WIN) bool MainWindow::isServiceRunning(QString name) { - SC_HANDLE hSCManager; - hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); - if (hSCManager == NULL) { - appendLogError("failed to open a service controller manager, error: " + - GetLastError()); - return false; - } + SC_HANDLE hSCManager; + hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + if (hSCManager == NULL) { + appendLogError("failed to open a service controller manager, error: " + + GetLastError()); + return false; + } - SC_HANDLE hService; - int length = name.length(); - wchar_t* array = new wchar_t[length + 1]; - name.toWCharArray(array); - array[length] = '\0'; + SC_HANDLE hService; + int length = name.length(); + wchar_t* array = new wchar_t[length + 1]; + name.toWCharArray(array); + array[length] = '\0'; - hService = OpenService(hSCManager, array, SERVICE_QUERY_STATUS); + hService = OpenService(hSCManager, array, SERVICE_QUERY_STATUS); - delete[] array; + delete[] array; - if (hService == NULL) { - appendLogDebug("failed to open service: " + name); - return false; - } + if (hService == NULL) { + appendLogDebug("failed to open service: " + name); + return false; + } - SERVICE_STATUS status; - if (QueryServiceStatus(hService, &status)) { - if (status.dwCurrentState == SERVICE_RUNNING) { - return true; - } - } + SERVICE_STATUS status; + if (QueryServiceStatus(hService, &status)) { + if (status.dwCurrentState == SERVICE_RUNNING) { + return true; + } + } #else bool MainWindow::isServiceRunning() { #endif - return false; + return false; } bool MainWindow::isBonjourRunning() { - bool result = false; + bool result = false; #if defined(Q_OS_WIN) - result = isServiceRunning("Bonjour Service"); + result = isServiceRunning("Bonjour Service"); #else - result = true; + result = true; #endif - return result; + return result; } void MainWindow::downloadBonjour() { #if defined(Q_OS_WIN) - QUrl url; - int arch = getProcessorArch(); - if (arch == kProcessorArchWin32) { - url.setUrl(bonjourBaseUrl + bonjourFilename32); - appendLogInfo("downloading 32-bit Bonjour"); - } - else if (arch == kProcessorArchWin64) { - url.setUrl(bonjourBaseUrl + bonjourFilename64); - appendLogInfo("downloading 64-bit Bonjour"); - } - else { - QMessageBox::critical( - this, tr("Synergy"), - tr("Failed to detect system architecture.")); - return; - } + QUrl url; + int arch = getProcessorArch(); + if (arch == kProcessorArchWin32) { + url.setUrl(bonjourBaseUrl + bonjourFilename32); + appendLogInfo("downloading 32-bit Bonjour"); + } + else if (arch == kProcessorArchWin64) { + url.setUrl(bonjourBaseUrl + bonjourFilename64); + appendLogInfo("downloading 64-bit Bonjour"); + } + else { + QMessageBox::critical( + this, tr("Synergy"), + tr("Failed to detect system architecture.")); + return; + } - if (m_pDataDownloader == NULL) { - m_pDataDownloader = new DataDownloader(this); - connect(m_pDataDownloader, SIGNAL(isComplete()), SLOT(installBonjour())); - } + if (m_pDataDownloader == NULL) { + m_pDataDownloader = new DataDownloader(this); + connect(m_pDataDownloader, SIGNAL(isComplete()), SLOT(installBonjour())); + } - m_pDataDownloader->download(url); + m_pDataDownloader->download(url); - if (m_DownloadMessageBox == NULL) { - m_DownloadMessageBox = new QMessageBox(this); - m_DownloadMessageBox->setWindowTitle("Synergy"); - m_DownloadMessageBox->setIcon(QMessageBox::Information); - m_DownloadMessageBox->setText("Installing Bonjour, please wait..."); - m_DownloadMessageBox->setStandardButtons(0); - m_pCancelButton = m_DownloadMessageBox->addButton( - tr("Cancel"), QMessageBox::RejectRole); - } + if (m_DownloadMessageBox == NULL) { + m_DownloadMessageBox = new QMessageBox(this); + m_DownloadMessageBox->setWindowTitle("Synergy"); + m_DownloadMessageBox->setIcon(QMessageBox::Information); + m_DownloadMessageBox->setText("Installing Bonjour, please wait..."); + m_DownloadMessageBox->setStandardButtons(0); + m_pCancelButton = m_DownloadMessageBox->addButton( + tr("Cancel"), QMessageBox::RejectRole); + } - m_DownloadMessageBox->exec(); + m_DownloadMessageBox->exec(); - if (m_DownloadMessageBox->clickedButton() == m_pCancelButton) { - m_pDataDownloader->cancel(); - } + if (m_DownloadMessageBox->clickedButton() == m_pCancelButton) { + m_pDataDownloader->cancel(); + } #endif } void MainWindow::installBonjour() { #if defined(Q_OS_WIN) - QString tempLocation = QDesktopServices::storageLocation( - QDesktopServices::TempLocation); - QString filename = tempLocation; - filename.append("\\").append(bonjourTargetFilename); - QFile file(filename); - if (!file.open(QIODevice::WriteOnly)) { - m_DownloadMessageBox->hide(); + QString tempLocation = QDesktopServices::storageLocation( + QDesktopServices::TempLocation); + QString filename = tempLocation; + filename.append("\\").append(bonjourTargetFilename); + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) { + m_DownloadMessageBox->hide(); - QMessageBox::warning( - this, "Synergy", - tr("Failed to download Bonjour installer to location: %1") - .arg(tempLocation)); - return; - } + QMessageBox::warning( + this, "Synergy", + tr("Failed to download Bonjour installer to location: %1") + .arg(tempLocation)); + return; + } - file.write(m_pDataDownloader->data()); - file.close(); + file.write(m_pDataDownloader->data()); + file.close(); - QStringList arguments; - arguments.append("/i"); - QString winFilename = QDir::toNativeSeparators(filename); - arguments.append(winFilename); - arguments.append("/passive"); - if (m_BonjourInstall == NULL) { - m_BonjourInstall = new CommandProcess("msiexec", arguments); - } + QStringList arguments; + arguments.append("/i"); + QString winFilename = QDir::toNativeSeparators(filename); + arguments.append(winFilename); + arguments.append("/passive"); + if (m_BonjourInstall == NULL) { + m_BonjourInstall = new CommandProcess("msiexec", arguments); + } - QThread* thread = new QThread; - connect(m_BonjourInstall, SIGNAL(finished()), this, - SLOT(bonjourInstallFinished())); - connect(m_BonjourInstall, SIGNAL(finished()), thread, SLOT(quit())); - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + QThread* thread = new QThread; + connect(m_BonjourInstall, SIGNAL(finished()), this, + SLOT(bonjourInstallFinished())); + connect(m_BonjourInstall, SIGNAL(finished()), thread, SLOT(quit())); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - m_BonjourInstall->moveToThread(thread); - thread->start(); + m_BonjourInstall->moveToThread(thread); + thread->start(); - QMetaObject::invokeMethod(m_BonjourInstall, "run", Qt::QueuedConnection); + QMetaObject::invokeMethod(m_BonjourInstall, "run", Qt::QueuedConnection); - m_DownloadMessageBox->hide(); + m_DownloadMessageBox->hide(); #endif } void MainWindow::promptAutoConfig() { - if (!isBonjourRunning()) { - int r = QMessageBox::question( - this, tr("Synergy"), - tr("Do you want to enable auto config and install Bonjour?\n\n" - "This feature helps you establish the connection."), - QMessageBox::Yes | QMessageBox::No); + if (!isBonjourRunning()) { + int r = QMessageBox::question( + this, tr("Synergy"), + tr("Do you want to enable auto config and install Bonjour?\n\n" + "This feature helps you establish the connection."), + QMessageBox::Yes | QMessageBox::No); - if (r == QMessageBox::Yes) { - m_AppConfig->setAutoConfig(true); - downloadBonjour(); - } - else { - m_AppConfig->setAutoConfig(false); - m_pCheckBoxAutoConfig->setChecked(false); - } - } + if (r == QMessageBox::Yes) { + m_AppConfig->setAutoConfig(true); + downloadBonjour(); + } + else { + m_AppConfig->setAutoConfig(false); + m_pCheckBoxAutoConfig->setChecked(false); + } + } - m_AppConfig->setAutoConfigPrompted(true); + m_AppConfig->setAutoConfigPrompted(true); } void MainWindow::on_m_pComboServerList_currentIndexChanged(QString ) { - if (m_pComboServerList->count() != 0) { - restartSynergy(); - } + if (m_pComboServerList->count() != 0) { + restartSynergy(); + } } void MainWindow::on_m_pCheckBoxAutoConfig_toggled(bool checked) { - if (!isBonjourRunning() && checked) { - if (!m_SuppressAutoConfigWarning) { - int r = QMessageBox::information( - this, tr("Synergy"), - tr("Auto config feature requires Bonjour.\n\n" - "Do you want to install Bonjour?"), - QMessageBox::Yes | QMessageBox::No); + if (!isBonjourRunning() && checked) { + if (!m_SuppressAutoConfigWarning) { + int r = QMessageBox::information( + this, tr("Synergy"), + tr("Auto config feature requires Bonjour.\n\n" + "Do you want to install Bonjour?"), + QMessageBox::Yes | QMessageBox::No); - if (r == QMessageBox::Yes) { - downloadBonjour(); - } - } + if (r == QMessageBox::Yes) { + downloadBonjour(); + } + } - m_pCheckBoxAutoConfig->setChecked(false); - return; - } + m_pCheckBoxAutoConfig->setChecked(false); + return; + } - m_pLineEditHostname->setDisabled(checked); - appConfig().setAutoConfig(checked); - updateZeroconfService(); + m_pLineEditHostname->setDisabled(checked); + appConfig().setAutoConfig(checked); + updateZeroconfService(); - if (!checked) { - m_pComboServerList->clear(); - m_pComboServerList->hide(); - } + if (!checked) { + m_pComboServerList->clear(); + m_pComboServerList->hide(); + } } void MainWindow::bonjourInstallFinished() { - appendLogInfo("Bonjour install finished"); + appendLogInfo("Bonjour install finished"); - m_pCheckBoxAutoConfig->setChecked(true); + m_pCheckBoxAutoConfig->setChecked(true); } void MainWindow::on_windowShown() { - if (!m_AppConfig->activationHasRun() && (m_AppConfig->edition() == kUnregistered)) { - ActivationDialog activationDialog (this, appConfig(), subscriptionManager()); - activationDialog.exec(); - } + if (!m_AppConfig->activationHasRun() && (m_AppConfig->edition() == kUnregistered)) { + ActivationDialog activationDialog (this, appConfig(), subscriptionManager()); + activationDialog.exec(); + } } QString MainWindow::getProfileRootForArg() { - CoreInterface coreInterface; - QString dir = coreInterface.getProfileDir(); + CoreInterface coreInterface; + QString dir = coreInterface.getProfileDir(); - // HACK: strip our app name since we're returning the root dir. + // HACK: strip our app name since we're returning the root dir. #if defined(Q_OS_WIN) - dir.replace("\\Synergy", ""); + dir.replace("\\Synergy", ""); #else - dir.replace("/.synergy", ""); + dir.replace("/.synergy", ""); #endif - return QString("\"%1\"").arg(dir); + return QString("\"%1\"").arg(dir); } diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index 1ee077eb..2e6316c5 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -33,8 +33,7 @@ SerialKey::SerialKey(Edition edition): m_warnTime(ULLONG_MAX), m_expireTime(ULLONG_MAX), m_edition(edition), - m_trial(false), - m_valid(true) + m_trial(false) { } @@ -43,46 +42,25 @@ SerialKey::SerialKey(std::string serial) : m_warnTime(0), m_expireTime(0), m_edition(kBasic), - m_trial(true), - m_valid(false) + m_trial(true) { string plainText = decode(serial); + bool valid = false; if (!plainText.empty()) { - parse(plainText); + valid = parse(plainText); } - if (!m_valid) { + if (!valid) { throw std::runtime_error ("Invalid serial key"); } } -bool -SerialKey::isValid(time_t currentTime) const -{ - bool result = false; - - if (m_valid) { - if (m_trial) { - if (currentTime < m_expireTime) { - result = true; - } - } - else { - result = true; - } - } - - return result; -} - bool SerialKey::isExpiring(time_t currentTime) const { bool result = false; - if (m_valid) { - if (m_warnTime <= currentTime && currentTime < m_expireTime) { - result = true; - } + if (m_warnTime <= currentTime && currentTime < m_expireTime) { + result = true; } return result; @@ -93,12 +71,11 @@ SerialKey::isExpired(time_t currentTime) const { bool result = false; - if (m_valid) { - if (m_expireTime <= currentTime) { - result = true; - } + if (m_expireTime <= currentTime) { + result = true; } + return result; } @@ -209,13 +186,13 @@ SerialKey::decode(const std::string& serial) return output; } -void +bool SerialKey::parse(std::string plainSerial) { string parityStart = plainSerial.substr(0, 1); string parityEnd = plainSerial.substr(plainSerial.length() - 1, 1); - m_valid = false; + bool valid = false; // check for parity chars { and }, record parity result, then remove them. if (parityStart == "{" && parityEnd == "}") { @@ -247,7 +224,7 @@ SerialKey::parse(std::string plainSerial) m_company = parts.at(5); sscanf(parts.at(6).c_str(), "%lld", &m_warnTime); sscanf(parts.at(7).c_str(), "%lld", &m_expireTime); - m_valid = true; + valid = true; } else if ((parts.size() == 9) && (parts.at(0).find("v2") != string::npos)) { @@ -260,9 +237,11 @@ SerialKey::parse(std::string plainSerial) m_company = parts.at(6); sscanf(parts.at(7).c_str(), "%lld", &m_warnTime); sscanf(parts.at(8).c_str(), "%lld", &m_expireTime); - m_valid = true; + valid = true; } } + + return valid; } Edition diff --git a/src/lib/shared/SerialKey.h b/src/lib/shared/SerialKey.h index c6ae3f20..29b12a9d 100644 --- a/src/lib/shared/SerialKey.h +++ b/src/lib/shared/SerialKey.h @@ -31,7 +31,6 @@ public: explicit SerialKey(Edition edition = kUnregistered); explicit SerialKey(std::string serial); - bool isValid(time_t currentTime) const; bool isExpiring(time_t currentTime) const; bool isExpired(time_t currentTime) const; bool isTrial() const; @@ -44,7 +43,7 @@ public: static Edition parseEdition(const std::string& editionStr); private: - void parse(std::string plainSerial); + bool parse(std::string plainSerial); std::string editionString() const; #ifdef TEST_ENV @@ -67,7 +66,6 @@ private: unsigned long long m_expireTime; Edition m_edition; bool m_trial; - bool m_valid; }; @@ -80,8 +78,7 @@ operator== (SerialKey const& lhs, SerialKey const& rhs) { (lhs.m_warnTime == rhs.m_warnTime) && (lhs.m_expireTime == rhs.m_expireTime) && (lhs.m_edition == rhs.m_edition) && - (lhs.m_trial == rhs.m_trial) && - (lhs.m_valid == rhs.m_valid); + (lhs.m_trial == rhs.m_trial); } inline bool From 599415f0473611cf56c38d9d3735a2a9c6f8df28 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 17:11:30 +0100 Subject: [PATCH 43/88] #5657 Only check trial times when using a trial --- src/lib/shared/SerialKey.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index 2e6316c5..cdc7695f 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -59,8 +59,10 @@ SerialKey::isExpiring(time_t currentTime) const { bool result = false; - if (m_warnTime <= currentTime && currentTime < m_expireTime) { - result = true; + if (m_trial) { + if (m_warnTime <= currentTime && currentTime < m_expireTime) { + result = true; + } } return result; @@ -71,11 +73,12 @@ SerialKey::isExpired(time_t currentTime) const { bool result = false; - if (m_expireTime <= currentTime) { - result = true; + if (m_trial) { + if (m_expireTime <= currentTime) { + result = true; + } } - return result; } From 88c59b4ca6ad49b0577faafc449799f82504886a Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 17:37:30 +0100 Subject: [PATCH 44/88] #5657 Fix unit tests after SerialKey::isValid removal --- src/test/unittests/shared/SerialKeyTests.cpp | 69 +++----------------- 1 file changed, 9 insertions(+), 60 deletions(-) diff --git a/src/test/unittests/shared/SerialKeyTests.cpp b/src/test/unittests/shared/SerialKeyTests.cpp index dfb8685b..126d26e8 100644 --- a/src/test/unittests/shared/SerialKeyTests.cpp +++ b/src/test/unittests/shared/SerialKeyTests.cpp @@ -42,33 +42,33 @@ TEST(SerialKeyTests, decode_validSerial_returnPlainText) TEST(SerialKeyTests, parse_noParty_invalid) { SerialKey serial; - serial.parse("MOCK"); - EXPECT_FALSE(serial.isValid(0)); + bool r = serial.parse("MOCK"); + EXPECT_FALSE(r); } TEST(SerialKeyTests, parse_invalidPartsLenghth_invalid) { SerialKey serial; - serial.parse("{Synergy;Rocks}"); - EXPECT_FALSE(serial.isValid(0)); + bool r = serial.parse("{Synergy;Rocks}"); + EXPECT_FALSE(r); } TEST(SerialKeyTests, parse_validV1Serial_valid) { SerialKey serial; - serial.parse("{v1;basic;Bob;1;email;company name;0;86400}"); - EXPECT_EQ(true, serial.isValid(0)); + bool r = serial.parse("{v1;basic;Bob;1;email;company name;0;86400}"); + EXPECT_EQ(true, r); EXPECT_EQ(kBasic, serial.edition()); EXPECT_FALSE(serial.isExpired(0)); EXPECT_EQ(true, serial.daysLeft(0)); - EXPECT_EQ(true, serial.isExpiring(1)); + EXPECT_FALSE(serial.isExpiring(1)); } TEST(SerialKeyTests, parse_validV2Serial_valid) { SerialKey serial; - serial.parse("{v2;trial;pro;Bob;1;email;company name;0;86400}"); - EXPECT_EQ(true, serial.isValid(0)); + bool r = serial.parse("{v2;trial;pro;Bob;1;email;company name;0;86400}"); + EXPECT_EQ(true, r); EXPECT_EQ(kPro, serial.edition()); EXPECT_FALSE(serial.isExpired(0)); EXPECT_EQ(true, serial.daysLeft(0)); @@ -76,57 +76,6 @@ TEST(SerialKeyTests, parse_validV2Serial_valid) EXPECT_EQ(true, serial.isTrial()); } -TEST(SerialKeyTests, isValid_validV1BasicSerial_valid) -{ - // {v1;basic;Bob;1;email;company name;0;86400} - SerialKey serial("7B76313B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); - EXPECT_EQ(true, serial.isValid(0)); - EXPECT_EQ(kBasic, serial.edition()); -} - -TEST(SerialKeyTests, isValid_expiredV1ProSerial_valid) -{ - // {v1;pro;Bob;1;email;company name;0;86400} - SerialKey serial("7B76313B70726F3B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); - EXPECT_EQ(true, serial.isValid(0)); - EXPECT_EQ(kPro, serial.edition()); -} - -TEST(SerialKeyTests, isValid_validV2LifetimeBasicSerial_valid) -{ - // {v2;lifetime;basic;Bob;1;email;company name;0;86400} - SerialKey serial("7B76323B6C69666574696D653B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); - EXPECT_EQ(true, serial.isValid(0)); - EXPECT_EQ(kBasic, serial.edition()); -} - -TEST(SerialKeyTests, isValid_validV2LifetimeProSerial_valid) -{ - // {v2;lifetime;pro;Bob;1;email;company name;0;86400} - SerialKey serial("7B76323B6C69666574696D653B70726F3B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); - EXPECT_EQ(true, serial.isValid(0)); - EXPECT_EQ(kPro, serial.edition()); -} - -TEST(SerialKeyTests, isValid_validV2TrialBasicSerial_valid) -{ - // {v2;trial;basic;Bob;1;email;company name;0;86400} - SerialKey serial("7B76323B747269616C3B62617369633B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); - EXPECT_EQ(true, serial.isTrial()); - EXPECT_EQ(true, serial.isValid(0)); - EXPECT_EQ(kBasic, serial.edition()); - -} - -TEST(SerialKeyTests, isValid_expiredV2TrialProSerial_invalid) -{ - // {v2;trial;pro;Bob;1;email;company name;0;86400} - SerialKey serial("7B76323B747269616C3B70726F3B426F623B313B656D61696C3B636F6D70616E79206E616D653B303B38363430307D"); - EXPECT_EQ(true, serial.isTrial()); - EXPECT_FALSE(serial.isValid(86401)); - EXPECT_EQ(kPro, serial.edition()); -} - TEST(SerialKeyTests, isExpiring_validV2TrialBasicSerial_returnFalse) { // {v2;trial;basic;Bob;1;email;company name;1;86400} From 99dbdc5eb3b57d9c5d36d08964039d2fcadca357 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 17:38:05 +0100 Subject: [PATCH 45/88] #5657 Use SerialKey class in ServerArgs --- src/lib/server/Server.cpp | 42 +++++++++---------- src/lib/shared/SerialKey.h | 3 -- src/lib/synergy/ArgParser.cpp | 24 +++++------ src/lib/synergy/ServerArgs.h | 7 ++-- .../synergy/ServerArgsParsingTests.cpp | 18 +------- 5 files changed, 37 insertions(+), 57 deletions(-) diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index a202648e..b99e3cf1 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -2,11 +2,11 @@ * synergy -- mouse and keyboard sharing utility * Copyright (C) 2012-2016 Symless Ltd. * Copyright (C) 2002 Chris Schoeneman - * + * * 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 @@ -453,15 +453,11 @@ Server::switchScreen(BaseClientProxy* dst, SInt32 x, SInt32 y, bool forScreensaver) { assert(dst != NULL); - + // if trial is expired, exit the process - if (!m_args.m_serial.empty()) { - SerialKey serial(m_args.m_serial); - if (!serial.isValid(std::time(0))) { - LOG((CLOG_ERR "trial is expired, aborting server")); - exit(kExitSuccess); - return; - } + if (!m_args.m_serial.isExpired(std::time(0))) { + LOG((CLOG_ERR "trial is expired, aborting server")); + exit(kExitSuccess); } #ifndef NDEBUG @@ -547,7 +543,7 @@ Server::jumpToScreen(BaseClientProxy* newScreen) // get the last cursor position on the target screen SInt32 x, y; newScreen->getJumpCursorPos(x, y); - + switchScreen(newScreen, x, y, false); } @@ -897,14 +893,14 @@ Server::isSwitchOkay(BaseClientProxy* newScreen, if (!preventSwitch && ( (this->m_switchNeedsShift && ((mods & KeyModifierShift) != KeyModifierShift)) || - (this->m_switchNeedsControl && ((mods & KeyModifierControl) != KeyModifierControl)) || + (this->m_switchNeedsControl && ((mods & KeyModifierControl) != KeyModifierControl)) || (this->m_switchNeedsAlt && ((mods & KeyModifierAlt) != KeyModifierAlt)) )) { LOG((CLOG_DEBUG1 "need modifiers to switch")); preventSwitch = true; stopSwitch(); - } - + } + return !preventSwitch; } @@ -1184,7 +1180,7 @@ Server::processOptions() } else if (id == kOptionClipboardSharing) { m_enableClipboard = (value != 0); - + if (m_enableClipboard == false) { LOG((CLOG_NOTE "clipboard sharing is disabled")); } @@ -1413,7 +1409,7 @@ Server::handleClientCloseTimeout(const Event&, void* vclient) void Server::handleSwitchToScreenEvent(const Event& event, void*) { - SwitchToScreenInfo* info = + SwitchToScreenInfo* info = static_cast(event.getData()); ClientList::const_iterator index = m_clients.find(info->m_screen); @@ -1428,7 +1424,7 @@ Server::handleSwitchToScreenEvent(const Event& event, void*) void Server::handleSwitchInDirectionEvent(const Event& event, void*) { - SwitchInDirectionInfo* info = + SwitchInDirectionInfo* info = static_cast(event.getData()); // jump to screen in chosen direction from center of this screen @@ -1718,7 +1714,7 @@ Server::onMouseUp(ButtonID id) m_ignoreFileTransfer = false; return; } - + if (m_args.m_enableDragDrop) { if (!m_screen->isOnScreen()) { String& file = m_screen->getDraggingFilename(); @@ -1823,7 +1819,7 @@ Server::onMouseMovePrimary(SInt32 x, SInt32 y) m_waitDragInfoThread = true; return true; } - + return false; } @@ -1839,7 +1835,7 @@ Server::sendDragInfoThread(void* arg) di.setFilename(dragFileList); m_dragFileList.push_back(di); } - + #if defined(__APPLE__) // on mac it seems that after faking a LMB up, system would signal back // to synergy a mouse up event, which doesn't happen on windows. as a @@ -1862,7 +1858,7 @@ Server::sendDragInfo(BaseClientProxy* newScreen) { String infoString; UInt32 fileCount = DragInformation::setupDragInfo(m_dragFileList, infoString); - + if (fileCount > 0) { char* info = NULL; size_t size = infoString.size(); @@ -2072,7 +2068,7 @@ Server::onFileChunkSending(const void* data) assert(m_active != NULL); // relay - m_active->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize); + m_active->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize); } void @@ -2381,7 +2377,7 @@ Server::sendFileToClient(const char* filename) if (m_sendFileThread != NULL) { StreamChunker::interruptFile(); } - + m_sendFileThread = new Thread( new TMethodJob( this, &Server::sendFileThread, diff --git a/src/lib/shared/SerialKey.h b/src/lib/shared/SerialKey.h index 29b12a9d..dd9b7160 100644 --- a/src/lib/shared/SerialKey.h +++ b/src/lib/shared/SerialKey.h @@ -48,9 +48,6 @@ private: #ifdef TEST_ENV private: - FRIEND_TEST(SerialKeyTests, decode_empty_returnEmptyString); - FRIEND_TEST(SerialKeyTests, decode_invalidDigit_returnEmptyString); - FRIEND_TEST(SerialKeyTests, decode_validSerial_returnPlainText); FRIEND_TEST(SerialKeyTests, parse_noParty_invalid); FRIEND_TEST(SerialKeyTests, parse_invalidPartsLenghth_invalid); FRIEND_TEST(SerialKeyTests, parse_validV1Serial_valid); diff --git a/src/lib/synergy/ArgParser.cpp b/src/lib/synergy/ArgParser.cpp index d16a6fa9..1af32843 100644 --- a/src/lib/synergy/ArgParser.cpp +++ b/src/lib/synergy/ArgParser.cpp @@ -1,11 +1,11 @@ /* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2014-2016 Symless Ltd. - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -71,7 +71,7 @@ ArgParser::parseServerArgs(ServerArgs& args, int argc, const char* const* argv) DpiHelper::s_primaryHeightCenter = synergy::string::stringToSizeType(argv[++i]); } else if (isArg(i, argc, argv, "", "--serial-key", 1)) { - args.m_serial = argv[++i]; + args.m_serial = SerialKey(argv[++i]); } else { LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, args.m_pname, argv[i], args.m_pname)); @@ -110,7 +110,7 @@ ArgParser::parseClientArgs(ClientArgs& args, int argc, const char* const* argv) // ignore -- included for backwards compatibility } else if (isArg(i, argc, argv, NULL, "--yscroll", 1)) { - // define scroll + // define scroll args.m_yscroll = atoi(argv[++i]); } else { @@ -271,10 +271,10 @@ ArgParser::parseGenericArgs(int argc, const char* const* argv, int& i) argsBase().m_enableIpc = true; } else if (isArg(i, argc, argv, NULL, "--server")) { - // HACK: stop error happening when using portable (synergyp) + // HACK: stop error happening when using portable (synergyp) } else if (isArg(i, argc, argv, NULL, "--client")) { - // HACK: stop error happening when using portable (synergyp) + // HACK: stop error happening when using portable (synergyp) } else if (isArg(i, argc, argv, NULL, "--enable-drag-drop")) { bool useDragDrop = true; @@ -378,7 +378,7 @@ ArgParser::splitCommandString(String& command, std::vector& argv) else if (space > rightDoubleQuote){ searchDoubleQuotes(command, leftDoubleQuote, rightDoubleQuote, rightDoubleQuote + 1); } - + if (!ignoreThisSpace) { String subString = command.substr(startPos, space - startPos); @@ -444,7 +444,7 @@ ArgParser::getArgv(std::vector& argsArray) // them to the inner array. So caller only need to use // delete[] to delete the outer array const char** argv = new const char*[argc]; - + for (size_t i = 0; i < argc; i++) { argv[i] = argsArray[i].c_str(); } @@ -476,7 +476,7 @@ ArgParser::assembleCommand(std::vector& argsArray, String ignoreArg, in if (!result.empty()) { // remove the tail space - result = result.substr(0, result.size() - 1); + result = result.substr(0, result.size() - 1); } return result; @@ -493,13 +493,13 @@ bool ArgParser::checkUnexpectedArgs() { #if SYSAPI_WIN32 - // suggest that user installs as a windows service. when launched as + // suggest that user installs as a windows service. when launched as // service, process should automatically detect that it should run in // daemon mode. if (argsBase().m_daemon) { - LOG((CLOG_ERR + LOG((CLOG_ERR "the --daemon argument is not supported on windows. " - "instead, install %s as a service (--service install)", + "instead, install %s as a service (--service install)", argsBase().m_pname)); return true; } diff --git a/src/lib/synergy/ServerArgs.h b/src/lib/synergy/ServerArgs.h index 7c69fae6..e139d110 100644 --- a/src/lib/synergy/ServerArgs.h +++ b/src/lib/synergy/ServerArgs.h @@ -1,11 +1,11 @@ /* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2014-2016 Symless Ltd. - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -18,6 +18,7 @@ #pragma once #include "synergy/ArgsBase.h" +#include "shared/SerialKey.h" class NetworkAddress; class Config; @@ -28,6 +29,6 @@ public: public: String m_configFile; - String m_serial; + SerialKey m_serial; Config* m_config; }; diff --git a/src/test/unittests/synergy/ServerArgsParsingTests.cpp b/src/test/unittests/synergy/ServerArgsParsingTests.cpp index 92db8c0d..f931f7e5 100644 --- a/src/test/unittests/synergy/ServerArgsParsingTests.cpp +++ b/src/test/unittests/synergy/ServerArgsParsingTests.cpp @@ -1,11 +1,11 @@ /* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2014-2016 Symless Ltd. - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -64,17 +64,3 @@ TEST(ServerArgsParsingTests, parseServerArgs_configArg_setConfigFile) EXPECT_EQ("mock_configFile", serverArgs.m_configFile); } - -TEST(ServerArgsParsingTests, parseServerArgs_serialArg_setSerial) -{ - NiceMock argParser; - ON_CALL(argParser, parseGenericArgs(_, _, _)).WillByDefault(Invoke(server_stubParseGenericArgs)); - ON_CALL(argParser, checkUnexpectedArgs()).WillByDefault(Invoke(server_stubCheckUnexpectedArgs)); - ServerArgs serverArgs; - const int argc = 3; - const char* kSerialCmd[argc] = { "stub", "--serial-key", "mock_serial" }; - - argParser.parseServerArgs(serverArgs, argc, kSerialCmd); - - EXPECT_EQ("mock_serial", serverArgs.m_serial); -} From 5a34da3ce0e5b56a85de9c03fbf2e7da09fd4eec Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 17:49:14 +0100 Subject: [PATCH 46/88] #5657 Refresh UI when synergys reports trial expired --- src/gui/src/MainWindow.cpp | 11 +- src/gui/src/MainWindow.h | 299 +++++++++++++++++++------------------ src/lib/server/Server.cpp | 2 +- 3 files changed, 160 insertions(+), 152 deletions(-) diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 64a9f50d..d8849899 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -411,12 +411,12 @@ void MainWindow::appendLogRaw(const QString& text) foreach(QString line, text.split(QRegExp("\r|\n|\r\n"))) { if (!line.isEmpty()) { m_pLogOutput->append(line); - updateStateFromLogLine(line); + updateFromLogLine(line); } } } -void MainWindow::updateStateFromLogLine(const QString &line) +void MainWindow::updateFromLogLine(const QString &line) { checkConnected(line); checkFingerprint(line); @@ -444,6 +444,13 @@ void MainWindow::checkConnected(const QString& line) } } +void MainWindow::checkLicense(const QString &line) +{ + if (line.contains("trial has expired")) { + m_SubscriptionManager->refresh(); + } +} + void MainWindow::checkFingerprint(const QString& line) { QRegExp fingerprintRegex(".*server fingerprint: ([A-F0-9:]+)"); diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index c00afdba..27fff56a 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -62,174 +62,175 @@ class SubscriptionManager; class MainWindow : public QMainWindow, public Ui::MainWindowBase { - Q_OBJECT + Q_OBJECT - friend class QSynergyApplication; - friend class SetupWizard; - friend class ActivationDialog; - friend class SettingsDialog; + friend class QSynergyApplication; + friend class SetupWizard; + friend class ActivationDialog; + friend class SettingsDialog; - public: - enum qSynergyState - { - synergyDisconnected, - synergyConnecting, - synergyConnected, - synergyTransfering - }; + public: + enum qSynergyState + { + synergyDisconnected, + synergyConnecting, + synergyConnected, + synergyTransfering + }; - enum qSynergyType - { - synergyClient, - synergyServer - }; + enum qSynergyType + { + synergyClient, + synergyServer + }; - enum qLevel { - Error, - Info - }; + enum qLevel { + Error, + Info + }; - enum qRuningState { - kStarted, - kStopped - }; + enum qRuningState { + kStarted, + kStopped + }; - public: - MainWindow(QSettings& settings, AppConfig& appConfig, - SubscriptionManager& subscriptionManager); - ~MainWindow(); + public: + MainWindow(QSettings& settings, AppConfig& appConfig, + SubscriptionManager& subscriptionManager); + ~MainWindow(); - public: - void setVisible(bool visible); - int synergyType() const { return m_pGroupClient->isChecked() ? synergyClient : synergyServer; } - int synergyState() const { return m_SynergyState; } - QString hostname() const { return m_pLineEditHostname->text(); } - QString configFilename(); - QString address(); - QString appPath(const QString& name); - void open(); - void clearLog(); - VersionChecker& versionChecker() { return m_VersionChecker; } - QString getScreenName(); - ServerConfig& serverConfig() { return m_ServerConfig; } - void showConfigureServer(const QString& message); - void showConfigureServer() { showConfigureServer(""); } - void autoAddScreen(const QString name); - void updateZeroconfService(); - void serverDetected(const QString name); - void updateLocalFingerprint(); - SubscriptionManager& subscriptionManager() const; + public: + void setVisible(bool visible); + int synergyType() const { return m_pGroupClient->isChecked() ? synergyClient : synergyServer; } + int synergyState() const { return m_SynergyState; } + QString hostname() const { return m_pLineEditHostname->text(); } + QString configFilename(); + QString address(); + QString appPath(const QString& name); + void open(); + void clearLog(); + VersionChecker& versionChecker() { return m_VersionChecker; } + QString getScreenName(); + ServerConfig& serverConfig() { return m_ServerConfig; } + void showConfigureServer(const QString& message); + void showConfigureServer() { showConfigureServer(""); } + void autoAddScreen(const QString name); + void updateZeroconfService(); + void serverDetected(const QString name); + void updateLocalFingerprint(); + SubscriptionManager& subscriptionManager() const; - public slots: - void setEdition(Edition edition); - void beginTrial(bool isExpiring); - void endTrial(bool isExpired); - void appendLogRaw(const QString& text); - void appendLogInfo(const QString& text); - void appendLogDebug(const QString& text); - void appendLogError(const QString& text); - void startSynergy(); + public slots: + void setEdition(Edition edition); + void beginTrial(bool isExpiring); + void endTrial(bool isExpired); + void appendLogRaw(const QString& text); + void appendLogInfo(const QString& text); + void appendLogDebug(const QString& text); + void appendLogError(const QString& text); + void startSynergy(); - protected slots: - void sslToggled(bool enabled); - void on_m_pGroupClient_toggled(bool on); - void on_m_pGroupServer_toggled(bool on); - bool on_m_pButtonBrowseConfigFile_clicked(); - void on_m_pButtonConfigureServer_clicked(); - bool on_m_pActionSave_triggered(); - void on_m_pActionAbout_triggered(); - void on_m_pActionSettings_triggered(); - void on_m_pActivate_triggered(); - void synergyFinished(int exitCode, QProcess::ExitStatus); - void trayActivated(QSystemTrayIcon::ActivationReason reason); - void stopSynergy(); - void logOutput(); - void logError(); - void updateFound(const QString& version); - void bonjourInstallFinished(); + protected slots: + void sslToggled(bool enabled); + void on_m_pGroupClient_toggled(bool on); + void on_m_pGroupServer_toggled(bool on); + bool on_m_pButtonBrowseConfigFile_clicked(); + void on_m_pButtonConfigureServer_clicked(); + bool on_m_pActionSave_triggered(); + void on_m_pActionAbout_triggered(); + void on_m_pActionSettings_triggered(); + void on_m_pActivate_triggered(); + void synergyFinished(int exitCode, QProcess::ExitStatus); + void trayActivated(QSystemTrayIcon::ActivationReason reason); + void stopSynergy(); + void logOutput(); + void logError(); + void updateFound(const QString& version); + void bonjourInstallFinished(); - protected: - QSettings& settings() { return m_Settings; } - AppConfig& appConfig() { return *m_AppConfig; } - QProcess* synergyProcess() { return m_pSynergy; } - void setSynergyProcess(QProcess* p) { m_pSynergy = p; } - void initConnections(); - void createMenuBar(); - void createStatusBar(); - void createTrayIcon(); - void loadSettings(); - void saveSettings(); - void setIcon(qSynergyState state); - void setSynergyState(qSynergyState state); - bool checkForApp(int which, QString& app); - bool clientArgs(QStringList& args, QString& app); - bool serverArgs(QStringList& args, QString& app); - void setStatus(const QString& status); - void sendIpcMessage(qIpcMessageType type, const char* buffer, bool showErrors); - void onModeChanged(bool startDesktop, bool applyService); - void updateStateFromLogLine(const QString& line); - QString getIPAddresses(); - void stopService(); - void stopDesktop(); - void changeEvent(QEvent* event); - void retranslateMenuBar(); + protected: + QSettings& settings() { return m_Settings; } + AppConfig& appConfig() { return *m_AppConfig; } + QProcess* synergyProcess() { return m_pSynergy; } + void setSynergyProcess(QProcess* p) { m_pSynergy = p; } + void initConnections(); + void createMenuBar(); + void createStatusBar(); + void createTrayIcon(); + void loadSettings(); + void saveSettings(); + void setIcon(qSynergyState state); + void setSynergyState(qSynergyState state); + bool checkForApp(int which, QString& app); + bool clientArgs(QStringList& args, QString& app); + bool serverArgs(QStringList& args, QString& app); + void setStatus(const QString& status); + void sendIpcMessage(qIpcMessageType type, const char* buffer, bool showErrors); + void onModeChanged(bool startDesktop, bool applyService); + void updateFromLogLine(const QString& line); + QString getIPAddresses(); + void stopService(); + void stopDesktop(); + void changeEvent(QEvent* event); + void retranslateMenuBar(); #if defined(Q_OS_WIN) - bool isServiceRunning(QString name); + bool isServiceRunning(QString name); #else - bool isServiceRunning(); + bool isServiceRunning(); #endif - bool isBonjourRunning(); - void downloadBonjour(); - void promptAutoConfig(); - QString getProfileRootForArg(); - void checkConnected(const QString& line); - void checkFingerprint(const QString& line); - bool autoHide(); - QString getTimeStamp(); - void restartSynergy(); - void proofreadInfo(); + bool isBonjourRunning(); + void downloadBonjour(); + void promptAutoConfig(); + QString getProfileRootForArg(); + void checkConnected(const QString& line); + void checkLicense(const QString& line); + void checkFingerprint(const QString& line); + bool autoHide(); + QString getTimeStamp(); + void restartSynergy(); + void proofreadInfo(); - void showEvent (QShowEvent*); + void showEvent (QShowEvent*); - private: - QSettings& m_Settings; - AppConfig* m_AppConfig; - SubscriptionManager* m_SubscriptionManager; - QProcess* m_pSynergy; - int m_SynergyState; - ServerConfig m_ServerConfig; - QTemporaryFile* m_pTempConfigFile; - QSystemTrayIcon* m_pTrayIcon; - QMenu* m_pTrayIconMenu; - bool m_AlreadyHidden; - VersionChecker m_VersionChecker; - IpcClient m_IpcClient; - QMenuBar* m_pMenuBar; - QMenu* m_pMenuFile; - QMenu* m_pMenuEdit; - QMenu* m_pMenuWindow; - QMenu* m_pMenuHelp; - ZeroconfService* m_pZeroconfService; - DataDownloader* m_pDataDownloader; - QMessageBox* m_DownloadMessageBox; - QAbstractButton* m_pCancelButton; - QMutex m_UpdateZeroconfMutex; - bool m_SuppressAutoConfigWarning; - CommandProcess* m_BonjourInstall; - bool m_SuppressEmptyServerWarning; - qRuningState m_ExpectedRunningState; - QMutex m_StopDesktopMutex; - SslCertificate* m_pSslCertificate; + private: + QSettings& m_Settings; + AppConfig* m_AppConfig; + SubscriptionManager* m_SubscriptionManager; + QProcess* m_pSynergy; + int m_SynergyState; + ServerConfig m_ServerConfig; + QTemporaryFile* m_pTempConfigFile; + QSystemTrayIcon* m_pTrayIcon; + QMenu* m_pTrayIconMenu; + bool m_AlreadyHidden; + VersionChecker m_VersionChecker; + IpcClient m_IpcClient; + QMenuBar* m_pMenuBar; + QMenu* m_pMenuFile; + QMenu* m_pMenuEdit; + QMenu* m_pMenuWindow; + QMenu* m_pMenuHelp; + ZeroconfService* m_pZeroconfService; + DataDownloader* m_pDataDownloader; + QMessageBox* m_DownloadMessageBox; + QAbstractButton* m_pCancelButton; + QMutex m_UpdateZeroconfMutex; + bool m_SuppressAutoConfigWarning; + CommandProcess* m_BonjourInstall; + bool m_SuppressEmptyServerWarning; + qRuningState m_ExpectedRunningState; + QMutex m_StopDesktopMutex; + SslCertificate* m_pSslCertificate; private slots: - void on_m_pCheckBoxAutoConfig_toggled(bool checked); - void on_m_pComboServerList_currentIndexChanged(QString ); - void on_m_pButtonApply_clicked(); - void installBonjour(); - void on_windowShown(); + void on_m_pCheckBoxAutoConfig_toggled(bool checked); + void on_m_pComboServerList_currentIndexChanged(QString ); + void on_m_pButtonApply_clicked(); + void installBonjour(); + void on_windowShown(); signals: - void windowShown(); + void windowShown(); }; #endif diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index b99e3cf1..0b1660ea 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -456,7 +456,7 @@ Server::switchScreen(BaseClientProxy* dst, // if trial is expired, exit the process if (!m_args.m_serial.isExpired(std::time(0))) { - LOG((CLOG_ERR "trial is expired, aborting server")); + LOG((CLOG_ERR "trial has expired, aborting server")); exit(kExitSuccess); } From fc67cdf56ea9d627598b8a343e2624396b9f8a12 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 17:57:14 +0100 Subject: [PATCH 47/88] #5657 Rename SubscriptionManager to LicenseManager --- src/gui/gui.pro | 8 +- src/gui/src/ActivationDialog.cpp | 20 +-- src/gui/src/ActivationDialog.h | 16 +- src/gui/src/LicenseManager.cpp | 144 ++++++++++++++++++ ...SubscriptionManager.h => LicenseManager.h} | 28 ++-- src/gui/src/MainWindow.cpp | 34 ++--- src/gui/src/MainWindow.h | 8 +- src/gui/src/SetupWizard.cpp | 2 +- src/gui/src/SubscriptionManager.cpp | 144 ------------------ src/gui/src/main.cpp | 12 +- 10 files changed, 208 insertions(+), 208 deletions(-) create mode 100644 src/gui/src/LicenseManager.cpp rename src/gui/src/{SubscriptionManager.h => LicenseManager.h} (65%) delete mode 100644 src/gui/src/SubscriptionManager.cpp diff --git a/src/gui/gui.pro b/src/gui/gui.pro index e56926bc..b60c280d 100644 --- a/src/gui/gui.pro +++ b/src/gui/gui.pro @@ -61,12 +61,12 @@ SOURCES += src/main.cpp \ src/Fingerprint.cpp \ src/SslCertificate.cpp \ src/WebClient.cpp \ - src/SubscriptionManager.cpp \ src/ActivationNotifier.cpp \ src/ActivationDialog.cpp \ src/CancelActivationDialog.cpp \ src/FailedLoginDialog.cpp \ - ../lib/shared/SerialKey.cpp + ../lib/shared/SerialKey.cpp \ + src/LicenseManager.cpp HEADERS += src/MainWindow.h \ src/AboutDialog.h \ src/ServerConfig.h \ @@ -108,14 +108,14 @@ HEADERS += src/MainWindow.h \ src/Fingerprint.h \ src/SslCertificate.h \ src/WebClient.h \ - src/SubscriptionManager.h \ src/ActivationNotifier.h \ src/ElevateMode.h \ src/ActivationDialog.h \ src/CancelActivationDialog.h \ src/FailedLoginDialog.h \ ../lib/shared/EditionType.h \ - ../lib/shared/SerialKey.h + ../lib/shared/SerialKey.h \ + src/LicenseManager.h RESOURCES += res/Synergy.qrc RC_FILE = res/win/Synergy.rc macx { diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 9e7f5e82..a964fca6 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -7,7 +7,7 @@ #include "ActivationNotifier.h" #include "MainWindow.h" #include "QUtility.h" -#include "SubscriptionManager.h" +#include "LicenseManager.h" #include "FailedLoginDialog.h" #include @@ -15,11 +15,11 @@ #include ActivationDialog::ActivationDialog(QWidget* parent, AppConfig& appConfig, - SubscriptionManager& subscriptionManager) : + LicenseManager& licenseManager) : QDialog(parent), ui(new Ui::ActivationDialog), m_appConfig(&appConfig), - m_subscriptionManager (&subscriptionManager) + m_LicenseManager (&licenseManager) { ui->setupUi(this); refreshSerialKey(); @@ -39,10 +39,10 @@ ActivationDialog::~ActivationDialog() void ActivationDialog::reject() { - if (m_subscriptionManager->activeEdition() == kUnregistered) { + if (m_LicenseManager->activeEdition() == kUnregistered) { CancelActivationDialog cancelActivationDialog(this); if (QDialog::Accepted == cancelActivationDialog.exec()) { - m_subscriptionManager->skipActivation(); + m_LicenseManager->skipActivation(); m_appConfig->activationHasRun(true); m_appConfig->saveSettings(); } @@ -59,7 +59,7 @@ void ActivationDialog::accept() std::pair result; try { QString serialKey = ui->m_pTextEditSerialKey->toPlainText(); - result = m_subscriptionManager->setSerialKey(serialKey); + result = m_LicenseManager->setSerialKey(serialKey); } catch (std::exception& e) { message.critical(this, "Unknown Error", @@ -77,17 +77,17 @@ void ActivationDialog::accept() return; } - Edition edition = m_subscriptionManager->activeEdition(); + Edition edition = m_LicenseManager->activeEdition(); if (edition != kUnregistered) { - if (m_subscriptionManager->serialKey().isTrial()) { + if (m_LicenseManager->serialKey().isTrial()) { message.information(this, "Thanks!", tr("Thanks for trying %1!").arg - (m_subscriptionManager->getEditionName(edition))); + (m_LicenseManager->getEditionName(edition))); } else { message.information(this, "Activated!", tr("Thanks for activating %1!").arg - (m_subscriptionManager->getEditionName(edition))); + (m_LicenseManager->getEditionName(edition))); } } diff --git a/src/gui/src/ActivationDialog.h b/src/gui/src/ActivationDialog.h index b0e9aa94..1cc45933 100644 --- a/src/gui/src/ActivationDialog.h +++ b/src/gui/src/ActivationDialog.h @@ -2,7 +2,7 @@ #define ACTIVATIONDIALOG_H #include -#include +#include namespace Ui { class ActivationDialog; @@ -13,23 +13,23 @@ class AppConfig; class ActivationDialog : public QDialog { Q_OBJECT - + public: - ActivationDialog(QWidget *parent, AppConfig& appConfig, - SubscriptionManager& subscriptionManager); + ActivationDialog(QWidget *parent, AppConfig& appConfig, + LicenseManager& licenseManager); ~ActivationDialog(); public slots: void reject(); void accept(); - + protected: - void refreshSerialKey(); - + void refreshSerialKey(); + private: Ui::ActivationDialog *ui; AppConfig* m_appConfig; - SubscriptionManager* m_subscriptionManager; + LicenseManager* m_LicenseManager; }; #endif // ACTIVATIONDIALOG_H diff --git a/src/gui/src/LicenseManager.cpp b/src/gui/src/LicenseManager.cpp new file mode 100644 index 00000000..53ee76cf --- /dev/null +++ b/src/gui/src/LicenseManager.cpp @@ -0,0 +1,144 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2015 Synergy Seamless Inc. + * + * 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 "LicenseManager.h" +#include "EditionType.h" +#include "AppConfig.h" +#include +#include +#include +#include + +LicenseManager::LicenseManager(AppConfig* appConfig) : + m_AppConfig(appConfig), + m_serialKey(appConfig->edition()) { + try { + setSerialKey(m_AppConfig->serialKey()); + } catch (...) { + /* Remove garbage serial keys from the registry */ + m_AppConfig->setSerialKey(""); + m_AppConfig->setEdition(kUnregistered); + m_AppConfig->saveSettings(); + } +} + +std::pair +LicenseManager::setSerialKey(QString serialKeyString) +{ + std::pair ret (true, ""); + + SerialKey serialKey (serialKeyString.toStdString()); + if (serialKey.isExpired(::time(0))) { + ret.first = false; + ret.second = "Serial key expired"; + return ret; + } + + if (serialKey != m_serialKey) { + using std::swap; + swap (serialKey, m_serialKey); + + m_AppConfig->setSerialKey (serialKeyString); + notifyActivation ("serial:" + serialKeyString); + emit serialKeyChanged (m_serialKey); + + if (m_serialKey.edition() != serialKey.edition()) { + m_AppConfig->setEdition (m_serialKey.edition()); + emit editionChanged (m_serialKey.edition()); + } + + if (serialKey.isTrial()) { + emit endTrial(false); + } + + if (m_serialKey.isTrial()) { + emit beginTrial(m_serialKey.isExpiring(::time(0))); + } + + m_AppConfig->saveSettings(); + } + + return ret; +} + +Edition +LicenseManager::activeEdition() const +{ + return m_serialKey.edition(); +} + +QString +LicenseManager::activeEditionName() const +{ + return getEditionName(activeEdition(), m_serialKey.isTrial()); +} + +SerialKey +LicenseManager::serialKey() const +{ + return m_serialKey; +} + +void LicenseManager::refresh() const +{ + emit serialKeyChanged (m_serialKey); + emit editionChanged (m_serialKey.edition()); + if (m_serialKey.isTrial()) { + emit beginTrial(m_serialKey.isExpiring(::time(0))); + } +} + +void LicenseManager::skipActivation() +{ + notifyActivation ("skip:unknown"); +} + +QString +LicenseManager::getEditionName(Edition const edition, bool trial) +{ + std::string name ("Synergy "); + switch (edition) { + case kUnregistered: + name += "(UNREGISTERED)"; + return QString::fromUtf8 (name.c_str(), name.size()); + case kBasic: + name += "Basic"; + break; + default: + name += "Pro"; + } + if (trial) { + name += " (Trial)"; + } + return QString::fromUtf8 (name.c_str(), name.size()); +} + +void LicenseManager::notifyActivation(QString identity) +{ + ActivationNotifier* notifier = new ActivationNotifier(); + notifier->setIdentity(identity); + + QThread* thread = new QThread(); + connect(notifier, SIGNAL(finished()), thread, SLOT(quit())); + connect(notifier, SIGNAL(finished()), notifier, SLOT(deleteLater())); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + + notifier->moveToThread(thread); + thread->start(); + + QMetaObject::invokeMethod(notifier, "notify", Qt::QueuedConnection); +} diff --git a/src/gui/src/SubscriptionManager.h b/src/gui/src/LicenseManager.h similarity index 65% rename from src/gui/src/SubscriptionManager.h rename to src/gui/src/LicenseManager.h index 5023ddc2..edb412af 100644 --- a/src/gui/src/SubscriptionManager.h +++ b/src/gui/src/LicenseManager.h @@ -24,30 +24,30 @@ class AppConfig; -class SubscriptionManager: public QObject +class LicenseManager: public QObject { - Q_OBJECT + Q_OBJECT public: - SubscriptionManager(AppConfig* appConfig); - std::pair setSerialKey(QString serialKey); - void refresh() const; - Edition activeEdition() const; + LicenseManager(AppConfig* appConfig); + std::pair setSerialKey(QString serialKey); + void refresh() const; + Edition activeEdition() const; QString activeEditionName() const; SerialKey serialKey() const; - void skipActivation(); + void skipActivation(); static QString getEditionName(Edition edition, bool trial = false); private: - void notifyActivation(QString identity); + void notifyActivation(QString identity); private: - AppConfig* m_AppConfig; - SerialKey m_serialKey; + AppConfig* m_AppConfig; + SerialKey m_serialKey; signals: - void serialKeyChanged (SerialKey) const; - void editionChanged (Edition) const; - void beginTrial (bool expiring) const; - void endTrial (bool expired) const; + void serialKeyChanged (SerialKey) const; + void editionChanged (Edition) const; + void beginTrial (bool expiring) const; + void endTrial (bool expired) const; }; diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index d8849899..417fcb9f 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -30,7 +30,7 @@ #include "ZeroconfService.h" #include "DataDownloader.h" #include "CommandProcess.h" -#include "SubscriptionManager.h" +#include "LicenseManager.h" #include "EditionType.h" #include "QUtility.h" #include "ProcessorArch.h" @@ -77,10 +77,10 @@ static const char* synergyIconFiles[] = }; MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig, - SubscriptionManager& subscriptionManager) : + LicenseManager& licenseManager) : m_Settings(settings), m_AppConfig(&appConfig), - m_SubscriptionManager(&subscriptionManager), + m_LicenseManager(&licenseManager), m_pSynergy(NULL), m_SynergyState(synergyDisconnected), m_ServerConfig(&m_Settings, 5, 3, m_AppConfig->screenName(), this), @@ -142,19 +142,19 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig, connect (this, SIGNAL(windowShown()), this, SLOT(on_windowShown()), Qt::QueuedConnection); - connect (m_SubscriptionManager, SIGNAL(editionChanged(Edition)), + connect (m_LicenseManager, SIGNAL(editionChanged(Edition)), this, SLOT(setEdition(Edition)), Qt::QueuedConnection); - connect (m_SubscriptionManager, SIGNAL(beginTrial(bool)), + connect (m_LicenseManager, SIGNAL(beginTrial(bool)), this, SLOT(beginTrial(bool)), Qt::QueuedConnection); - connect (m_SubscriptionManager, SIGNAL(endTrial(bool)), + connect (m_LicenseManager, SIGNAL(endTrial(bool)), this, SLOT(endTrial(bool)), Qt::QueuedConnection); connect (m_AppConfig, SIGNAL(sslToggled(bool)), this, SLOT(sslToggled(bool)), Qt::QueuedConnection); - m_SubscriptionManager->refresh(); + m_LicenseManager->refresh(); } MainWindow::~MainWindow() @@ -447,7 +447,7 @@ void MainWindow::checkConnected(const QString& line) void MainWindow::checkLicense(const QString &line) { if (line.contains("trial has expired")) { - m_SubscriptionManager->refresh(); + m_LicenseManager->refresh(); } } @@ -1049,7 +1049,7 @@ void MainWindow::serverDetected(const QString name) void MainWindow::setEdition(Edition edition) { - setWindowTitle(m_SubscriptionManager->getEditionName (edition)); + setWindowTitle(m_LicenseManager->getEditionName (edition)); if (m_AppConfig->getCryptoEnabled()) { m_pSslCertificate = new SslCertificate(this); m_pSslCertificate->generateCertificate(); @@ -1069,11 +1069,11 @@ void MainWindow::beginTrial(bool isExpiring) " color:#0000ff;\">Buy now!" "

"; expiringNotice = expiringNotice.arg - (m_SubscriptionManager->serialKey().daysLeft(::time(0))); + (m_LicenseManager->serialKey().daysLeft(::time(0))); this->m_trialLabel->setText(expiringNotice); this->m_trialWidget->show(); } - setWindowTitle (m_SubscriptionManager->activeEditionName()); + setWindowTitle (m_LicenseManager->activeEditionName()); } void MainWindow::endTrial(bool isExpired) @@ -1081,7 +1081,7 @@ void MainWindow::endTrial(bool isExpired) if (!isExpired) { this->m_trialWidget->hide(); } - setWindowTitle (m_SubscriptionManager->activeEditionName()); + setWindowTitle (m_LicenseManager->activeEditionName()); } void MainWindow::updateLocalFingerprint() @@ -1097,10 +1097,10 @@ void MainWindow::updateLocalFingerprint() } } -SubscriptionManager& -MainWindow::subscriptionManager() const +LicenseManager& +MainWindow::licenseManager() const { - return *m_SubscriptionManager; + return *m_LicenseManager; } void MainWindow::on_m_pGroupClient_toggled(bool on) @@ -1204,7 +1204,7 @@ void MainWindow::on_m_pButtonConfigureServer_clicked() void MainWindow::on_m_pActivate_triggered() { - ActivationDialog activationDialog(this, appConfig(), subscriptionManager()); + ActivationDialog activationDialog(this, appConfig(), licenseManager()); activationDialog.exec(); } @@ -1423,7 +1423,7 @@ void MainWindow::bonjourInstallFinished() void MainWindow::on_windowShown() { if (!m_AppConfig->activationHasRun() && (m_AppConfig->edition() == kUnregistered)) { - ActivationDialog activationDialog (this, appConfig(), subscriptionManager()); + ActivationDialog activationDialog (this, appConfig(), licenseManager()); activationDialog.exec(); } } diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index 27fff56a..b7fc2ca5 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -58,7 +58,7 @@ class ZeroconfService; class DataDownloader; class CommandProcess; class SslCertificate; -class SubscriptionManager; +class LicenseManager; class MainWindow : public QMainWindow, public Ui::MainWindowBase { @@ -96,7 +96,7 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase public: MainWindow(QSettings& settings, AppConfig& appConfig, - SubscriptionManager& subscriptionManager); + LicenseManager& licenseManager); ~MainWindow(); public: @@ -118,7 +118,7 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase void updateZeroconfService(); void serverDetected(const QString name); void updateLocalFingerprint(); - SubscriptionManager& subscriptionManager() const; + LicenseManager& licenseManager() const; public slots: void setEdition(Edition edition); @@ -195,7 +195,7 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase private: QSettings& m_Settings; AppConfig* m_AppConfig; - SubscriptionManager* m_SubscriptionManager; + LicenseManager* m_LicenseManager; QProcess* m_pSynergy; int m_SynergyState; ServerConfig m_ServerConfig; diff --git a/src/gui/src/SetupWizard.cpp b/src/gui/src/SetupWizard.cpp index b4e6ab3a..da3ff8c3 100644 --- a/src/gui/src/SetupWizard.cpp +++ b/src/gui/src/SetupWizard.cpp @@ -19,7 +19,7 @@ #include "MainWindow.h" #include "WebClient.h" #include "ActivationNotifier.h" -#include "SubscriptionManager.h" +#include "LicenseManager.h" #include "EditionType.h" #include "QSynergyApplication.h" #include "QUtility.h" diff --git a/src/gui/src/SubscriptionManager.cpp b/src/gui/src/SubscriptionManager.cpp deleted file mode 100644 index b42cc98d..00000000 --- a/src/gui/src/SubscriptionManager.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* - * synergy -- mouse and keyboard sharing utility - * Copyright (C) 2015 Synergy Seamless Inc. - * - * 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 "SubscriptionManager.h" -#include "EditionType.h" -#include "AppConfig.h" -#include -#include -#include -#include - -SubscriptionManager::SubscriptionManager(AppConfig* appConfig) : - m_AppConfig(appConfig), - m_serialKey(appConfig->edition()) { - try { - setSerialKey(m_AppConfig->serialKey()); - } catch (...) { - /* Remove garbage serial keys from the registry */ - m_AppConfig->setSerialKey(""); - m_AppConfig->setEdition(kUnregistered); - m_AppConfig->saveSettings(); - } -} - -std::pair -SubscriptionManager::setSerialKey(QString serialKeyString) -{ - std::pair ret (true, ""); - - SerialKey serialKey (serialKeyString.toStdString()); - if (serialKey.isExpired(::time(0))) { - ret.first = false; - ret.second = "Serial key expired"; - return ret; - } - - if (serialKey != m_serialKey) { - using std::swap; - swap (serialKey, m_serialKey); - - m_AppConfig->setSerialKey (serialKeyString); - notifyActivation ("serial:" + serialKeyString); - emit serialKeyChanged (m_serialKey); - - if (m_serialKey.edition() != serialKey.edition()) { - m_AppConfig->setEdition (m_serialKey.edition()); - emit editionChanged (m_serialKey.edition()); - } - - if (serialKey.isTrial()) { - emit endTrial(false); - } - - if (m_serialKey.isTrial()) { - emit beginTrial(m_serialKey.isExpiring(::time(0))); - } - - m_AppConfig->saveSettings(); - } - - return ret; -} - -Edition -SubscriptionManager::activeEdition() const -{ - return m_serialKey.edition(); -} - -QString -SubscriptionManager::activeEditionName() const -{ - return getEditionName(activeEdition(), m_serialKey.isTrial()); -} - -SerialKey -SubscriptionManager::serialKey() const -{ - return m_serialKey; -} - -void SubscriptionManager::refresh() const -{ - emit serialKeyChanged (m_serialKey); - emit editionChanged (m_serialKey.edition()); - if (m_serialKey.isTrial()) { - emit beginTrial(m_serialKey.isExpiring(::time(0))); - } -} - -void SubscriptionManager::skipActivation() -{ - notifyActivation ("skip:unknown"); -} - -QString -SubscriptionManager::getEditionName(Edition const edition, bool trial) -{ - std::string name ("Synergy "); - switch (edition) { - case kUnregistered: - name += "(UNREGISTERED)"; - return QString::fromUtf8 (name.c_str(), name.size()); - case kBasic: - name += "Basic"; - break; - default: - name += "Pro"; - } - if (trial) { - name += " (Trial)"; - } - return QString::fromUtf8 (name.c_str(), name.size()); -} - -void SubscriptionManager::notifyActivation(QString identity) -{ - ActivationNotifier* notifier = new ActivationNotifier(); - notifier->setIdentity(identity); - - QThread* thread = new QThread(); - connect(notifier, SIGNAL(finished()), thread, SLOT(quit())); - connect(notifier, SIGNAL(finished()), notifier, SLOT(deleteLater())); - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - - notifier->moveToThread(thread); - thread->start(); - - QMetaObject::invokeMethod(notifier, "notify", Qt::QueuedConnection); -} diff --git a/src/gui/src/main.cpp b/src/gui/src/main.cpp index 18febc8e..d36db755 100644 --- a/src/gui/src/main.cpp +++ b/src/gui/src/main.cpp @@ -2,11 +2,11 @@ * synergy -- mouse and keyboard sharing utility * Copyright (C) 2012-2016 Symless Ltd. * Copyright (C) 2008 Volker Lanz (vl@fidra.de) - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -20,7 +20,7 @@ #define TRAY_RETRY_WAIT 2000 #include "QSynergyApplication.h" -#include "SubscriptionManager.h" +#include "LicenseManager.h" #include "MainWindow.h" #include "AppConfig.h" #include "SetupWizard.h" @@ -66,7 +66,7 @@ int main(int argc, char* argv[]) "Please drag Synergy to the Applications folder, and open it from there."); return 1; } - + if (!checkMacAssistiveDevices()) { return 1; @@ -85,11 +85,11 @@ int main(int argc, char* argv[]) QSettings settings; AppConfig appConfig (&settings); qRegisterMetaType("Edition"); - SubscriptionManager subscriptionManager (&appConfig); + LicenseManager licenseManager (&appConfig); app.switchTranslator(appConfig.language()); - MainWindow mainWindow(settings, appConfig, subscriptionManager); + MainWindow mainWindow(settings, appConfig, licenseManager); SetupWizard setupWizard(mainWindow, true); if (appConfig.wizardShouldRun()) From 98610fabde2264767c76f52b310d2e3f7f22056e Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 17:59:01 +0100 Subject: [PATCH 48/88] #5657 Remove unused CoreInterfaces --- src/gui/src/CoreInterface.cpp | 19 ------------------- src/gui/src/CoreInterface.h | 3 --- 2 files changed, 22 deletions(-) diff --git a/src/gui/src/CoreInterface.cpp b/src/gui/src/CoreInterface.cpp index b06a5d90..f7e987d7 100644 --- a/src/gui/src/CoreInterface.cpp +++ b/src/gui/src/CoreInterface.cpp @@ -38,12 +38,6 @@ CoreInterface::CoreInterface() { } -QString CoreInterface::getPluginDir() -{ - QStringList args("--get-plugin-dir"); - return run(args); -} - QString CoreInterface::getProfileDir() { QStringList args("--get-profile-dir"); @@ -68,19 +62,6 @@ QString CoreInterface::getSerialKeyFilePath() return filename; } -QString CoreInterface::activateSerial(const QString& serial) -{ - QStringList args("--subscription-serial"); - args << serial; - - return run(args); -} - -QString CoreInterface::checkSubscription() -{ - QStringList args("--check-subscription"); - return run(args); -} QString CoreInterface::notifyActivation(const QString& identity) { diff --git a/src/gui/src/CoreInterface.h b/src/gui/src/CoreInterface.h index cd61ae25..c8a291e2 100644 --- a/src/gui/src/CoreInterface.h +++ b/src/gui/src/CoreInterface.h @@ -24,13 +24,10 @@ class CoreInterface public: CoreInterface(); - QString getPluginDir(); QString getProfileDir(); QString getInstalledDir(); QString getArch(); QString getSerialKeyFilePath(); - QString activateSerial(const QString& serial); - QString checkSubscription(); QString notifyActivation(const QString& identity); QString run(const QStringList& args, const QString& input = ""); }; From 859608424d13972a4536f9e4db83bee7e20a5cb6 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Mon, 17 Oct 2016 18:06:29 +0100 Subject: [PATCH 49/88] #5657 Link synergyd against Synergy shared library --- src/cmd/synergyd/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cmd/synergyd/CMakeLists.txt b/src/cmd/synergyd/CMakeLists.txt index 4ee859fc..c8b578a9 100644 --- a/src/cmd/synergyd/CMakeLists.txt +++ b/src/cmd/synergyd/CMakeLists.txt @@ -1,11 +1,11 @@ # synergy -- mouse and keyboard sharing utility # Copyright (C) 2012-2016 Symless Ltd. # Copyright (C) 2012 Nick Bolton -# +# # 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 @@ -35,7 +35,7 @@ else() endif() target_link_libraries(synergyd - arch base common io ipc mt net platform synergy ${libs} ${OPENSSL_LIBS}) + arch base common io ipc mt net platform synergy shared ${libs} ${OPENSSL_LIBS}) if (WIN32) ADD_CUSTOM_COMMAND( From e65631c45142000769dc51ac05c83ff06bed17f5 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Tue, 18 Oct 2016 13:02:36 +0100 Subject: [PATCH 50/88] #5657 Fix wrong logic about checking if serial key expired --- src/lib/server/Server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index 0b1660ea..d3aece30 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -455,7 +455,7 @@ Server::switchScreen(BaseClientProxy* dst, assert(dst != NULL); // if trial is expired, exit the process - if (!m_args.m_serial.isExpired(std::time(0))) { + if (m_args.m_serial.isExpired(std::time(0))) { LOG((CLOG_ERR "trial has expired, aborting server")); exit(kExitSuccess); } From e48be9099de1cd80568cf15aaf47241f6c4fe743 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Tue, 18 Oct 2016 13:05:29 +0100 Subject: [PATCH 51/88] #5657 Remove whitespace for serial key input --- src/gui/src/ActivationDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index a964fca6..2d4d2056 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -58,7 +58,7 @@ void ActivationDialog::accept() std::pair result; try { - QString serialKey = ui->m_pTextEditSerialKey->toPlainText(); + QString serialKey = ui->m_pTextEditSerialKey->toPlainText().trimmed(); result = m_LicenseManager->setSerialKey(serialKey); } catch (std::exception& e) { From 45f37c508ccde13f278805546a991dca02a162ec Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Tue, 18 Oct 2016 14:56:48 +0100 Subject: [PATCH 52/88] #5657 Make serial key argument for server only --- src/gui/src/MainWindow.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 417fcb9f..4866ec26 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -554,10 +554,6 @@ void MainWindow::startSynergy() args << "--name" << getScreenName(); - if (!appConfig().serialKey().isEmpty()) { - args << "--serial-key" << appConfig().serialKey(); - } - if (desktopMode) { setSynergyProcess(new QProcess(this)); @@ -787,6 +783,10 @@ bool MainWindow::serverArgs(QStringList& args, QString& app) #endif args << "-c" << configFilename << "--address" << address(); + if (!appConfig().serialKey().isEmpty()) { + args << "--serial-key" << appConfig().serialKey(); + } + #if defined(Q_OS_WIN) // pass in physical resolution and primary screen center // TODO: get this information in the core binary even when From 880864a249d1b482f6880d0b35c49ecb71636f27 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Tue, 18 Oct 2016 14:57:34 +0100 Subject: [PATCH 53/88] Version to 1.8.5-rc1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fc5a3311..6d3bf196 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,8 +17,8 @@ # Version number for Synergy set(VERSION_MAJOR 1) set(VERSION_MINOR 8) -set(VERSION_REV 4) -set(VERSION_STAGE stable) +set(VERSION_REV 5) +set(VERSION_STAGE rc1) set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REV}") cmake_minimum_required(VERSION 2.6) From e5aae66ff70733b42aaaae0a7c77d667a7fb2ced Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Tue, 18 Oct 2016 15:13:19 +0100 Subject: [PATCH 54/88] #5657 Don't clear edition when appconfig contains an invalid serial key --- src/gui/src/LicenseManager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/src/LicenseManager.cpp b/src/gui/src/LicenseManager.cpp index 53ee76cf..ea9c61f5 100644 --- a/src/gui/src/LicenseManager.cpp +++ b/src/gui/src/LicenseManager.cpp @@ -31,7 +31,6 @@ LicenseManager::LicenseManager(AppConfig* appConfig) : } catch (...) { /* Remove garbage serial keys from the registry */ m_AppConfig->setSerialKey(""); - m_AppConfig->setEdition(kUnregistered); m_AppConfig->saveSettings(); } } From 17961501f640d8af9b36903ae6d64e3f949a4269 Mon Sep 17 00:00:00 2001 From: Josh Harris Date: Tue, 18 Oct 2016 15:26:56 +0100 Subject: [PATCH 55/88] Update ISSUE_TEMPLATE.md Moved the 'Server' and 'Client' fields around --- .github/ISSUE_TEMPLATE.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index fa15485a..590f59a5 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,8 +1,7 @@ ### Operating Systems ### -Client: Applesoft Windy OS 10 - Server: microOS Tiara +Client: Applesoft Windy OS 10 **READ ME, DELETE ME**: On Windows, hold the Windows key and press 'r', type 'winver' and hit return to get your OS version. On Mac, hit the Apple menu (top left of the screen) and check 'About this Mac'. Linux users... you know what you're using ;) From 020b7974dfc7778fb70a4bb63a56f30a06dc4d4c Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Tue, 18 Oct 2016 15:32:59 +0100 Subject: [PATCH 56/88] #5657 Fix trial warning label and serial key serialisation --- src/gui/src/MainWindow.cpp | 19 +++++++++++-------- src/lib/shared/SerialKey.cpp | 13 +++++++++---- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 417fcb9f..7b88d620 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -1061,15 +1061,18 @@ void MainWindow::setEdition(Edition edition) void MainWindow::beginTrial(bool isExpiring) { if (isExpiring) { - QString expiringNotice = "

%1 days of " - "your Synergy Pro trial remain. " - "Buy now!" - "

"; + QString expiringNotice ("

%1 days of " + "your %2 trial remain. " + "Buy now!" + "

"); expiringNotice = expiringNotice.arg - (m_LicenseManager->serialKey().daysLeft(::time(0))); + (m_LicenseManager->serialKey().daysLeft(::time(0))).arg + (LicenseManager::getEditionName(m_LicenseManager->activeEdition())).arg + (QString::fromStdString(m_LicenseManager->serialKey().toString())); + this->m_trialLabel->setText(expiringNotice); this->m_trialWidget->show(); } diff --git a/src/lib/shared/SerialKey.cpp b/src/lib/shared/SerialKey.cpp index cdc7695f..60a039cb 100644 --- a/src/lib/shared/SerialKey.cpp +++ b/src/lib/shared/SerialKey.cpp @@ -126,15 +126,20 @@ std::string SerialKey::toString() const { std::ostringstream oss; - oss << "v2;"; - oss << (isTrial() ? "trial" : "lifetime") << ";"; + oss << "{"; + if (isTrial()) { + oss << "v2;trial;"; + } else { + oss << "v1;"; + } oss << editionString() << ";"; oss << m_name << ";"; oss << m_userLimit << ";"; oss << m_email << ";"; oss << m_company << ";"; - oss << m_warnTime << ";"; - oss << m_expireTime; + oss << (isTrial() ? m_warnTime : 0) << ";"; + oss << (isTrial() ? m_expireTime : 0); + oss << "}"; return hexEncode(oss.str()); } From dfc7c31d67abfd2217c266a6ef693b41f29982e2 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Tue, 18 Oct 2016 15:43:15 +0100 Subject: [PATCH 57/88] #5657 Delay auto client adding while activation dialog is shown --- src/gui/src/MainWindow.cpp | 29 ++++++++++++++++++++++++++++- src/gui/src/MainWindow.h | 3 +++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 4866ec26..b5a44569 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -101,7 +101,8 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig, m_BonjourInstall(NULL), m_SuppressEmptyServerWarning(false), m_ExpectedRunningState(kStopped), - m_pSslCertificate(NULL) + m_pSslCertificate(NULL), + m_ActivationDialogRunning(false) { setupUi(this); @@ -1167,6 +1168,14 @@ void MainWindow::on_m_pActionSettings_triggered() void MainWindow::autoAddScreen(const QString name) { if (!m_ServerConfig.ignoreAutoConfigClient()) { + if (m_ActivationDialogRunning) { + // TODO: refactor this code + // add this screen to the pending list and check this list until + // users finish activation dialog + m_PendingClientNames.append(name); + return; + } + int r = m_ServerConfig.autoAddScreen(name); if (r != kAutoAddScreenOk) { switch (r) { @@ -1205,6 +1214,9 @@ void MainWindow::on_m_pButtonConfigureServer_clicked() void MainWindow::on_m_pActivate_triggered() { ActivationDialog activationDialog(this, appConfig(), licenseManager()); + m_ActivationDialogRunning = true; + connect (&activationDialog, SIGNAL(finished(int)), + this, SLOT(on_activationDialogFinish()), Qt::QueuedConnection); activationDialog.exec(); } @@ -1424,10 +1436,25 @@ void MainWindow::on_windowShown() { if (!m_AppConfig->activationHasRun() && (m_AppConfig->edition() == kUnregistered)) { ActivationDialog activationDialog (this, appConfig(), licenseManager()); + m_ActivationDialogRunning = true; + connect (&activationDialog, SIGNAL(finished(int)), + this, SLOT(on_activationDialogFinish()), Qt::QueuedConnection); activationDialog.exec(); } } +void MainWindow::on_activationDialogFinish() +{ + m_ActivationDialogRunning = false; + if (!m_PendingClientNames.empty()) { + foreach (const QString& name, m_PendingClientNames) { + autoAddScreen(name); + } + + m_PendingClientNames.clear(); + } +} + QString MainWindow::getProfileRootForArg() { CoreInterface coreInterface; diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index b7fc2ca5..632abec2 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -221,6 +221,8 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase qRuningState m_ExpectedRunningState; QMutex m_StopDesktopMutex; SslCertificate* m_pSslCertificate; + bool m_ActivationDialogRunning; + QStringList m_PendingClientNames; private slots: void on_m_pCheckBoxAutoConfig_toggled(bool checked); @@ -228,6 +230,7 @@ private slots: void on_m_pButtonApply_clicked(); void installBonjour(); void on_windowShown(); + void on_activationDialogFinish(); signals: void windowShown(); From dc4beba9e915440bbfc2e01f05da60348dcba605 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Tue, 18 Oct 2016 16:48:32 +0100 Subject: [PATCH 58/88] #5680 Stop deleting socket twice if it's been adopted by PacketStream --- src/lib/net/IListenSocket.h | 9 +-------- src/lib/net/SecureListenSocket.cpp | 11 ----------- src/lib/net/SecureListenSocket.h | 1 - src/lib/net/TCPListenSocket.h | 1 - src/lib/server/ClientListener.cpp | 10 ---------- src/lib/server/ClientListener.h | 2 -- src/lib/server/Server.cpp | 7 +------ 7 files changed, 2 insertions(+), 39 deletions(-) diff --git a/src/lib/net/IListenSocket.h b/src/lib/net/IListenSocket.h index 905ec831..2cd6fb1b 100644 --- a/src/lib/net/IListenSocket.h +++ b/src/lib/net/IListenSocket.h @@ -41,14 +41,7 @@ public: */ virtual IDataSocket* accept() = 0; - - //! Delete connection socket - /*! - This is used when the socket was created but not adopted by a client - proxy. - */ - virtual void deleteSocket(void*) = 0; - + //@} // ISocket overrides diff --git a/src/lib/net/SecureListenSocket.cpp b/src/lib/net/SecureListenSocket.cpp index b4c08646..533ae41a 100644 --- a/src/lib/net/SecureListenSocket.cpp +++ b/src/lib/net/SecureListenSocket.cpp @@ -93,14 +93,3 @@ SecureListenSocket::accept() throw ex; } } - -void -SecureListenSocket::deleteSocket(void* socket) -{ - SecureSocketSet::iterator it; - it = m_secureSocketSet.find((IDataSocket*)socket); - if (it != m_secureSocketSet.end()) { - delete *it; - m_secureSocketSet.erase(it); - } -} diff --git a/src/lib/net/SecureListenSocket.h b/src/lib/net/SecureListenSocket.h index ff19602c..960a8a26 100644 --- a/src/lib/net/SecureListenSocket.h +++ b/src/lib/net/SecureListenSocket.h @@ -33,7 +33,6 @@ public: // IListenSocket overrides virtual IDataSocket* accept(); - void deleteSocket(void*); private: typedef std::set SecureSocketSet; diff --git a/src/lib/net/TCPListenSocket.h b/src/lib/net/TCPListenSocket.h index cf4469a0..41308622 100644 --- a/src/lib/net/TCPListenSocket.h +++ b/src/lib/net/TCPListenSocket.h @@ -43,7 +43,6 @@ public: // IListenSocket overrides virtual IDataSocket* accept(); - virtual void deleteSocket(void*) { } protected: void setListeningJob(); diff --git a/src/lib/server/ClientListener.cpp b/src/lib/server/ClientListener.cpp index 619ba2a0..a8bef7cf 100644 --- a/src/lib/server/ClientListener.cpp +++ b/src/lib/server/ClientListener.cpp @@ -106,12 +106,6 @@ ClientListener::setServer(Server* server) m_server = server; } -void -ClientListener::deleteSocket(void* socket) -{ - m_listen->deleteSocket(socket); -} - ClientProxy* ClientListener::getNextClient() { @@ -213,10 +207,6 @@ ClientListener::handleUnknownClient(const Event&, void* vclient) } delete unknownClient; - - if (m_useSecureNetwork && !handshakeOk) { - deleteSocket(socket); - } } void diff --git a/src/lib/server/ClientListener.h b/src/lib/server/ClientListener.h index 7674386c..545e163a 100644 --- a/src/lib/server/ClientListener.h +++ b/src/lib/server/ClientListener.h @@ -48,8 +48,6 @@ public: //@} - void deleteSocket(void* socket); - //! @name accessors //@{ diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp index d9394ed5..30249f1e 100644 --- a/src/lib/server/Server.cpp +++ b/src/lib/server/Server.cpp @@ -1378,10 +1378,7 @@ Server::handleClientDisconnected(const Event&, void* vclient) removeActiveClient(client); removeOldClient(client); - PacketStreamFilter* streamFileter = dynamic_cast(client->getStream()); - TCPSocket* socket = dynamic_cast(streamFileter->getStream()); delete client; - m_clientListener->deleteSocket(socket); } void @@ -1391,10 +1388,8 @@ Server::handleClientCloseTimeout(const Event&, void* vclient) BaseClientProxy* client = static_cast(vclient); LOG((CLOG_NOTE "forced disconnection of client \"%s\"", getName(client).c_str())); removeOldClient(client); - PacketStreamFilter* streamFileter = dynamic_cast(client->getStream()); - TCPSocket* socket = dynamic_cast(streamFileter->getStream()); + delete client; - m_clientListener->deleteSocket(socket); } void From 02c23905d6bc32a264b176ef40a3d4cf353bca25 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Tue, 18 Oct 2016 17:01:44 +0100 Subject: [PATCH 59/88] #5657 Handle expired keys properly --- src/gui/src/ActivationDialog.cpp | 5 ++-- src/gui/src/LicenseManager.cpp | 45 ++++++++++++++------------------ src/gui/src/LicenseManager.h | 4 +-- src/gui/src/MainWindow.cpp | 31 ++++++++++++++++------ 4 files changed, 47 insertions(+), 38 deletions(-) diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 2d4d2056..4ad3d7b4 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -81,8 +81,9 @@ void ActivationDialog::accept() if (edition != kUnregistered) { if (m_LicenseManager->serialKey().isTrial()) { message.information(this, "Thanks!", - tr("Thanks for trying %1!").arg - (m_LicenseManager->getEditionName(edition))); + tr("Thanks for trying %1!\n\n%2 days of your trial remain").arg + (m_LicenseManager->getEditionName(edition)).arg + (m_LicenseManager->serialKey().daysLeft(::time(0)))); } else { message.information(this, "Activated!", diff --git a/src/gui/src/LicenseManager.cpp b/src/gui/src/LicenseManager.cpp index ea9c61f5..b82a2648 100644 --- a/src/gui/src/LicenseManager.cpp +++ b/src/gui/src/LicenseManager.cpp @@ -26,22 +26,16 @@ LicenseManager::LicenseManager(AppConfig* appConfig) : m_AppConfig(appConfig), m_serialKey(appConfig->edition()) { - try { - setSerialKey(m_AppConfig->serialKey()); - } catch (...) { - /* Remove garbage serial keys from the registry */ - m_AppConfig->setSerialKey(""); - m_AppConfig->saveSettings(); - } } std::pair -LicenseManager::setSerialKey(QString serialKeyString) +LicenseManager::setSerialKey(QString serialKeyString, bool acceptExpired) { std::pair ret (true, ""); - + time_t currentTime = ::time(0); SerialKey serialKey (serialKeyString.toStdString()); - if (serialKey.isExpired(::time(0))) { + + if (!acceptExpired && serialKey.isExpired(currentTime)) { ret.first = false; ret.second = "Serial key expired"; return ret; @@ -50,22 +44,25 @@ LicenseManager::setSerialKey(QString serialKeyString) if (serialKey != m_serialKey) { using std::swap; swap (serialKey, m_serialKey); - m_AppConfig->setSerialKey (serialKeyString); notifyActivation ("serial:" + serialKeyString); emit serialKeyChanged (m_serialKey); + if (serialKey.isTrial()) { + emit endTrial(false); + } + if (m_serialKey.edition() != serialKey.edition()) { m_AppConfig->setEdition (m_serialKey.edition()); emit editionChanged (m_serialKey.edition()); } - if (serialKey.isTrial()) { - emit endTrial(false); - } - if (m_serialKey.isTrial()) { - emit beginTrial(m_serialKey.isExpiring(::time(0))); + if (m_serialKey.isExpired(currentTime)) { + emit endTrial(true); + } else { + emit beginTrial(m_serialKey.isExpiring(currentTime)); + } } m_AppConfig->saveSettings(); @@ -92,13 +89,9 @@ LicenseManager::serialKey() const return m_serialKey; } -void LicenseManager::refresh() const +void LicenseManager::refresh(bool acceptExpired) { - emit serialKeyChanged (m_serialKey); - emit editionChanged (m_serialKey.edition()); - if (m_serialKey.isTrial()) { - emit beginTrial(m_serialKey.isExpiring(::time(0))); - } + setSerialKey (m_AppConfig->serialKey(), acceptExpired); } void LicenseManager::skipActivation() @@ -109,16 +102,16 @@ void LicenseManager::skipActivation() QString LicenseManager::getEditionName(Edition const edition, bool trial) { - std::string name ("Synergy "); + std::string name ("Synergy"); switch (edition) { case kUnregistered: - name += "(UNREGISTERED)"; + name += " (UNREGISTERED)"; return QString::fromUtf8 (name.c_str(), name.size()); case kBasic: - name += "Basic"; + name += " Basic"; break; default: - name += "Pro"; + name += " Pro"; } if (trial) { name += " (Trial)"; diff --git a/src/gui/src/LicenseManager.h b/src/gui/src/LicenseManager.h index edb412af..deac8b4f 100644 --- a/src/gui/src/LicenseManager.h +++ b/src/gui/src/LicenseManager.h @@ -30,8 +30,8 @@ class LicenseManager: public QObject public: LicenseManager(AppConfig* appConfig); - std::pair setSerialKey(QString serialKey); - void refresh() const; + std::pair setSerialKey(QString serialKey, bool acceptExpired = false); + void refresh(bool acceptExpired = false); Edition activeEdition() const; QString activeEditionName() const; SerialKey serialKey() const; diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 7b88d620..e1861ca7 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -154,7 +154,8 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig, connect (m_AppConfig, SIGNAL(sslToggled(bool)), this, SLOT(sslToggled(bool)), Qt::QueuedConnection); - m_LicenseManager->refresh(); + setWindowTitle (m_LicenseManager->activeEditionName()); + m_LicenseManager->refresh(true); } MainWindow::~MainWindow() @@ -1064,15 +1065,14 @@ void MainWindow::beginTrial(bool isExpiring) QString expiringNotice ("

%1 days of " "your %2 trial remain. " + "\"http://symless.com/pricing\">" "Buy now!" "

"); - expiringNotice = expiringNotice.arg - (m_LicenseManager->serialKey().daysLeft(::time(0))).arg - (LicenseManager::getEditionName(m_LicenseManager->activeEdition())).arg - (QString::fromStdString(m_LicenseManager->serialKey().toString())); - + expiringNotice = expiringNotice + .arg (m_LicenseManager->serialKey().daysLeft(::time(0))) + .arg (LicenseManager::getEditionName + (m_LicenseManager->activeEdition())); this->m_trialLabel->setText(expiringNotice); this->m_trialWidget->show(); } @@ -1081,7 +1081,22 @@ void MainWindow::beginTrial(bool isExpiring) void MainWindow::endTrial(bool isExpired) { - if (!isExpired) { + if (isExpired) { + QString expiredNotice ( + "

Your %1 trial has expired. " + "" + "Buy now!

" + ); + expiredNotice = expiredNotice + .arg(LicenseManager::getEditionName + (m_LicenseManager->activeEdition())) + .arg(QString::fromStdString + (m_LicenseManager->serialKey().toString())); + + this->m_trialLabel->setText(expiredNotice); + this->m_trialWidget->show(); + } else { this->m_trialWidget->hide(); } setWindowTitle (m_LicenseManager->activeEditionName()); From 47913e57b8d0f48d2fd84834dcd4f8971789a187 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Tue, 18 Oct 2016 18:45:15 +0100 Subject: [PATCH 60/88] #5657 Raise activation dialog when trial expires --- src/gui/src/AppConfig.cpp | 12 ++++++------ src/gui/src/AppConfig.h | 9 ++++----- src/gui/src/MainWindow.cpp | 9 +++++++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/gui/src/AppConfig.cpp b/src/gui/src/AppConfig.cpp index cdb26e14..7fd37f50 100644 --- a/src/gui/src/AppConfig.cpp +++ b/src/gui/src/AppConfig.cpp @@ -2,11 +2,11 @@ * synergy -- mouse and keyboard sharing utility * Copyright (C) 2012-2016 Symless Ltd. * Copyright (C) 2008 Volker Lanz (vl@fidra.de) - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -153,7 +153,7 @@ void AppConfig::loadSettings() QVariant elevateMode = settings().value("elevateModeEnum"); if (!elevateMode.isValid()) { elevateMode = settings().value ("elevateMode", - QVariant(static_cast(defaultElevateMode))); + QVariant(static_cast(defaultElevateMode))); } m_ElevateMode = static_cast(elevateMode.toInt()); m_AutoConfigPrompted = settings().value("autoConfigPrompted", false).toBool(); @@ -178,8 +178,8 @@ void AppConfig::saveSettings() settings().setValue("language", m_Language); settings().setValue("startedBefore", m_StartedBefore); settings().setValue("autoConfig", m_AutoConfig); - // Refer to enum ElevateMode declaration for insight in to why this - // flag is mapped this way + // Refer to enum ElevateMode declaration for insight in to why this + // flag is mapped this way settings().setValue("elevateMode", m_ElevateMode == ElevateAlways); settings().setValue("elevateModeEnum", static_cast(m_ElevateMode)); settings().setValue("autoConfigPrompted", m_AutoConfigPrompted); @@ -274,7 +274,7 @@ void AppConfig::setCryptoEnabled(bool e) { emit sslToggled(e); } -bool AppConfig::getCryptoEnabled() const { +bool AppConfig::getCryptoEnabled() const { return (edition() == kPro) && m_CryptoEnabled; } diff --git a/src/gui/src/AppConfig.h b/src/gui/src/AppConfig.h index b7eacf61..7aaeeb41 100644 --- a/src/gui/src/AppConfig.h +++ b/src/gui/src/AppConfig.h @@ -2,11 +2,11 @@ * synergy -- mouse and keyboard sharing utility * Copyright (C) 2012-2016 Symless Ltd. * Copyright (C) 2008 Volker Lanz (vl@fidra.de) - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -104,9 +104,8 @@ class AppConfig: public QObject bool activationHasRun() const; AppConfig& activationHasRun(bool value); - void saveSettings(); - - protected: + void saveSettings();; +protected: QSettings& settings(); void setScreenName(const QString& s); void setPort(int i); diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index e1861ca7..5becfbb7 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -448,7 +448,7 @@ void MainWindow::checkConnected(const QString& line) void MainWindow::checkLicense(const QString &line) { if (line.contains("trial has expired")) { - m_LicenseManager->refresh(); + m_LicenseManager->refresh(true); } } @@ -1096,6 +1096,8 @@ void MainWindow::endTrial(bool isExpired) this->m_trialLabel->setText(expiredNotice); this->m_trialWidget->show(); + stopSynergy(); + m_AppConfig->activationHasRun(false); } else { this->m_trialWidget->hide(); } @@ -1440,7 +1442,10 @@ void MainWindow::bonjourInstallFinished() void MainWindow::on_windowShown() { - if (!m_AppConfig->activationHasRun() && (m_AppConfig->edition() == kUnregistered)) { + time_t currentTime = ::time(0); + if (!m_AppConfig->activationHasRun() + && ((m_AppConfig->edition() == kUnregistered) || + (m_LicenseManager->serialKey().isExpired(currentTime)))) { ActivationDialog activationDialog (this, appConfig(), licenseManager()); activationDialog.exec(); } From f441c24a23ef7b447851d70ed480dd568e0a8cea Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Wed, 19 Oct 2016 11:36:48 +0100 Subject: [PATCH 61/88] #5657 Minor activation UI tweaks --- src/gui/res/ActivationDialog.ui | 17 ++--------------- src/gui/res/CancelActivationDialog.ui | 2 +- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/gui/res/ActivationDialog.ui b/src/gui/res/ActivationDialog.ui index 0a980eb2..1d66c454 100644 --- a/src/gui/res/ActivationDialog.ui +++ b/src/gui/res/ActivationDialog.ui @@ -6,8 +6,8 @@ 0 0 - 440 - 214 + 410 + 211
@@ -54,19 +54,6 @@ p, li { white-space: pre-wrap; }
- - - - Qt::Vertical - - - - 20 - 40 - - - - diff --git a/src/gui/res/CancelActivationDialog.ui b/src/gui/res/CancelActivationDialog.ui index b98733f6..054b3fe6 100644 --- a/src/gui/res/CancelActivationDialog.ui +++ b/src/gui/res/CancelActivationDialog.ui @@ -19,7 +19,7 @@ Are you sure? -If you don't activate Synergy you'll be missing out on some great features +If you don't activate Synergy you'll be missing out on some great features. true From e01d0ce4c70cc6c577333fc0ecdc59ae71f8e6e5 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Wed, 19 Oct 2016 16:01:15 +0100 Subject: [PATCH 62/88] #5657 Raise activation dialog when key expires --- src/gui/res/ActivationDialog.ui | 55 +++++++++++++++++++++++++++++++- src/gui/src/ActivationDialog.cpp | 4 +++ src/gui/src/LicenseManager.cpp | 14 ++++---- src/gui/src/MainWindow.cpp | 54 +++++++++++++++++++------------ src/gui/src/MainWindow.h | 5 +-- 5 files changed, 103 insertions(+), 29 deletions(-) diff --git a/src/gui/res/ActivationDialog.ui b/src/gui/res/ActivationDialog.ui index 1d66c454..1425ee0c 100644 --- a/src/gui/res/ActivationDialog.ui +++ b/src/gui/res/ActivationDialog.ui @@ -54,6 +54,57 @@ p, li { white-space: pre-wrap; } + + + + + 2 + + + 0 + + + 0 + + + 8 + + + + + + + + :/res/icons/16x16/money.png + + + + + + + <html><head/><body><p>Your trial has expired. <a href="http://symless.com/pricing?src=gui"><span style=" text-decoration: underline; color:#0000ff;">Buy now!</span></a></p></body></html> + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + @@ -66,7 +117,9 @@ p, li { white-space: pre-wrap; }
- + + + buttonBox diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 4ad3d7b4..3f1cab9f 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -23,6 +23,10 @@ ActivationDialog::ActivationDialog(QWidget* parent, AppConfig& appConfig, { ui->setupUi(this); refreshSerialKey(); + time_t currentTime = ::time(0); + if (!m_LicenseManager->serialKey().isExpired(currentTime)) { + ui->m_trialWidget->hide(); + } } void ActivationDialog::refreshSerialKey() diff --git a/src/gui/src/LicenseManager.cpp b/src/gui/src/LicenseManager.cpp index b82a2648..d3a865e1 100644 --- a/src/gui/src/LicenseManager.cpp +++ b/src/gui/src/LicenseManager.cpp @@ -44,17 +44,17 @@ LicenseManager::setSerialKey(QString serialKeyString, bool acceptExpired) if (serialKey != m_serialKey) { using std::swap; swap (serialKey, m_serialKey); - m_AppConfig->setSerialKey (serialKeyString); - notifyActivation ("serial:" + serialKeyString); - emit serialKeyChanged (m_serialKey); + m_AppConfig->setSerialKey(serialKeyString); + notifyActivation("serial:" + serialKeyString); + emit serialKeyChanged(m_serialKey); if (serialKey.isTrial()) { emit endTrial(false); } if (m_serialKey.edition() != serialKey.edition()) { - m_AppConfig->setEdition (m_serialKey.edition()); - emit editionChanged (m_serialKey.edition()); + m_AppConfig->setEdition(m_serialKey.edition()); + emit editionChanged(m_serialKey.edition()); } if (m_serialKey.isTrial()) { @@ -91,7 +91,9 @@ LicenseManager::serialKey() const void LicenseManager::refresh(bool acceptExpired) { - setSerialKey (m_AppConfig->serialKey(), acceptExpired); + if (!m_AppConfig->serialKey().isEmpty()) { + setSerialKey(m_AppConfig->serialKey(), acceptExpired); + } } void LicenseManager::skipActivation() diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 4efc88ce..10308d2b 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -420,8 +420,10 @@ void MainWindow::appendLogRaw(const QString& text) void MainWindow::updateFromLogLine(const QString &line) { + // TODO: this code makes Andrew cry checkConnected(line); checkFingerprint(line); + checkLicense(line); } void MainWindow::checkConnected(const QString& line) @@ -449,7 +451,7 @@ void MainWindow::checkConnected(const QString& line) void MainWindow::checkLicense(const QString &line) { if (line.contains("trial has expired")) { - m_LicenseManager->refresh(true); + raiseActivationDialog(); } } @@ -541,6 +543,14 @@ void MainWindow::clearLog() void MainWindow::startSynergy() { + SerialKey serialKey = m_LicenseManager->serialKey(); + time_t currentTime = ::time(0); + if (serialKey.isExpired(currentTime)) { + if (QDialog::Rejected == raiseActivationDialog()) { + return; + } + } + bool desktopMode = appConfig().processMode() == Desktop; bool serviceMode = appConfig().processMode() == Service; @@ -1233,11 +1243,7 @@ void MainWindow::on_m_pButtonConfigureServer_clicked() void MainWindow::on_m_pActivate_triggered() { - ActivationDialog activationDialog(this, appConfig(), licenseManager()); - m_ActivationDialogRunning = true; - connect (&activationDialog, SIGNAL(finished(int)), - this, SLOT(on_activationDialogFinish()), Qt::QueuedConnection); - activationDialog.exec(); + raiseActivationDialog(); } void MainWindow::on_m_pButtonApply_clicked() @@ -1452,22 +1458,16 @@ void MainWindow::bonjourInstallFinished() m_pCheckBoxAutoConfig->setChecked(true); } -void MainWindow::on_windowShown() +int MainWindow::raiseActivationDialog() { - time_t currentTime = ::time(0); - if (!m_AppConfig->activationHasRun() - && ((m_AppConfig->edition() == kUnregistered) || - (m_LicenseManager->serialKey().isExpired(currentTime)))) { - ActivationDialog activationDialog (this, appConfig(), licenseManager()); - m_ActivationDialogRunning = true; - connect (&activationDialog, SIGNAL(finished(int)), - this, SLOT(on_activationDialogFinish()), Qt::QueuedConnection); - activationDialog.exec(); + if (m_ActivationDialogRunning) { + return QDialog::Rejected; } -} - -void MainWindow::on_activationDialogFinish() -{ + ActivationDialog activationDialog (this, appConfig(), licenseManager()); + m_ActivationDialogRunning = true; + connect (&activationDialog, SIGNAL(finished(int)), + this, SLOT(on_activationDialogFinish()), Qt::QueuedConnection); + int result = activationDialog.exec(); m_ActivationDialogRunning = false; if (!m_PendingClientNames.empty()) { foreach (const QString& name, m_PendingClientNames) { @@ -1476,6 +1476,20 @@ void MainWindow::on_activationDialogFinish() m_PendingClientNames.clear(); } + if (result == QDialog::Accepted) { + restartSynergy(); + } + return result; +} + +void MainWindow::on_windowShown() +{ + time_t currentTime = ::time(0); + if (!m_AppConfig->activationHasRun() + && ((m_AppConfig->edition() == kUnregistered) || + (m_LicenseManager->serialKey().isExpired(currentTime)))) { + raiseActivationDialog(); + } } QString MainWindow::getProfileRootForArg() diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h index 632abec2..2ca3e711 100644 --- a/src/gui/src/MainWindow.h +++ b/src/gui/src/MainWindow.h @@ -120,7 +120,9 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase void updateLocalFingerprint(); LicenseManager& licenseManager() const; - public slots: + int raiseActivationDialog(); + +public slots: void setEdition(Edition edition); void beginTrial(bool isExpiring); void endTrial(bool isExpired); @@ -230,7 +232,6 @@ private slots: void on_m_pButtonApply_clicked(); void installBonjour(); void on_windowShown(); - void on_activationDialogFinish(); signals: void windowShown(); From f2a1d962bc40ddd5b33104ad0baa7b3ecde11ddb Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Wed, 19 Oct 2016 17:40:34 +0100 Subject: [PATCH 63/88] #5657 Fix skip activation loop --- src/gui/src/ActivationDialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 3f1cab9f..94fbe1a0 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -49,6 +49,8 @@ void ActivationDialog::reject() m_LicenseManager->skipActivation(); m_appConfig->activationHasRun(true); m_appConfig->saveSettings(); + } else { + return; } } QDialog::reject(); From ae590907a8d18a6536822faa8d3d7bc83b3da2f3 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Wed, 19 Oct 2016 17:50:44 +0100 Subject: [PATCH 64/88] #5657 Remind users to activate all devices if they might be using SSL --- src/gui/src/ActivationDialog.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 94fbe1a0..4f167cd4 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -85,11 +85,20 @@ void ActivationDialog::accept() Edition edition = m_LicenseManager->activeEdition(); if (edition != kUnregistered) { + QString thanksMessage = tr("Thanks for trying %1! %3\n\n%2 days of " + "your trial remain"). + arg (m_LicenseManager->getEditionName(edition)). + arg (m_LicenseManager->serialKey().daysLeft(::time(0))); + + if (edition == kPro) { + thanksMessage = thanksMessage.arg("If you're using SSL, " + "remember to activate all of your devices."); + } else { + thanksMessage = thanksMessage.arg(""); + } + if (m_LicenseManager->serialKey().isTrial()) { - message.information(this, "Thanks!", - tr("Thanks for trying %1!\n\n%2 days of your trial remain").arg - (m_LicenseManager->getEditionName(edition)).arg - (m_LicenseManager->serialKey().daysLeft(::time(0)))); + message.information(this, "Thanks!", thanksMessage); } else { message.information(this, "Activated!", From 868887155dea2ec479f0304cd608ce305e7a4a0c Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Thu, 20 Oct 2016 11:30:02 +0100 Subject: [PATCH 65/88] #5657 Update buy now links --- src/gui/src/ActivationDialog.cpp | 7 +++++++ src/gui/src/MainWindow.cpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 4f167cd4..26392d98 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -34,6 +34,13 @@ void ActivationDialog::refreshSerialKey() ui->m_pTextEditSerialKey->setText(m_appConfig->serialKey()); ui->m_pTextEditSerialKey->setFocus(); ui->m_pTextEditSerialKey->moveCursor(QTextCursor::End); + ui->m_trialLabel->setText(tr("

Your trial has " + "expired. Buy now!" + "

") + .arg (m_appConfig->serialKey())); } ActivationDialog::~ActivationDialog() diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 10308d2b..1178edd9 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -1095,7 +1095,7 @@ void MainWindow::endTrial(bool isExpired) if (isExpired) { QString expiredNotice ( "

Your %1 trial has expired. " + "\"https://symless.com/synergy/trial/thanks?id=%2\">" "" "Buy now!

" ); From bdf55460587f9ab77113cfc0a2c8d628b8bf843e Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 20 Oct 2016 14:02:01 +0100 Subject: [PATCH 66/88] Revert "#5640 About dialog tweaks" This reverts commit 9837c982cd2ff0ec89b9683eaabd6657868f858a. --- src/gui/res/AboutDialogBase.ui | 87 +++++++++++++++++----------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/src/gui/res/AboutDialogBase.ui b/src/gui/res/AboutDialogBase.ui index 01df3dd9..52d8330f 100644 --- a/src/gui/res/AboutDialogBase.ui +++ b/src/gui/res/AboutDialogBase.ui @@ -13,19 +13,25 @@ 0 0 450 - 378 + 350 - + 0 0 + + + 450 + 350 + + 450 - 378 + 350 @@ -35,20 +41,48 @@ true - - + + - + 0 0 - + + <p> +Keyboard and mouse sharing application. Cross platform, open source, and totally awesome.<br /><br /> +Copyright © 2012-2016 Symless Ltd.<br /> +Copyright © 2002-2012 Chris Schoeneman, Nick Bolton, Volker Lanz.<br /> +Synergy is based on CosmoSynergy by Richard Lee and Adam Feder.<br /> +The Synergy GUI is based on QSynergy by Volker Lanz.<br /><br /> +Visit our website for help and info (symless.com). +</p> +Synergy is released under the GNU General Public License (GPLv2).<br /><br /> + + + 1 + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + - 450 - 16777215 + 20 + 100 + + + + @@ -163,41 +197,6 @@
- - - - - 0 - 0 - - - - <html><head/><body><p>Keyboard and mouse sharing application. <br/><br/>Copyright © 2012-2016 Symless Ltd.<br/>Copyright © 2002-2012 Chris Schoeneman, Nick Bolton, Volker Lanz.</p><p>Synergy is based on CosmoSynergy by Richard Lee and Adam Feder.<br/>The Synergy GUI is based on QSynergy by Volker Lanz. </p><p>Synergy is released under the GNU General Public License (GPLv2).</p></body></html> - - - false - - - 1 - - - - - - - Qt::Vertical - - - QSizePolicy::Preferred - - - - 20 - 100 - - - -
From 3048ca5fc6505099b7e234a235d6f296bd877614 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 20 Oct 2016 14:02:34 +0100 Subject: [PATCH 67/88] Revert "#5640 About dialog tweaks" This reverts commit 03b8788660af08290f74cc4812ac32f29932010c. --- src/gui/res/AboutDialogBase.ui | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gui/res/AboutDialogBase.ui b/src/gui/res/AboutDialogBase.ui index 52d8330f..ba18e309 100644 --- a/src/gui/res/AboutDialogBase.ui +++ b/src/gui/res/AboutDialogBase.ui @@ -13,7 +13,7 @@ 0 0 450 - 350 + 300 @@ -25,13 +25,13 @@ 450 - 350 + 300 450 - 350 + 300 @@ -51,14 +51,14 @@ <p> -Keyboard and mouse sharing application. Cross platform, open source, and totally awesome.<br /><br /> +Keyboard and mouse sharing application. Cross platform and open source.<br /><br /> Copyright © 2012-2016 Symless Ltd.<br /> -Copyright © 2002-2012 Chris Schoeneman, Nick Bolton, Volker Lanz.<br /> +Copyright © 2002-2012 Chris Schoeneman, Nick Bolton, Volker Lanz.<br /><br /> +Synergy is released under the GNU General Public License (GPLv2).<br /><br /> Synergy is based on CosmoSynergy by Richard Lee and Adam Feder.<br /> The Synergy GUI is based on QSynergy by Volker Lanz.<br /><br /> Visit our website for help and info (symless.com). -</p> -Synergy is released under the GNU General Public License (GPLv2).<br /><br /> +</p> 1 From e17130f0603eb799738c525be6c8c05f835506c0 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Thu, 20 Oct 2016 14:02:47 +0100 Subject: [PATCH 68/88] Revert "#5640 Update icon to the new hotness" This reverts commit 833c73f1bdb4ade0b89b9ca93da61db26654a548. --- res/synergy.ico | Bin 24277 -> 287934 bytes src/gui/res/icons/256x256/synergy.ico | Bin 24277 -> 287934 bytes src/gui/res/image/about.png | Bin 5701 -> 4941 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/res/synergy.ico b/res/synergy.ico index 9e3d57100883a631db42ac5c9ef323228f84b43f..fc2e41468ec60a88e0da4194f288a18572f3f36f 100644 GIT binary patch literal 287934 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 literal 24277 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 z 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 diff --git a/src/gui/res/icons/256x256/synergy.ico b/src/gui/res/icons/256x256/synergy.ico index 9e3d57100883a631db42ac5c9ef323228f84b43f..fc2e41468ec60a88e0da4194f288a18572f3f36f 100644 GIT binary patch literal 287934 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 literal 24277 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 z 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 diff --git a/src/gui/res/image/about.png b/src/gui/res/image/about.png index 66307bb077761806aace467307b7b7ae93e58a95..4630bd32a5d36f7588a3d8b4bbf9b8706e80adec 100644 GIT binary patch literal 4941 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 LNkvXXu0mjfO~zre literal 5701 zcmV-L7P{$)P)*H>Q%MKG-%M^7y5?0SX5k7!_aXX zQf-wb-QZ($koF)~f?PsH?1CUJDRUG*l6hv?^(I|JKlm;}(CNw?g@qf7*L?o1)?^wQ z5<-1nATp+j`GN5p2idHp1K?v{5VOyU2%@#zQ9L)}?6TKQV#Gh;t9yMED+5^ugjwy# zKbKLwcAZmeLJb!kLCjGJ;;;#u{%Ef%9|a#D2QkW4L=g7@LHYtgz5s%BQgE4KAj%wj ze*O~6+V$USO{pR531W(jW5ZwE(hP_(2R`1dyb@w@xzjQs;m~~9WEW=ak(Bfq@;YG^4MQS$KTmk zYgG;5Ll9#`jPUTQyRRSo{AWiTwu+aLE2=6W>R9Q8(^add_x~;;DlsXDsw8-v&gsTx zm+b|TT&FdyhDIld(HyM@lWK&+R=E#8whV_G0+MtBl4QpB?K4tqUJb!QSJwj#rYK|j z#Ec0JyY-!5&g1cTm^m`u+UigJcONU-*G?}OS_gesZg-R}Cb~y-r<;t1l;mwyKc3K9 zT0_HW5Fkgy@QE2qfgF=VRF6cEULcc0cyF+LG^;E`=f`Y;2SCPZ&8rs4-CD@ZMCMt$3Y-}3hGAn`g~j7>Cq*dHZRed zT|-C-qBEF;^hsH5fE@dopHvYAS|VQ%#l_{tXWv*oduWj=6_A+7k{%e+!d+>720lIv z5?802HRuo7PUhWLvU$rft?4y{k{~7^$aWw|Y8|d&1@mc6Maj80KAkn>LeTb7Qc_CN z&>=J2m6gL-9Wns~gJF&ByC$kbhut2BqYr!)sj5yoT@#nZQffPqyI*U54MAxTAO}mInAz0j za-33WTDgE6TPw@X_g?k(eV+QNygs?f6-h$|e*)U%W|dlGzb*IVT_syG@`JcM&emhF zq9aH=J5Zd`YIIhKm?-Q7nuAJy#cUSj#WmQJ>^MI1A68E?T~Fm>3zkOXZ>0 zvg$13x&VU(c)dfcdfw*GAh^DzD@cEkDIhyReqicz(llHwCYGo(uo088l0kc5cCA4< zIfQI$dC?s!XAX2X#0`Mo;JgmRxl$g@@y_i89|c;P50I9aq})hN&;B`OZPnpMw_ zD8+3c^L;!IjL7W(*#`0p_Zto@xbW)+t*ia^2!g+jR6&listU^^D`(u>Savs>JdY?X z4B95n)UxGWalLL=QXB&Txcs+?S;;?4ax#jRjkUKc7F%{%f-gpC9 z2J)iTwSL90_G*zJ|2AAmkxm|8fSw%tH8|CgPGMXuBV5UWMqZ14!c=(1j|H&2L=K< zv+4{0^iwdxrDnqkTx)~N0PLKPh?)$u0xG!jZ9#C3D1oknzR($v&*(2xI4Gc5c@6ak z5BXo)S#=njpVvY^E0MG)UhjLlSE)am401iltsv__a4yP4zu~h=1L;Eld#Hc@K$^Nz zavqE%npy2II)>RXfA1TW&-Qw%w?M}h2P6ot@B{Ozh1=!4RN)1xK;8`(#yj%Q6@wO8 zmN;nOv+jyYC5JSn&i7-0GB)buO(YGJ4)QNQ$*RN7zVJH8c&fL}PZ{*1#?U!qeEj|b`B>wr<9bOu%ZIr-8Du(j zZY)2R)uaivq#zK^9pR2QZ&Ew>0uFfNK>p3D=b73CcplmGyycQzk?aR3SmT`!#B~uF zQsVhCu>{3Y7eVlV_BgTpt$T9AOf%x<4SN)pO#zWhuRjHnBl(OY&OcO|p;Giv7*H52 zLk`4cL|81hDgZgWTH!tBKYG`mM)s%uki!ey%WNt6TCoAjJxE#u2|0!RI)~0e9e+^( zaxfZLFoO6YtKLs75HMKsD3jkwX9itUQ0s#zCrEXS|36`9Iu_7~ssssSMg#)RxNq<= zy5oq3^CERw1AnW`NEpYIXD|-|nMNH~8b~-mBQ%f4$PDAj@ex<2a_Za@Y4qRDnJ+Ja z-08JgeOw1e@n@(EZ>^NmZ-4Va#=Rh0LAG$F+;AD7yMZkk-Ze%^^*dA}OCi*X&GXdJ`dDAVTr*J`cS2T+SZ~q#f|QNt~IQLiO{=5yVZS zq7vX}vMt=$0e%Q8TDAI|ifGm)3I~Ik)IoUr7S%aUB(5h1-Pamlbf)<|zq&C0srxr` z<^--|`9cyktnzWptE@U4y_Iw57gF7L4d}(w)G4Kd!QcHj^z+CJ^8!hHt>p7bN!^;U znlz;+$v60j>erPBBn_@SOwUOQ7x^*o_y`Pmx1!A>VoLq0XPN&Lsga!an8K0XKSA7X z7yI~?+jB*oeuInyVz*n%-U0u^&B7CvtoF=yIXTYzxXz;nHVQ~!XWHne4F0x>Oiw=b zWhbXaQi%w@qE=lv?Xgv+Zkvb*4nM!|C9}(m){+j?QxZTxe87I46(9u;pg(i>9LV%} ziHIR3bsFctJk6ccSAZc4T6 z_eYewY1F@3@*RdOH*?NzPe0DG)EB^1mak;$g!3|i7TxFi@JDP!VyOn*L(#f4GMjiw zBY#5L+1msl2#)T{neR}vPNJPpu)x{Rm1M^H8vsh^cc-Lpqoy$U&>nh@?0y&U`I-k2 z1;O=PmtNx|pYFc*+6O0Zo$7Kq(n(z+%l7H?wwY;tUd}4Bh*OtOZ~O^DKd^dSt~VlM zn=Wn9?$6ITJ5;V>_l#~zjc`)b8K#$BIF*)np;kZ5YUXvA9}}~;L^F+`AcKR`EYf~Io7K$4C?W_xv@?2x_}KiME*%{9v6G*< z?aTi=r{+kVe%{WmqwH12Lr_;V^23rKj!{oj#Vavkv5t;wN%uKO#TnCN88qh|Cs5HjP@NC8kDC2|?PD29cUZ`(&RIYmxfCxcO3 zKR>6dn;I4`(9ULcF+orj?P_quUEMyV6e_Md(_Ui9ITF(JC_4zh1? zRPw&QA(}|=fu!7S>NIhZh}HsDy+?5ma$u!?%QrIDt?=Wt*9F_@KI}}{t~VyCpTCK@ zQU&L@jw#C;k5y2oS3nlib4NTwk+dpu+@;!QRv_)+k0Abhm0Cu+2WS)(qZem<(IxTu zzPpRUkRvTfo%)5Vs#2fasYl;7k9(FLo&WNI5MB2dGEwf5m|F?t1Y0CT!jW~b#98k| z4!l%*C=F2$D!Qv!GoGX}uS7fm2Ftl8`wPX!MOMH&!h#>tXeZGn`xl69& z`@$)OH>%?rG(5D&Rqa^Ee|*>!w1<-731o+d5DA&M!RC`5;QA*(%k=MUE?D2LIk4P4~v@2vK$d}ffokrML=JL*}sCx%bA=wp*WDcr&&xvQlmeDe4~OO_kvTfU?ol!Hk>}%KK}1tag4E&QkZ|B4m5&`8j#p|1*w=6PfSHP9ATv zz`0+6@Bw$tjuhnZrM++fo6Ks@)dWH@-IF(Z)sNc58yLX$KEM=gs^O0y*-B~weuxkq zr*92@NQW>aNM{uf^O%gE_Ml`SK;PyQ8UgQMwZoO@N8l{;_C(j6#X7y9blreS&KYiC|0AK}ltuU3mH8iDg8V4c zko&pOPER&1P7;>WpyDAS+{zullKD^Q{F3XKUgS1_bN&R`;G1cL#(XD!GV)1s*1sY` zY^3L~T@tk6qi$+2hXw*C=M%n2-)|zb)9Y}TFh4WiK5&5AmDdtBGL>C*B8bG%QIwAb zTEqxiH{zWn^kBBo+Nl_(Pl zPzszde5pzWY8W@s?Z)7Zd|Ld>@n&fxI|a(eeGyg;nt`RG5-sbyx2ygB~Osj z5I>$Q1*F3f_cqcl1qz2NFBxIqwDS((Lg_vkzKWH$;qT3*X;dTsGQAjc}D$AAXe3L2!x zcXMB=i*tJwbtX(KjH5v%mjWESnD6v#pH*koiH*c2i)?7cn@$5su82)AtbB8AXR#rf0otPlXpi?nd#HIW0*;zXS>pl+ zsJmFrjwvU}6w=_^pO8lR)+*?mlUPj(i;doMO* z)D4~Pc`{|NK4X4o|fvL5? zXcAS&T83I={KRwY{VWbA-}a9pCQ`+T##tkNZG-G{!p$@wuTX zi1Q*7xLh;+{3?f2OLL#T$}Hmkc^}@{+VPQ`DTGS`9ShxaEWsznOeBUrho z1bpaqp>4k$|PCYzp+%&DpHH6d$sb$bvU5+Qa<-7$%`80%eh;n6&*@)kj7d{&YT7f^+ymyRi&%y$!mWAYp8#MAgmQGE0AQEIa2p_s440m^fxqa5%0}z{E(UPD8P1FwO#Bu+1Qev+fi;@v&LyB+>1kUUXuI9_(@ r`z;4vd*WNI$uwxtpdn1~UjYUHJ07uvdGRB800000NkvXXu0mjf|6u05 From 1842a68a0ed58e207ef3ddeed0e5ce0e2ff1e157 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Thu, 20 Oct 2016 14:55:21 +0100 Subject: [PATCH 69/88] #5657 Always show trial counter, fix plurality --- src/gui/src/MainWindow.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 1178edd9..77a9a841 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -1072,21 +1072,26 @@ void MainWindow::setEdition(Edition edition) void MainWindow::beginTrial(bool isExpiring) { - if (isExpiring) { + //Hack + //if (isExpiring) { + time_t daysLeft = m_LicenseManager->serialKey().daysLeft(::time(0)); QString expiringNotice ("

%1 days of " + "font-weight:600;\">%1 day%3 of " "your %2 trial remain. " + "\"https://symless.com/synergy/trial/thanks?id=%4\">" "Buy now!" "

"); expiringNotice = expiringNotice - .arg (m_LicenseManager->serialKey().daysLeft(::time(0))) + .arg (daysLeft) .arg (LicenseManager::getEditionName - (m_LicenseManager->activeEdition())); + (m_LicenseManager->activeEdition())) + .arg ((daysLeft == 1) ? "" : "s") + .arg(QString::fromStdString + (m_LicenseManager->serialKey().toString())); this->m_trialLabel->setText(expiringNotice); this->m_trialWidget->show(); - } + //} setWindowTitle (m_LicenseManager->activeEditionName()); } From b66043e0008379054a179411046afc211a502ec5 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Thu, 20 Oct 2016 17:59:03 +0100 Subject: [PATCH 70/88] #5657 Tweak plurality of trial countdown --- src/gui/src/ActivationDialog.cpp | 9 ++++++--- src/gui/src/MainWindow.cpp | 7 ++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 26392d98..6abbc938 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -91,11 +91,14 @@ void ActivationDialog::accept() } Edition edition = m_LicenseManager->activeEdition(); + time_t daysLeft = m_LicenseManager->serialKey().daysLeft(::time(0)); if (edition != kUnregistered) { - QString thanksMessage = tr("Thanks for trying %1! %3\n\n%2 days of " - "your trial remain"). + QString thanksMessage = tr("Thanks for trying %1! %5\n\n%2 day%3 of " + "your trial remain%4"). arg (m_LicenseManager->getEditionName(edition)). - arg (m_LicenseManager->serialKey().daysLeft(::time(0))); + arg (daysLeft). + arg ((daysLeft == 1) ? "" : "s"). + arg ((daysLeft == 1) ? "s" : ""); if (edition == kPro) { thanksMessage = thanksMessage.arg("If you're using SSL, " diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 77a9a841..8a2888fe 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -1077,7 +1077,7 @@ void MainWindow::beginTrial(bool isExpiring) time_t daysLeft = m_LicenseManager->serialKey().daysLeft(::time(0)); QString expiringNotice ("

%1 day%3 of " - "your %2 trial remain. " "Buy now!" @@ -1087,8 +1087,9 @@ void MainWindow::beginTrial(bool isExpiring) .arg (LicenseManager::getEditionName (m_LicenseManager->activeEdition())) .arg ((daysLeft == 1) ? "" : "s") - .arg(QString::fromStdString - (m_LicenseManager->serialKey().toString())); + .arg (QString::fromStdString + (m_LicenseManager->serialKey().toString())) + .arg ((daysLeft == 1) ? "s" : ""); this->m_trialLabel->setText(expiringNotice); this->m_trialWidget->show(); //} From 492df1f3fd66de2b0020e8444585ff33c3cc9fe2 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Tue, 25 Oct 2016 16:27:12 +0100 Subject: [PATCH 71/88] #5657 Stop service and refresh license when trial expires --- src/gui/src/LicenseManager.cpp | 7 +++++-- src/gui/src/LicenseManager.h | 2 +- src/gui/src/MainWindow.cpp | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/gui/src/LicenseManager.cpp b/src/gui/src/LicenseManager.cpp index d3a865e1..aa623c88 100644 --- a/src/gui/src/LicenseManager.cpp +++ b/src/gui/src/LicenseManager.cpp @@ -89,10 +89,13 @@ LicenseManager::serialKey() const return m_serialKey; } -void LicenseManager::refresh(bool acceptExpired) +void LicenseManager::refresh() { if (!m_AppConfig->serialKey().isEmpty()) { - setSerialKey(m_AppConfig->serialKey(), acceptExpired); + setSerialKey(m_AppConfig->serialKey(), true); + } + if (m_serialKey.isExpired(::time(0))) { + emit endTrial(true); } } diff --git a/src/gui/src/LicenseManager.h b/src/gui/src/LicenseManager.h index deac8b4f..3d1d03b8 100644 --- a/src/gui/src/LicenseManager.h +++ b/src/gui/src/LicenseManager.h @@ -31,7 +31,7 @@ class LicenseManager: public QObject public: LicenseManager(AppConfig* appConfig); std::pair setSerialKey(QString serialKey, bool acceptExpired = false); - void refresh(bool acceptExpired = false); + void refresh(); Edition activeEdition() const; QString activeEditionName() const; SerialKey serialKey() const; diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 8a2888fe..a431d27e 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -156,7 +156,7 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig, this, SLOT(sslToggled(bool)), Qt::QueuedConnection); setWindowTitle (m_LicenseManager->activeEditionName()); - m_LicenseManager->refresh(true); + m_LicenseManager->refresh(); } MainWindow::~MainWindow() @@ -451,6 +451,7 @@ void MainWindow::checkConnected(const QString& line) void MainWindow::checkLicense(const QString &line) { if (line.contains("trial has expired")) { + licenseManager().refresh(); raiseActivationDialog(); } } From f08f0b3f37f9212ff93ab3c959d3f63e3224c922 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Tue, 25 Oct 2016 16:40:33 +0100 Subject: [PATCH 72/88] #5657 Fix activation dialog tab order --- src/gui/res/ActivationDialog.ui | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gui/res/ActivationDialog.ui b/src/gui/res/ActivationDialog.ui index 1425ee0c..86fea30d 100644 --- a/src/gui/res/ActivationDialog.ui +++ b/src/gui/res/ActivationDialog.ui @@ -42,6 +42,9 @@ true + + true + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -117,6 +120,9 @@ p, li { white-space: pre-wrap; } + + m_pTextEditSerialKey + From b7e0473cb4e582015aca422a3b83f0a0023d9297 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Wed, 26 Oct 2016 15:34:36 +0100 Subject: [PATCH 73/88] Update buildbot to Qt 4.8.7 --- src/gui/src/MainWindow.cpp | 1 - src/gui/src/ZeroconfService.h | 1 + src/setup/win32/Include.wxi | 2 +- src/setup/win32/Product.wxs | 3 ++- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index a431d27e..8e53f2d6 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -51,7 +51,6 @@ #endif #if defined(Q_OS_WIN) -#define _WIN32_WINNT 0x0501 #define WIN32_LEAN_AND_MEAN #include #endif diff --git a/src/gui/src/ZeroconfService.h b/src/gui/src/ZeroconfService.h index 8f9aa6db..fde78201 100644 --- a/src/gui/src/ZeroconfService.h +++ b/src/gui/src/ZeroconfService.h @@ -21,6 +21,7 @@ #include "ZeroconfRecord.h" #include +#include typedef int32_t DNSServiceErrorType; diff --git a/src/setup/win32/Include.wxi b/src/setup/win32/Include.wxi index 62f92ac7..0961390b 100644 --- a/src/setup/win32/Include.wxi +++ b/src/setup/win32/Include.wxi @@ -7,7 +7,7 @@ - + diff --git a/src/setup/win32/Product.wxs b/src/setup/win32/Product.wxs index 03a309e8..51a9976b 100644 --- a/src/setup/win32/Product.wxs +++ b/src/setup/win32/Product.wxs @@ -118,7 +118,8 @@ - + + From 906fd15b4a2e7421ff2f697cf6a94bec60022f22 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Wed, 26 Oct 2016 15:57:38 +0100 Subject: [PATCH 74/88] #5707 Store and update last version uses in settings --- src/gui/src/AppConfig.cpp | 11 +++++++++++ src/gui/src/AppConfig.h | 7 ++++++- src/gui/src/MainWindow.cpp | 5 +++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/gui/src/AppConfig.cpp b/src/gui/src/AppConfig.cpp index 7fd37f50..06f9f43d 100644 --- a/src/gui/src/AppConfig.cpp +++ b/src/gui/src/AppConfig.cpp @@ -162,6 +162,7 @@ void AppConfig::loadSettings() m_CryptoEnabled = settings().value("cryptoEnabled", true).toBool(); m_AutoHide = settings().value("autoHide", false).toBool(); m_Serialkey = settings().value("serialKey", "").toString(); + m_lastVersion = settings().value("lastVersion", "Unknown").toString(); m_LastExpiringWarningTime = settings().value("lastExpiringWarningTime", 0).toInt(); m_ActivationHasRun = settings().value("activationHasRun", false).toBool(); } @@ -187,6 +188,7 @@ void AppConfig::saveSettings() settings().setValue("cryptoEnabled", m_CryptoEnabled); settings().setValue("autoHide", m_AutoHide); settings().setValue("serialKey", m_Serialkey); + settings().setValue("lastVersion", m_lastVersion); settings().setValue("lastExpiringWarningTime", m_LastExpiringWarningTime); settings().setValue("activationHasRun", m_ActivationHasRun); settings().sync(); @@ -203,6 +205,15 @@ AppConfig& AppConfig::activationHasRun(bool value) return *this; } +QString AppConfig::lastVersion() const +{ + return m_lastVersion; +} + +void AppConfig::setLastVersion(QString version) { + m_lastVersion = version; +} + QSettings &AppConfig::settings() { return *m_pSettings; } void AppConfig::setScreenName(const QString &s) { m_ScreenName = s; } diff --git a/src/gui/src/AppConfig.h b/src/gui/src/AppConfig.h index 7aaeeb41..4dcd4572 100644 --- a/src/gui/src/AppConfig.h +++ b/src/gui/src/AppConfig.h @@ -104,7 +104,11 @@ class AppConfig: public QObject bool activationHasRun() const; AppConfig& activationHasRun(bool value); - void saveSettings();; + QString lastVersion() const; + + void saveSettings(); + void setLastVersion(QString version); + protected: QSettings& settings(); void setScreenName(const QString& s); @@ -139,6 +143,7 @@ protected: bool m_CryptoEnabled; bool m_AutoHide; QString m_Serialkey; + QString m_lastVersion; int m_LastExpiringWarningTime; bool m_ActivationHasRun; diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 8e53f2d6..59808220 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -156,6 +156,11 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig, setWindowTitle (m_LicenseManager->activeEditionName()); m_LicenseManager->refresh(); + + QString currentVersion = m_VersionChecker.getVersion(); + if (m_AppConfig->lastVersion() != currentVersion) { + m_AppConfig->setLastVersion (currentVersion); + } } MainWindow::~MainWindow() From 9f1e91cc7688e9b0edbb55188f9f633add5cf804 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Wed, 26 Oct 2016 19:50:18 +0100 Subject: [PATCH 75/88] Remove pointless call to curl_free --- src/lib/arch/unix/ArchInternetUnix.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/arch/unix/ArchInternetUnix.cpp b/src/lib/arch/unix/ArchInternetUnix.cpp index 596000c1..fe4a39ba 100644 --- a/src/lib/arch/unix/ArchInternetUnix.cpp +++ b/src/lib/arch/unix/ArchInternetUnix.cpp @@ -116,7 +116,6 @@ CurlFacade::urlEncode(const String& url) char* resultCStr = curl_easy_escape(m_curl, url.c_str(), 0); if (resultCStr == NULL) { - curl_free(resultCStr); throw XArch("CURL escape failed."); } From 2de06b972751dae2086805530d405f02995a3638 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Wed, 26 Oct 2016 20:14:02 +0100 Subject: [PATCH 76/88] #5707 Add support for upgrade notifications --- src/gui/src/CoreInterface.cpp | 6 ++++++ src/gui/src/CoreInterface.h | 1 + src/gui/src/LicenseManager.cpp | 5 +++++ src/gui/src/LicenseManager.h | 1 + src/lib/synergy/ArgParser.cpp | 4 ++++ src/lib/synergy/ToolApp.cpp | 31 ++++++++++++++++++++++++++++--- src/lib/synergy/ToolApp.h | 1 + src/lib/synergy/ToolArgs.cpp | 3 ++- src/lib/synergy/ToolArgs.h | 1 + 9 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/gui/src/CoreInterface.cpp b/src/gui/src/CoreInterface.cpp index f7e987d7..8b3a1ae3 100644 --- a/src/gui/src/CoreInterface.cpp +++ b/src/gui/src/CoreInterface.cpp @@ -62,6 +62,12 @@ QString CoreInterface::getSerialKeyFilePath() return filename; } +QString CoreInterface::notifyUpgrade (QString const& version, + QString const& serialKey) { + QStringList args("--notify-upgrade"); + QString input(version + ":" + serialKey); + return run(args, input); +} QString CoreInterface::notifyActivation(const QString& identity) { diff --git a/src/gui/src/CoreInterface.h b/src/gui/src/CoreInterface.h index c8a291e2..3576f02a 100644 --- a/src/gui/src/CoreInterface.h +++ b/src/gui/src/CoreInterface.h @@ -29,5 +29,6 @@ public: QString getArch(); QString getSerialKeyFilePath(); QString notifyActivation(const QString& identity); + QString notifyUpgrade (QString const& version, QString const& serialKey); QString run(const QStringList& args, const QString& input = ""); }; diff --git a/src/gui/src/LicenseManager.cpp b/src/gui/src/LicenseManager.cpp index aa623c88..15f79933 100644 --- a/src/gui/src/LicenseManager.cpp +++ b/src/gui/src/LicenseManager.cpp @@ -71,6 +71,11 @@ LicenseManager::setSerialKey(QString serialKeyString, bool acceptExpired) return ret; } +void +LicenseManager::notifyUpdate(QString version) { + +} + Edition LicenseManager::activeEdition() const { diff --git a/src/gui/src/LicenseManager.h b/src/gui/src/LicenseManager.h index 3d1d03b8..4d235369 100644 --- a/src/gui/src/LicenseManager.h +++ b/src/gui/src/LicenseManager.h @@ -36,6 +36,7 @@ public: QString activeEditionName() const; SerialKey serialKey() const; void skipActivation(); + void notifyUpdate(QString version); static QString getEditionName(Edition edition, bool trial = false); private: diff --git a/src/lib/synergy/ArgParser.cpp b/src/lib/synergy/ArgParser.cpp index 1af32843..828e4c06 100644 --- a/src/lib/synergy/ArgParser.cpp +++ b/src/lib/synergy/ArgParser.cpp @@ -208,6 +208,10 @@ ArgParser::parseToolArgs(ToolArgs& args, int argc, const char* const* argv) args.m_notifyActivation = true; return true; } + else if (isArg(i, argc, argv, NULL, "--notify-upgrade", 0)) { + args.m_notifyUpgrade = true; + return true; + } else { return false; } diff --git a/src/lib/synergy/ToolApp.cpp b/src/lib/synergy/ToolApp.cpp index bf3dfdce..434ae684 100644 --- a/src/lib/synergy/ToolApp.cpp +++ b/src/lib/synergy/ToolApp.cpp @@ -1,11 +1,11 @@ /* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2014-2016 Symless Ltd. - * + * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. - * + * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -80,6 +80,9 @@ ToolApp::run(int argc, char** argv) else if (m_args.m_getArch) { std::cout << ARCH->getPlatformName() << std::endl; } + else if (m_args.m_notifyUpgrade) { + notifyUpgrade(); + } else if (m_args.m_notifyActivation) { notifyActivation(); } @@ -134,7 +137,29 @@ ToolApp::loginAuth() } } -void +void +ToolApp::notifyUpgrade() +{ + String data; + std::cin >> data; + + std::vector parts = synergy::string::splitString(data, ':'); + size_t count = parts.size(); + + if (count == 2) { + std::stringstream ss; + ss << JSON_URL << "notify/upgraded/"; + ss << "?version=" << parts[0]; + ss << "&serial=" << parts[1]; + + std::cout << ARCH->internet().get(ss.str()) << std::endl; + } + else { + throw XSynergy("Invalid upgrade data."); + } +} + +void ToolApp::notifyActivation() { String info; diff --git a/src/lib/synergy/ToolApp.h b/src/lib/synergy/ToolApp.h index 39c87ca7..0f918827 100644 --- a/src/lib/synergy/ToolApp.h +++ b/src/lib/synergy/ToolApp.h @@ -30,6 +30,7 @@ public: private: void loginAuth(); void notifyActivation(); + void notifyUpgrade(); private: ToolArgs m_args; diff --git a/src/lib/synergy/ToolArgs.cpp b/src/lib/synergy/ToolArgs.cpp index 5f67c666..4884696f 100644 --- a/src/lib/synergy/ToolArgs.cpp +++ b/src/lib/synergy/ToolArgs.cpp @@ -23,6 +23,7 @@ ToolArgs::ToolArgs() : m_getInstalledDir(false), m_getProfileDir(false), m_getArch(false), - m_notifyActivation(false) + m_notifyActivation(false), + m_notifyUpgrade(false) { } diff --git a/src/lib/synergy/ToolArgs.h b/src/lib/synergy/ToolArgs.h index 5febab9e..4a620a9b 100644 --- a/src/lib/synergy/ToolArgs.h +++ b/src/lib/synergy/ToolArgs.h @@ -30,4 +30,5 @@ public: bool m_getProfileDir; bool m_getArch; bool m_notifyActivation; + bool m_notifyUpgrade; }; From 4206799ae357ac603be9720bd702edfcf691d588 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Fri, 28 Oct 2016 13:17:07 +0100 Subject: [PATCH 77/88] #5707 Add from and to version numbers to version update notification --- src/gui/src/ActivationNotifier.cpp | 21 ++++++++++++++++++++- src/gui/src/ActivationNotifier.h | 8 +++++++- src/gui/src/CoreInterface.cpp | 7 ++++--- src/gui/src/CoreInterface.h | 4 +++- src/gui/src/LicenseManager.cpp | 15 ++++++++++++++- src/gui/src/LicenseManager.h | 2 +- src/gui/src/MainWindow.cpp | 5 ++++- src/lib/synergy/ToolApp.cpp | 7 ++++--- 8 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/gui/src/ActivationNotifier.cpp b/src/gui/src/ActivationNotifier.cpp index fb2fd465..e8159a34 100644 --- a/src/gui/src/ActivationNotifier.cpp +++ b/src/gui/src/ActivationNotifier.cpp @@ -20,7 +20,7 @@ #include "CoreInterface.h" ActivationNotifier::ActivationNotifier(QObject *parent) : - QObject(parent) + QObject(parent) { } @@ -29,6 +29,15 @@ void ActivationNotifier::setIdentity(QString identity) m_Identity = identity; } +void ActivationNotifier::setUpgradeInfo(QString const& fromVersion, + QString const& toVersion, + QString const& serialKey) +{ + m_fromVersion = fromVersion; + m_toVersion = toVersion; + m_serialKey = serialKey; +} + void ActivationNotifier::notify() { CoreInterface coreInterface; @@ -39,3 +48,13 @@ void ActivationNotifier::notify() // catch all exceptions and fails silently } } + +void ActivationNotifier::notifyUpgrade() +{ + try { + CoreInterface coreInterface; + coreInterface.notifyUpdate(m_fromVersion, m_toVersion, + m_serialKey); + } catch (...) { + } +} diff --git a/src/gui/src/ActivationNotifier.h b/src/gui/src/ActivationNotifier.h index d245cd27..fda336a4 100644 --- a/src/gui/src/ActivationNotifier.h +++ b/src/gui/src/ActivationNotifier.h @@ -24,18 +24,24 @@ class ActivationNotifier : public QObject { Q_OBJECT public: - explicit ActivationNotifier(QObject *parent = 0); + explicit ActivationNotifier(QObject *parent = 0); void setIdentity(QString identity); + void setUpgradeInfo(QString const& fromVersion, + QString const& toVersion, QString const& serialKey); public slots: void notify(); + void notifyUpgrade(); signals: void finished(); private: QString m_Identity; + QString m_fromVersion; + QString m_toVersion; + QString m_serialKey; }; #endif // ACTIVATIONNOTIFIER_H diff --git a/src/gui/src/CoreInterface.cpp b/src/gui/src/CoreInterface.cpp index 8b3a1ae3..aa8d14a3 100644 --- a/src/gui/src/CoreInterface.cpp +++ b/src/gui/src/CoreInterface.cpp @@ -62,10 +62,11 @@ QString CoreInterface::getSerialKeyFilePath() return filename; } -QString CoreInterface::notifyUpgrade (QString const& version, - QString const& serialKey) { +QString CoreInterface::notifyUpdate (QString const& fromVersion, + QString const& toVersion, + QString const& serialKey) { QStringList args("--notify-upgrade"); - QString input(version + ":" + serialKey); + QString input(fromVersion + ":" + toVersion + ":" + serialKey); return run(args, input); } diff --git a/src/gui/src/CoreInterface.h b/src/gui/src/CoreInterface.h index 3576f02a..98a84f57 100644 --- a/src/gui/src/CoreInterface.h +++ b/src/gui/src/CoreInterface.h @@ -29,6 +29,8 @@ public: QString getArch(); QString getSerialKeyFilePath(); QString notifyActivation(const QString& identity); - QString notifyUpgrade (QString const& version, QString const& serialKey); + QString notifyUpdate (QString const& fromVersion, + QString const& toVersion, + QString const& serialKey); QString run(const QStringList& args, const QString& input = ""); }; diff --git a/src/gui/src/LicenseManager.cpp b/src/gui/src/LicenseManager.cpp index 15f79933..97a1826a 100644 --- a/src/gui/src/LicenseManager.cpp +++ b/src/gui/src/LicenseManager.cpp @@ -72,8 +72,21 @@ LicenseManager::setSerialKey(QString serialKeyString, bool acceptExpired) } void -LicenseManager::notifyUpdate(QString version) { +LicenseManager::notifyUpdate(QString fromVersion, QString toVersion) { + ActivationNotifier* notifier = new ActivationNotifier(); + notifier->setUpgradeInfo (fromVersion, toVersion, + QString::fromStdString(m_serialKey.toString())); + QThread* thread = new QThread(); + connect(notifier, SIGNAL(finished()), thread, SLOT(quit())); + connect(notifier, SIGNAL(finished()), notifier, SLOT(deleteLater())); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + + notifier->moveToThread(thread); + thread->start(); + + QMetaObject::invokeMethod(notifier, "notifyUpgrade", + Qt::QueuedConnection); } Edition diff --git a/src/gui/src/LicenseManager.h b/src/gui/src/LicenseManager.h index 4d235369..4592b28e 100644 --- a/src/gui/src/LicenseManager.h +++ b/src/gui/src/LicenseManager.h @@ -36,7 +36,7 @@ public: QString activeEditionName() const; SerialKey serialKey() const; void skipActivation(); - void notifyUpdate(QString version); + void notifyUpdate(QString fromVersion, QString toVersion); static QString getEditionName(Edition edition, bool trial = false); private: diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 59808220..19a1dfa2 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -157,9 +157,12 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig, setWindowTitle (m_LicenseManager->activeEditionName()); m_LicenseManager->refresh(); + QString lastVersion = m_AppConfig->lastVersion(); QString currentVersion = m_VersionChecker.getVersion(); - if (m_AppConfig->lastVersion() != currentVersion) { + if (lastVersion != currentVersion) { m_AppConfig->setLastVersion (currentVersion); + m_AppConfig->saveSettings(); + m_LicenseManager->notifyUpdate (lastVersion, currentVersion); } } diff --git a/src/lib/synergy/ToolApp.cpp b/src/lib/synergy/ToolApp.cpp index 434ae684..22a871b8 100644 --- a/src/lib/synergy/ToolApp.cpp +++ b/src/lib/synergy/ToolApp.cpp @@ -146,11 +146,12 @@ ToolApp::notifyUpgrade() std::vector parts = synergy::string::splitString(data, ':'); size_t count = parts.size(); - if (count == 2) { + if (count == 3) { std::stringstream ss; ss << JSON_URL << "notify/upgraded/"; - ss << "?version=" << parts[0]; - ss << "&serial=" << parts[1]; + ss << "?from=" << parts[0]; + ss << "&to=" << parts[1]; + ss << "&serial=" << parts[2]; std::cout << ARCH->internet().get(ss.str()) << std::endl; } From 73685c3d923368841b71a154e8ebc68c28922b9d Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Fri, 28 Oct 2016 14:48:01 +0100 Subject: [PATCH 78/88] #5707 Tweak notify url for upgrades --- src/lib/synergy/ToolApp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/synergy/ToolApp.cpp b/src/lib/synergy/ToolApp.cpp index 22a871b8..917894ef 100644 --- a/src/lib/synergy/ToolApp.cpp +++ b/src/lib/synergy/ToolApp.cpp @@ -148,7 +148,7 @@ ToolApp::notifyUpgrade() if (count == 3) { std::stringstream ss; - ss << JSON_URL << "notify/upgraded/"; + ss << JSON_URL << "notify/upgrade"; ss << "?from=" << parts[0]; ss << "&to=" << parts[1]; ss << "&serial=" << parts[2]; From fa7daa48f7f44820f727584e641d53992dd1ed15 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Fri, 28 Oct 2016 14:48:36 +0100 Subject: [PATCH 79/88] Revert "Update buildbot to Qt 4.8.7" This reverts commit b7e0473cb4e582015aca422a3b83f0a0023d9297. --- src/gui/src/MainWindow.cpp | 1 + src/gui/src/ZeroconfService.h | 1 - src/setup/win32/Include.wxi | 2 +- src/setup/win32/Product.wxs | 3 +-- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/gui/src/MainWindow.cpp b/src/gui/src/MainWindow.cpp index 19a1dfa2..f32ebdc8 100644 --- a/src/gui/src/MainWindow.cpp +++ b/src/gui/src/MainWindow.cpp @@ -51,6 +51,7 @@ #endif #if defined(Q_OS_WIN) +#define _WIN32_WINNT 0x0501 #define WIN32_LEAN_AND_MEAN #include #endif diff --git a/src/gui/src/ZeroconfService.h b/src/gui/src/ZeroconfService.h index fde78201..8f9aa6db 100644 --- a/src/gui/src/ZeroconfService.h +++ b/src/gui/src/ZeroconfService.h @@ -21,7 +21,6 @@ #include "ZeroconfRecord.h" #include -#include typedef int32_t DNSServiceErrorType; diff --git a/src/setup/win32/Include.wxi b/src/setup/win32/Include.wxi index 0961390b..62f92ac7 100644 --- a/src/setup/win32/Include.wxi +++ b/src/setup/win32/Include.wxi @@ -7,7 +7,7 @@ - + diff --git a/src/setup/win32/Product.wxs b/src/setup/win32/Product.wxs index 51a9976b..03a309e8 100644 --- a/src/setup/win32/Product.wxs +++ b/src/setup/win32/Product.wxs @@ -118,8 +118,7 @@ - - + From af9037276c718c6b224fe6695422703f19e87132 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Fri, 28 Oct 2016 14:54:38 +0100 Subject: [PATCH 80/88] #5707 Prefer 'update' over 'upgrade' --- src/gui/src/ActivationNotifier.cpp | 4 ++-- src/gui/src/ActivationNotifier.h | 4 ++-- src/gui/src/CoreInterface.cpp | 2 +- src/gui/src/LicenseManager.cpp | 4 ++-- src/lib/synergy/ArgParser.cpp | 4 ++-- src/lib/synergy/ToolApp.cpp | 6 +++--- src/lib/synergy/ToolApp.h | 2 +- src/lib/synergy/ToolArgs.cpp | 2 +- src/lib/synergy/ToolArgs.h | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/gui/src/ActivationNotifier.cpp b/src/gui/src/ActivationNotifier.cpp index e8159a34..0786d90c 100644 --- a/src/gui/src/ActivationNotifier.cpp +++ b/src/gui/src/ActivationNotifier.cpp @@ -29,7 +29,7 @@ void ActivationNotifier::setIdentity(QString identity) m_Identity = identity; } -void ActivationNotifier::setUpgradeInfo(QString const& fromVersion, +void ActivationNotifier::setUpdateInfo(QString const& fromVersion, QString const& toVersion, QString const& serialKey) { @@ -49,7 +49,7 @@ void ActivationNotifier::notify() } } -void ActivationNotifier::notifyUpgrade() +void ActivationNotifier::notifyUpdate() { try { CoreInterface coreInterface; diff --git a/src/gui/src/ActivationNotifier.h b/src/gui/src/ActivationNotifier.h index fda336a4..2ac216c7 100644 --- a/src/gui/src/ActivationNotifier.h +++ b/src/gui/src/ActivationNotifier.h @@ -27,12 +27,12 @@ public: explicit ActivationNotifier(QObject *parent = 0); void setIdentity(QString identity); - void setUpgradeInfo(QString const& fromVersion, + void setUpdateInfo(QString const& fromVersion, QString const& toVersion, QString const& serialKey); public slots: void notify(); - void notifyUpgrade(); + void notifyUpdate(); signals: void finished(); diff --git a/src/gui/src/CoreInterface.cpp b/src/gui/src/CoreInterface.cpp index aa8d14a3..b3c11f0a 100644 --- a/src/gui/src/CoreInterface.cpp +++ b/src/gui/src/CoreInterface.cpp @@ -65,7 +65,7 @@ QString CoreInterface::getSerialKeyFilePath() QString CoreInterface::notifyUpdate (QString const& fromVersion, QString const& toVersion, QString const& serialKey) { - QStringList args("--notify-upgrade"); + QStringList args("--notify-update"); QString input(fromVersion + ":" + toVersion + ":" + serialKey); return run(args, input); } diff --git a/src/gui/src/LicenseManager.cpp b/src/gui/src/LicenseManager.cpp index 97a1826a..f11531a0 100644 --- a/src/gui/src/LicenseManager.cpp +++ b/src/gui/src/LicenseManager.cpp @@ -74,7 +74,7 @@ LicenseManager::setSerialKey(QString serialKeyString, bool acceptExpired) void LicenseManager::notifyUpdate(QString fromVersion, QString toVersion) { ActivationNotifier* notifier = new ActivationNotifier(); - notifier->setUpgradeInfo (fromVersion, toVersion, + notifier->setUpdateInfo (fromVersion, toVersion, QString::fromStdString(m_serialKey.toString())); QThread* thread = new QThread(); @@ -85,7 +85,7 @@ LicenseManager::notifyUpdate(QString fromVersion, QString toVersion) { notifier->moveToThread(thread); thread->start(); - QMetaObject::invokeMethod(notifier, "notifyUpgrade", + QMetaObject::invokeMethod(notifier, "notifyUpdate", Qt::QueuedConnection); } diff --git a/src/lib/synergy/ArgParser.cpp b/src/lib/synergy/ArgParser.cpp index 828e4c06..6693ebf0 100644 --- a/src/lib/synergy/ArgParser.cpp +++ b/src/lib/synergy/ArgParser.cpp @@ -208,8 +208,8 @@ ArgParser::parseToolArgs(ToolArgs& args, int argc, const char* const* argv) args.m_notifyActivation = true; return true; } - else if (isArg(i, argc, argv, NULL, "--notify-upgrade", 0)) { - args.m_notifyUpgrade = true; + else if (isArg(i, argc, argv, NULL, "--notify-update", 0)) { + args.m_notifyUpdate = true; return true; } else { diff --git a/src/lib/synergy/ToolApp.cpp b/src/lib/synergy/ToolApp.cpp index 917894ef..653012ff 100644 --- a/src/lib/synergy/ToolApp.cpp +++ b/src/lib/synergy/ToolApp.cpp @@ -80,8 +80,8 @@ ToolApp::run(int argc, char** argv) else if (m_args.m_getArch) { std::cout << ARCH->getPlatformName() << std::endl; } - else if (m_args.m_notifyUpgrade) { - notifyUpgrade(); + else if (m_args.m_notifyUpdate) { + notifyUpdate(); } else if (m_args.m_notifyActivation) { notifyActivation(); @@ -138,7 +138,7 @@ ToolApp::loginAuth() } void -ToolApp::notifyUpgrade() +ToolApp::notifyUpdate() { String data; std::cin >> data; diff --git a/src/lib/synergy/ToolApp.h b/src/lib/synergy/ToolApp.h index 0f918827..771b298f 100644 --- a/src/lib/synergy/ToolApp.h +++ b/src/lib/synergy/ToolApp.h @@ -30,7 +30,7 @@ public: private: void loginAuth(); void notifyActivation(); - void notifyUpgrade(); + void notifyUpdate(); private: ToolArgs m_args; diff --git a/src/lib/synergy/ToolArgs.cpp b/src/lib/synergy/ToolArgs.cpp index 4884696f..2685c1df 100644 --- a/src/lib/synergy/ToolArgs.cpp +++ b/src/lib/synergy/ToolArgs.cpp @@ -24,6 +24,6 @@ ToolArgs::ToolArgs() : m_getProfileDir(false), m_getArch(false), m_notifyActivation(false), - m_notifyUpgrade(false) + m_notifyUpdate(false) { } diff --git a/src/lib/synergy/ToolArgs.h b/src/lib/synergy/ToolArgs.h index 4a620a9b..4619efc1 100644 --- a/src/lib/synergy/ToolArgs.h +++ b/src/lib/synergy/ToolArgs.h @@ -30,5 +30,5 @@ public: bool m_getProfileDir; bool m_getArch; bool m_notifyActivation; - bool m_notifyUpgrade; + bool m_notifyUpdate; }; From af62174b5937932d282837ceb724de6846f5ec2d Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Fri, 28 Oct 2016 15:58:59 +0100 Subject: [PATCH 81/88] #5707 Only notify activation on user action --- src/gui/src/ActivationDialog.cpp | 1 + src/gui/src/LicenseManager.cpp | 1 - src/gui/src/LicenseManager.h | 2 -- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gui/src/ActivationDialog.cpp b/src/gui/src/ActivationDialog.cpp index 6abbc938..253ec3ee 100644 --- a/src/gui/src/ActivationDialog.cpp +++ b/src/gui/src/ActivationDialog.cpp @@ -90,6 +90,7 @@ void ActivationDialog::accept() return; } + m_LicenseManager->notifyActivation("serial:" + m_appConfig->serialKey()); Edition edition = m_LicenseManager->activeEdition(); time_t daysLeft = m_LicenseManager->serialKey().daysLeft(::time(0)); if (edition != kUnregistered) { diff --git a/src/gui/src/LicenseManager.cpp b/src/gui/src/LicenseManager.cpp index f11531a0..a6af21d8 100644 --- a/src/gui/src/LicenseManager.cpp +++ b/src/gui/src/LicenseManager.cpp @@ -45,7 +45,6 @@ LicenseManager::setSerialKey(QString serialKeyString, bool acceptExpired) using std::swap; swap (serialKey, m_serialKey); m_AppConfig->setSerialKey(serialKeyString); - notifyActivation("serial:" + serialKeyString); emit serialKeyChanged(m_serialKey); if (serialKey.isTrial()) { diff --git a/src/gui/src/LicenseManager.h b/src/gui/src/LicenseManager.h index 4592b28e..2dc89cd8 100644 --- a/src/gui/src/LicenseManager.h +++ b/src/gui/src/LicenseManager.h @@ -38,8 +38,6 @@ public: void skipActivation(); void notifyUpdate(QString fromVersion, QString toVersion); static QString getEditionName(Edition edition, bool trial = false); - -private: void notifyActivation(QString identity); private: From ef9842c81916cc5b5a2ab40f35bfc397823905c4 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Fri, 28 Oct 2016 16:01:57 +0100 Subject: [PATCH 82/88] #5707 Change update notification url --- src/lib/synergy/ToolApp.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/synergy/ToolApp.cpp b/src/lib/synergy/ToolApp.cpp index 653012ff..3dcfd05d 100644 --- a/src/lib/synergy/ToolApp.cpp +++ b/src/lib/synergy/ToolApp.cpp @@ -146,17 +146,17 @@ ToolApp::notifyUpdate() std::vector parts = synergy::string::splitString(data, ':'); size_t count = parts.size(); - if (count == 3) { - std::stringstream ss; - ss << JSON_URL << "notify/upgrade"; - ss << "?from=" << parts[0]; - ss << "&to=" << parts[1]; - ss << "&serial=" << parts[2]; + if (count == 3) { + std::stringstream ss; + ss << JSON_URL << "notify/update"; + ss << "?from=" << parts[0]; + ss << "&to=" << parts[1]; + ss << "&serial=" << parts[2]; std::cout << ARCH->internet().get(ss.str()) << std::endl; } else { - throw XSynergy("Invalid upgrade data."); + throw XSynergy("Invalid update data."); } } From 3e9815dfddc8c83522c6caaaef6d88ec5f6dfe9f Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Fri, 28 Oct 2016 16:34:35 +0100 Subject: [PATCH 83/88] #5707 Add newline to update notification string --- src/gui/src/CoreInterface.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/src/CoreInterface.cpp b/src/gui/src/CoreInterface.cpp index b3c11f0a..e560fc45 100644 --- a/src/gui/src/CoreInterface.cpp +++ b/src/gui/src/CoreInterface.cpp @@ -67,6 +67,7 @@ QString CoreInterface::notifyUpdate (QString const& fromVersion, QString const& serialKey) { QStringList args("--notify-update"); QString input(fromVersion + ":" + toVersion + ":" + serialKey); + input.append("\n"); return run(args, input); } From 2f2dd7742f79bb60d943cb4ad6d9bfdf79873df1 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Fri, 28 Oct 2016 17:43:08 +0100 Subject: [PATCH 84/88] #5707 Don't send update notifications for new users --- src/gui/src/LicenseManager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gui/src/LicenseManager.cpp b/src/gui/src/LicenseManager.cpp index a6af21d8..221fd16d 100644 --- a/src/gui/src/LicenseManager.cpp +++ b/src/gui/src/LicenseManager.cpp @@ -72,6 +72,11 @@ LicenseManager::setSerialKey(QString serialKeyString, bool acceptExpired) void LicenseManager::notifyUpdate(QString fromVersion, QString toVersion) { + if ((fromVersion == "Unknown") + && (m_serialKey == SerialKey(kUnregistered))) { + return; + } + ActivationNotifier* notifier = new ActivationNotifier(); notifier->setUpdateInfo (fromVersion, toVersion, QString::fromStdString(m_serialKey.toString())); From c9bb421fb5deb056166e728c820ae547f5fa0c2f Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Fri, 28 Oct 2016 17:48:20 +0100 Subject: [PATCH 85/88] Version to v1.8.5-rc2 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d3bf196..e888229a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ set(VERSION_MAJOR 1) set(VERSION_MINOR 8) set(VERSION_REV 5) -set(VERSION_STAGE rc1) +set(VERSION_STAGE rc2) set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REV}") cmake_minimum_required(VERSION 2.6) From 771d2a419b27e28b3fa3e839eaa1da34ec4c0cb1 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Mon, 31 Oct 2016 12:38:16 +0000 Subject: [PATCH 86/88] Fix 1.8.4 changelog --- ChangeLog | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/ChangeLog b/ChangeLog index 19d10d50..a496d276 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,16 +1,15 @@ v1.8.4-stable ============= - -Bug #4041 UHD/4K DPI scaling broken on Windows servers -Bug #4420 When XRandR adds a screen, it is inaccessible -Bug #5603 Activation notification depends on existence of /etc/os-release -Bug #5624 Update notification sometimes requests a downgrade -Bug #5329 Current date is shown for build date in the about dialog -Bug #5640 Synergy branding is inconsistent across platforms -Enhancement #5617 Remove redundant plugin infrastructure -Enhancement #5627 Move SSL certificate generation to main window -Enhancement #5628 Move SSL implementation into core binary -Enhancement #5629 Move activation from wizard into new dialog window +Bug #5183 - Slowly moving the cursor has no effect on high DPI clients +Bug #4041 - UHD/4K DPI scaling broken on Windows servers +Bug #4420 - When XRandR adds a screen, it is inaccessible +Bug #5603 - Activation notification depends on existence of /etc/os-release +Bug #5624 - Update notification sometimes requests a downgrade +Bug #5329 - Current date is shown for build date in the about dialog +Enhancement #5617 - Remove redundant plugin infrastructure +Enhancement #5627 - Move SSL certificate generation to main window +Enhancement #5628 - Move SSL implementation into core binary +Enhancement #5629 - Move activation from wizard into new dialog window v1.8.3-stable ============= From 20a34e5abf6e77692de27024fdf2bca98b29fa0d Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Mon, 31 Oct 2016 12:45:20 +0000 Subject: [PATCH 87/88] Update changelog for 1.8.5-stable --- ChangeLog | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ChangeLog b/ChangeLog index a496d276..477426ca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +v1.8.5-stable +============= +Bug #5680 - Server crashes when disconnecting SSL clients +Bug #5626 - Build fails using Xcode 8 and macOS SDK 10.12 +Feature #5657 - Trial version support +Feature #5707 - User upgrade statistics + v1.8.4-stable ============= Bug #5183 - Slowly moving the cursor has no effect on high DPI clients From a18eba7520f301fac921c178aa0c75d7768ef176 Mon Sep 17 00:00:00 2001 From: "Jerry (Xinyu Hou)" Date: Mon, 31 Oct 2016 12:45:42 +0000 Subject: [PATCH 88/88] Version to 1.8.5-stable --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e888229a..45c8d9d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ set(VERSION_MAJOR 1) set(VERSION_MINOR 8) set(VERSION_REV 5) -set(VERSION_STAGE rc2) +set(VERSION_STAGE stable) set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REV}") cmake_minimum_required(VERSION 2.6)