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/87] #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/87] 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/87] #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/87] #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/87] #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/87] #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/87] #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/87] 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/87] 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/87] #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/87] 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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] #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/87] 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/87] #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 020b7974dfc7778fb70a4bb63a56f30a06dc4d4c Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Tue, 18 Oct 2016 15:32:59 +0100 Subject: [PATCH 55/87] #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 56/87] #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 57/87] #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 58/87] #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 59/87] #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 60/87] #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 61/87] #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 62/87] #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 63/87] #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 64/87] #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 65/87] 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 66/87] 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 67/87] 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 zH?5NIoBip&L_&|b*DxVFf@S$eVPE(C7V9q_(|+h#nxWBdIBB()R5@vxNtmXmDuiCM z`@?%R+1kY6vgSZ?Ocd^;d0#8^iU*sz>Kuu*bMrFIv)p7*FF;_cJoHEokhhiQL;Vy- z#CBh@Ol@*}+FD3+0!z_gm$zNnuH>RO>N{u^ku6iwxeCFC)W1${>cG`&S-E`?3c*Wx zchxFd#>jxg`j2WAiq(#@JRI+aeRwI4L$*je7CcJPVv8&<(=!q79(L@Y$=5<2TP+FP z#A$azOw0|pY|~J2pRr5|L-y!agV`iv_bp9Nia5NWINU#vG0C5Dxo8Yz<#~2yN2Rtt znfq0ccVKe9L+D*R!iiX~UhsuI)pf%VSDy2lLDQ~hiokUvmOk9_b(2iZMm`q4=LYQlgPe6@g z1Ke|1*fvF}gCA63aQWPb7;Z*#(VtyLet&&BH$d%)nLc(uS$)Nmm|>lCR$OMQ;r-V$eDFM`7v^4KTirTjb6_K{@rF? z17tI9+x^C$NWSqhRx_@tS;Ksnw&px9V<~qW8Z0K5pg7{RT?L{gq*M>F{UE`;&pDXI z1uDU-iTmnQ`X*#l5XEzJowgThUIb+kaz~QmOy@y_Y2^)sn@f0sw?R)nYOJ?mr+}9? z>zPcgiR?#*vj=z6B0yNr>HFbAKEi+YR0}II)CK+-&CMr=Tr((7OqNTz&xmuuWx&K3 zSTraVI`b$wjkcE(>flT5K*Av@UUqDIRWu0p+zsK{fNbhub0z)vREx24O*koB)62R@ zFY0=qL(W)#5bOxW=M)KY#{8yIx9_1tsES0(He75&*5~>xtO^0+ z5cF9A!VJlBhBlj0-3NTFoe=fK^)=?4kFnmE0_A)Z&137Dljxer`!~%ZZ4`;wkH3?| zt{70sd)0VgN54L!d#Ep^9jzfb+=OaTLOtdRG_tiXX7$>w%61v zUp`Ut3VNwL)jkL4-UKQNLCtFo8j?Q&Mi#?#SwY(GYj`e)^?w9 z_XJF3-oBhKRSnw)BaLlf(U6nvKBD@%SUUyT{g(@cl=z)|Rnq3H7I_?zy7%CFPI@N; zY=7cVUJeuWuEjLS5yoy6=4fyQ=!top8`;r~TeH3Rsc9N{GC zolf0&Pt`q``QrzWLXX<4R-_8sVtO(7N?%pT3cZD~w&9XweYHX>vY#BKaNp*wz?CSE zzhtGBlv6~wgZxgoDrt-W&g-5iml<)C_SW8+dBKLPaVNlyj>YuPk)wqF1F9nFxMZL5 z8Xx&fvm%qbAxq1u?&5TA#tBcux%PzzSZY!{lry6@(|{6eO93XWoX}c z&M%>4gP%nthdzDmOY>K3Bceab9+(Bnt7fs2 z7pShrytk|btKj5z5Lk7xpo!TXE1!BjO(!!St$SQHi4D#a?oGLupDA&wUCSn+s$YI; zS|_k7j3MfwSqwUG4w37H6=UB%+TKW0Pt3qU_LAHDz>;?wKFAfL#Wu3yWh zWYz0Iop|x;iN8KW`G{9S()Paa^at}VeQHlH(Ll)8O#jG%=^km};R)FbYjhoBnkuFotjd$Q!o&0HtMOtjZgd^;rSCQV zyuYxjZCS<_N4b_;3ccuT{nN~xGDPx3OlAmuQvN8}OB1^}L9J?Z-aZojwH1AHQ%FpQ zONG<<>HP(-tC=}p*B7X83J|{g99?w2&UruuoV#+Oc|S*XHh+sz`1k9>zu%~MyDPFS zmy#8``z$HZ&J#{p-1#Orm^B6H>iW2e9KbKKkUJ-@8=CM)Zr`#*PM}Jarlo4#T_mLD z1`U+w3ip`LjdytQW3P{fcsy9q?A>gBOl4Y)1SZbTZbYlZ8Ix(ECKr1*hYu|S4Rg9Z z+NvW0cPW#55nr7~bA!UyS7-RdzC%aBNJ5>WHtZItxscxthh>nc@{o;9r*ychj?f@p zVFVY_+8B4(gl4ibxxeCox17{TcDO6lEl4zFeu&o3@9H!y()mWo)yqkidZTL3Oq<0M zHbZyWl_{p`C;me3Nwc(UM~anMh4*&B_;lkz^NB=f(Lag3;mQS_2&G}SKSsdjXB8QZ zqx;10Erhg@jS3=dZ-a|6GlQd64Fe)mymHwF%MbjhXNF*O(oO-ZiPydIqaRw|dkIBU zrs$3xXRcS#&a8CaR*%Tc)2uq+Zd{b*p-OU*(*9L0Be6KIhwtW;vNn~n!iwVQ$Hu=k zXd~$8T4g46c!%qsAm%lA;Gr1dN7m)hSDgeS#p_zdpPB9WkD>r90`6Yp-qAj^n@6bd zxQfj0=eX@Qgf_(;#%c3niL*0A0v!x!ittVU10?bBnp%P%H;Tw2BlW(z z91$-~S|lo~Jph)ZQ1jvCEp&{d#pj6cVhlU!cGnod1stBock(Nq!L)AY{ClI)^wfpj zn5Jt&5rjFuBkN`6J2LNweygyAR*im$Axd)>OBajg;?mU!lcfWw^hHI_O%cRI@f#?L zgpeFV3tPI)`&HM>Y_hNU^y;E>U~!2?h9~3XVsYTDFlbeU*Zm(tGd8qHy7UxFx5ojT zswbDdA=Y0TZj5{6l#c^bUXa(^11|Nhr@*R|*<)52|MJ|`q`rIKF0A%vVj`n;NWzJK z|6ELkmR?-+s>9wewxo1CRSq-bVLF>obJBgxamZ~s-qOpS49<7DJdBnOrfqD0myX{8 z!OA3V!%Sfj=m$qB2Dv;mtycUTgk#PJ*+Ow5GpKO<=H*ywa`=ikr?FnN4eqcOIVJp! zqr%ZX0p$2S^6=+8+JLAAx$d2*Ji(0?k&xATnS{#j;BaMvx*}6k!CC1MoqAiMN}u9# zBqRrQH)CaRru~H#(i<#Q;AXRyPt-6`{0B^O^dq8-GmHzT;ug_8E6}shHZnS?-ZnJ& z1kur$HAur+M98yxs1|0J_9$@Tg;}wE-%+kR26HQBg zphEHntF9roA)CO`H|DIMf92}`seCG8$C1&y^gmA3{|VL~eZJ#a{1UFvL;U}D34=bw zAmD#o!a9y6A3qzA0{_P)y!M| z5e^=r(#0B^5V}!IJxLTT4sFWHNE=+{K* zF-8d}$N>v5=*u#axH88eXP~YhLCJZbnN~DBM(h;)!~wwELo1TNJb|Z@@jBrTo+6`A zYCuSU30Qu?bI!(P8VP6FJ8J&P_#h@I6BrT27{_>-nb zQ=*5dDHZz-EFgt)@pGt0V=|!8FQQIYI!y3)X*<@yuQK2}%evyc{_FrUumKqGfbk?1 zkw~5C&NCsLuf=24kFk<{3@%{X(2(w z3f?b7u(ZiO2rx=hmLkrH#ZM%Cshr;Pu-?eqZ8Rg~PAi2# z`?fS(k7PoRkqTVr_Fv5l4Beiu30!Iq0i3m6eTA8tm=~db*R) zE1++o^sLda7hWqiHs(b7Hx;2tzn|GB=;q-kOhib+y(&Kgm#3piRS?=>gDpS*B?%)Pomm>NeOOv?ccrVIlX+lUUpAe;*s< z*NSFYrmob2oB%B+)ZV$C&P~?|i&`N9m_&&P`&8?YpLo*~K#Iv6!bOtP_{z7m&CY`a&cu;2f7Zq~D*hZ~ORm zXHyh=EAI__oCX>7I9q(9H8uPCVS8SOsgUvTxj@_F{O@p{+_PK?)a>cMu)i)|8hA~* zUk}b_w_fbe77Y*!c>Lr86FV9O{!Nxi(QGa_KrWdNvMpr*xRsQp8`N;!CP<5h@K&W= zvkXW#t7ShL<8i$yh6lv9M^FLC5XBjGh8>ZhO9NeDhrkn|E%9L#G}{>xX6Y!RDR;~i z9CkEx8bJWuGPY&}Q!`B2Fd}Z?bsWUQ6S5j~B2pTY63F zNrDr21Z}n#eKZEA8!d73iqc@TiQeh8!qmyeZkP05f#<7t=_e8EohUcH8&q(XpCpl< z3F+pi=Fw25>7R6>6Xx68o~EV=&hpa7)O-<)ikg6uPjjxzQ^l2FEt)Fni;`=Wf^p3X z1kfu+EdkGmRDjTaH!zYFJxz0jAoMg=M^?E^^d5kZN-FjCj`VB)4f4yzKiaJ_Od&*1 z6VomiTXFG%E_4S5dN~02?vl`7FiHQ8>7N03lkr8TLfQAOIl{QH@JaJfT*z|yPPUnw zt&{SFYv$zv5k4g^9rNP*Wy(Icm5cKNq+`7{6nMR*!u*NsnG7KtXQ`=|y_3bV;SbsJ z{kY%;&D~NiU-`ZkJMV+obcY`KFJ@LK$E0XxgaWP)uX_0S=&u=+Mu+wZxIw&*JJ!-OH87ZkkAGQL59Pm$*wbKnKH!}{ z)D5xOTvs2!4x*?%tm|}lmjJxgzY&%nBl*^4rKVz-I^~uiPkH7a!uv8l(9Nv5HeSCr~Fntyk|ON zv7RP=Hk*BEf(km=xA+Jlj8PMg(H%NwtK?uGqY0rTegl`4S$l`bHmQE%Mq+kW6b*v- zAT==-HMq}lKYkZN{^!p%K$klMV2;Tg@T*f+mQL#&UDwrkKoMdvg$g58{`jW#po069 z`c5>Oyr z?5iaIdB_nU**fHT4s2yw8UygNmxaO5)xjlm?u77VVc22H8D zCc#ae8w{nuu6ikGYqX?=nh7E25|PTCx9+t6(%2;PT!iiDQ=FDK&_S*t=pg^8V`89j zxkDl7d~aA?5n+kc0f+eF9l)KYt*yW#;u=;Df6M^p7&@~3#{K8-2#GTn4sF_r#`NG? za63ZS9w-B0vt*=|97+B>Hj!2Nzw9f)*ricnu3Wf@zJH$&(RkI(p9{MWyhRS@D+|Y& zAEST09PXdO>OQ&Ew7>}2!9S8ifI|0mya%HCvVUELo-m#T>|zU9S%uC^EhPKf7oIT2 zM);;{As@tT%1z&g+{o?p(GCGA6|kn;rx zekk@ve*=-@*+04A=UIgo1j?#atyeDX7{3mBROLEYGtumEtO5+GQ5)n zI!KIlKu3&#K41e^gIEB=hjy$WSFU9p^>gip4mH=9+1?{*>2y_Z3OqY<89UitAps%m zF&#Q6pOWyP@oJvIH%dO0jf~4of=@Wz)8ct`{ zIRS=BK;2ojuuZgi#cIz1@%;1O|1`HQu!$E(n+Ne}E34sH}t#cZGtBAGw>pPnT8`#=p zKQ7 ze?Z8J-gHP+m`B3Sb$0x{aVIFi0>iT}L{eNFvqdopL&Eudb!fTWIqJXHiyj-3hQgO; zrIj)}DvY+axwEddP|Pot;Jh{f?A!D6%4NBmfCIC;TB;H@jtQ@U*bY@!wDP0W)KrM= z)(}6swbaMhw%)?%RCk%#&$U08v!aIuACK`k9F|VdC6E50%Gelg9ce%&vtD2UqfNmV zFz9A?V}IKI{54K#vVXAb{4837u(%7NA$OgT+m`inP`%9f$891J>ii$4S2NIh z#E>ch9GU+p$g@*$9YNfY>wktZzEChEe% zsc20MpkI`H(r^u?x?`L>Lex6nSPLiVH+p3JMSK{q(d^*@R>Da>l~VtWDOvb8Vza2) zjhC}xY7Wl@vC&a(aJZ5yARbaUsgpdgNtOFGDiv;#io?MxmV%CP$UR(Kpq^R|rUoFD z9v|EJ-E6bc)J>US3>$ERLwf7mNQC!dLK|(!W%gDeJX@_sYj`Nz>f+eHhAGudFCdXjXURTn?Rtr60KkM^ZUR6Hu8OBVVXW_VwAZF5jDVh?I)Db zio)rJj!w-FxtRhfiMTZGNeEXQY_DFKP^f_M&2-S8)2zffK`cHLNf6%qMH(U~U?9cZ zcV`>1Ax#n}eY|2UsH(9qXTC86x)L*~;EX~dgTK}8#9#r)O%lMONUa?^x`N|&IHV>@ za9>QO*yY;Uz-SXbT0U@+lu4!F7c+0O-Jiqli{b&oJEuKH-PZQ+)W4oCLxDY>t>h;y zhB#rSeZcXnhJ6S*Ve~cVVdj8(r6)ar?kSoj0W@B9PnM%#$Ro+ z-T|6!*a;yD@3Rt!|JqIqGXZWq^iM1?#A9Ak7l}oo4oCl@8C{?U7(fVzd@h(4LCj~! zdno=|KtR<7tTpO|y9W5JcsSNs!4-|_!3r@<59XS1i5DWl`@;;2HaKOpfHrvXpO6DI zA4j@{$99q@hz*}weuTMOAtp~iFbYtMRp>(5ZLj8F1LJDh(1_jl<`-kzyd&qs6S)C| zLj)Pn2I(iHZ$_le@>W&~W*P`mAbTD{2vOeOi^-IQqvp{OC0`MxMw@W4iLhACNPGT* zc>E7^I=#{x3{Gfip*OYFS2cR-`Z!MiaN_tt793cbRm5cWkwCqad7AAt`kv8jLi}fGz+M~(-BXP8nnp)zF!2SD zBWHWqcS^XT`M_<1C6*9cfP=Oy;)vIj=%%G(0s>O-rtTaG3&eNOm&g(>>aKUUtKlB%PYAv7 zmMJLmv%5b%>jFwdRIErZ)!i%7ef-4_Bmai+*`(mpxNKGgQ8uNQcz=n0^WW&Sdu0YH zBbg~c&NptJycXI+BFIX6hU&c=Bhzk?f*&1YFnbW7Y}kdEL-k58Le*IXcm8!VHQJt{19s1q>`|`fL@8@@8 zpR@N}YwdOSn&`5BvO?kc3!rCyBMw4jlB+6O6pE@Uxa2D-E_k)A0zQp$Xf(JYh?M=R zQUWHOJEh}4MqX}2$Q1b8pMZ^Q-ydbZ9JV}ChogwT6VaL(n_rqG@L}uf)2=C#S!Ur8!@OM0|0qHm))U8rDwS5mR7JEWWlLtM2dX zLx%bt)M4MY>h`ulFj>U|A!0b>@w(g1Y;C~9v48IGq#qBPM`W-lH}J#{s3}Hb=;e2o zLWWZ{!xoUkI7n5+M$g5^FPbs=S#KLvcxZ=CMS$@MGHc}HWBagD62*?USIR~^XKc=9 zJhIU??wus0y^cd6jXr^|(s#ypMuA#?=o6?B1 zHb5PzLYW`uoR1;Wu=}`&@}xn?EJ%0Fej1*1w~!?L`w5^N;loEHepUs;tSrS$kWeFP z*TqqL?Ej*U6lph{BmoGX@TZR^V58TEADQf&y@KsSRl2xjqft;!EI!)Ews71!`-wt+ z^@%-i&UBA?XA2-898(0~_X6o+sG9puYMV&dOuLA z8F+Rdxrc*PWV)Tni%%T4bJV*#Gr7Kg4fKFX$8^stLvru@kTG3AI4h@I{}l$R@yn*l zmr6pa-4GZw+lydq^7>PimUgA%Pr~(*>WHvWG%%KDqz#B_yJQ&>vwJ1h1-a133yAdZg>{o3xo$!|jxtHq0H3U$%!Iz}pVcOn?(a28lMl z`C(}mh_iT3!!ZW1b+EP;S~VGm^EVJyDOjD~=f?@@EoL3ET$EjQ$9N4JFXlcb_S;h<6j+&tkA zc-Ls6z7SHcpCXUOSb>OB{-->YCC2iOF>@8Z1_ExDN`2OVwZfN&(Xv4%!*w^;pyXkc zC(#rNRg}DJ3Zo30JsQ4PcrD8FG_eix>j-w4a+U@(m*h*Pm$?HGAAUby=sR&|d9bKh7e=+xNt!yX(r6neaG^A(rvKP6F}K-f^Y5l^a5zW?Jf) zpJadSPYcL8L-|(XppTqw;Z$Bk@YhL>X^lT0?rX&CfRvkrXu}kX)xZC6W%I*ZPj}u2 z?nFO@4>UUS$h_Gg&Op$~aEPb7($lu48uSqZf@5cQzd(L`*|ARMbuWG=3<%v&%d`3< zDwd+h-Fugsid@2c2wj)x72>`cZaDvdmyc`<)K~tOvl!NkjUyxwk*J3bcsAfZH2_3N zf_}D>DwulvW#~dtmbvq?G736p=>Zgun(ZuG#kf_x%u; zV&-5GvkOq_YxBcbYZFarP#mwj$8abmkT*O|Bq2qBM;3M4Q_sgC7!r7H7)nL=rP;s_j|NW-O{^!t_2FW52;!7ta!1Y`ZI~u-H9|yxQvJg zn)U_r9sSdhA}LMEYa!yrAH~UZw`k|xG;=ii_rV0FGw0^|L2fh36zpMC8gpx`s~hD9cDcKJx$ve>;-mYF6j#={P` zodtl96@3ZYX@I!@N!HaLdRuAM(X zh*}dwTVfC1cNIc~J;n}qUDA#q@1*&{M>{eeqxpdjN<~lLkM}X{uSb-B`^q9jkGSm; z#;7g-T(>v-77a5P(;0A?xe$fZA*W(AWhG@ly)2asy2CXKJDy)sMSq~`Lj5{}PEa$S zIBm|p<9tv9hQXE**GKoZv$gCoT3{I~X9;;eyp2fT-r}TFq|iyM3Qrwb^2W(iIu|GY zjCUi7mf%A&uO>%x+zT7ENur$dhiQM+pmXR;twQ->*PXDEdWfwm(1+;^h43h%q_s zq}W?%qKaXDeCFm#{5Cqn{J7|7$|deE7m;mYO<6K|xlHSuz}I=bA{mKQz^Uk(7b7x_ zV#*Y}2H)ZLXWien#;gKaMc2FFE435d5zS-;;VoG0KW`?I|jCKoGgdcdwx4Mc?JJ6L;g@6ccdz{ zw}j|M>s41au_vXE8h`Ge>xp+`)GKb!8||$ode}NG_@SJMi5_@A#uLfUhl;R_ds=JN zsxdM6RT1hjUxCT&+^BHNY}pIx-qzFRe$Bmj3hRL%=&8vmFC1hpo||ny$Uy4i{h~gS z=3NN;C{Pmgm&U24EEVB7h9jhDzPKi)k}#Dn>XG$Rjy`3~&oGh`|FV5DYpbakZB96n z=B-lTgpU>>5X8lueYX$Cl{$}HP1xyD#NkV-Yd78SRlEQzk^kL0T(kyPl_H{}Rs4Tu z9+oT!sxTiaY%UxaB`S|l%i{(k&4R2`|e zz%XfJ)!)^U8{F9#UQHqjC{jGVL?pS{eTBIIocnVxdIs#NpU$-6T@pVgDfkS;@f zE0<@RR7P9Uh=kl?r9?A4R`Agz$(yHLTY2l4&Z*yiQIZ}Y;ZXC(;}QYKn;eiK_by%Y zP1u~mCJN6&e{VL;O$j(?7F^!6xVQmaJ5Uui5H;nd1`5B118gJ!Zs|qo@N?l-_0A3w zy{Od0n&XhOmNx!ibHLX+^p_>=3Du7J0z=lw%iZ#yejwh9Z9px)=oSPn<7#f1k-Fjd zBCBV+eq}JNN49R9R26@23pqR_LJT##1Of6W2$GNbrT>1#Z;Q6LXLocH&S^pPz`gP> zSliNHGln9Jf-|07CmVb#D6Xc44HC-+VnQ`RlsXvKPXN3LC7YABZ`AIrSI^D+;*DP0 z_4X1`Su%#%;-cr}g;AOhP05x%q~0}{29d54#w-B!6-#Yl;FgWI3A-3g^r-3I8;=07 ztru$g6(P}SzGu+80s&csjr+h>&2Ke~!`%X}FCS0s{Yv96?bk^x+cM(dc3|FCdLq^( z3*i;)htB)j1B^9iApRu<8e+jgXyvW}W0Pe@Gi9+LnQNRrpa?URvv1aw3kVYg$o>xT z0V`T`w5)jFpU+r*Vf&L3)&?P32+i$NM~hgt9eoD0*sl6?fy%ID9Y%G+khft482Y3y zaO%AMT$TYkf+I$7kL#oDU>OUj*S*krlUFj&VV*(Zi?T>I@&?wdUxzYHPM zu}3)yi=+LD!v_B5=J-4-*7qsDX0Z%Bn%fNEKKSRy*-L_49*_tnJnX$%FnF`NExjQ# zN?sn*8*??+h8x-fG7Rghxzev!C1;rk$?IhU5_LkHigA#ewMBAq3F8H8fmBHN_r>Ck z+sff?KaVGx@-NK!CggrEDKZOh+rY>%of6w+ls6H2PqZ5WVM}HMfQUK=1as3V`1i{K!ypPc{w~jv*7HM$DB^ zGfbbMu9l_hajX&yturQ-9NAyJ3L=#xYt%Z?;W}MEF}O)Wm@P1O{0E*Y1VO)G96I!zd1MPhgUB5Ikc+z<55+4ud-);? zCHj#kBe_$25)b~^pIqU}chG=MudoO~irD+m8i8LgG=2SedWb79BO3RXfoGmZA!05Y zFx~HC0}zK7TNtKp#@k*l$nCK%TW4cQBtlT-p2q%GN`#(2ky`5BRu3J&7++Lu$7RVA zI4wa}niU>|A=xVOGww1+|65Ym^>w>#y@`M&hmbodn)cg=6sYIFD%{9@g_L-K&)$2z zF>K~k12JE%g6)suIIQJ1n_y;7P7IeP(3c?Gr=#A&w^iutytNOOaJm$+w9K{UcOV!g ze-mTzY-J1*SN5K0>!A0uOIlKG1jSHl)L+~@*nydCAGz{vU&@|k z>Bai7du$$*>21=Nl|xn-ekXhck=s!f09>7FiW7r>)0!V;3~;3U7y_;9%KljZ^BnYn zf{`rgE|=a$hJOS^k7**Tz?Z;J834$I|MLQ9!B38J=PB6k$|k@<2Rg}0w9$L^+eCUL z3fDKLuvr{<7-lROe!~pPG1CQpwqHUWi?VMuc(kFG^)hs23S*O0Wx=^RLU=y$N)bfg zHBiTppA4Amf~09R+?okXh2}yGj|Lh>TdnaZy`(@>&8RW_=Z<;blKnKPqQkRIVq`*$ zVB59zkCkVfB&MeQQFCpur;zH)!P{nnD5}${z4R+Rlh1C+0Cri0H}udd#CD?NoGqn! ztjlYR0Y+ENs1oxY5L+eZ?&1ZZvV)FVJ zRRRB22-tuUT+{$rS$dH!N2kEg8do9edM;3ZqmCA8qc+_3r2lT}Py3~5uuGg8DzTz& zS9Aqn^U0)nLzEk~(`kT5&)z5ZQO$mf6O6L18ub}E%Pm%QHtvrS7p3pIV}MyaSg|m4 zyFMxZ88lMZvn9JR4x~WOytPhoqdiAo)e*OI#=#zb;^O+bAI7px9hig>=o?hkuk=wT zT-vMS;)oE@K(FFXtAsIApK(lITiP{k1y}gd+pMzD{A3eLNTutv|Q%8?d;3TPp9pm1oL13QR$1B<`^q^Rw zZPXHiMhZ(N`cI61l*RJ@yth?j;Rf%9u*0~+%)pfGeB))P6M8Odi5rH%^&6btHSFM0HIJ#W!bZ)rz;B0V+ra4RCtcy*5 zIG@COG@28caV%(3xfWEl-nqYbNO5;U>0ap-@@a81uS4+_Vic@jXsd*M@uNKeDtY+> z7CMyj`!n$*--1##kKjSE4&x#V`x4n+z}8WUaEptJMoE5rmI%Sh_zU9gPP_<$vf`AD z!Zc01wH{1+0ZU`1QQv=`l0T={`G5ksp5a1}R-ch+OTY%b6mx}63%5Jv#8p9z8@%`4 z9u_uB&DjeHvB-UXjS;W zfbippzx&oqJ((=(afAIy215-O-m0hUh-CZijL7^gLtPd*?fIkkY0RI}%Cp!3&Q1xvp? z?c<1&g(4ZU4pMGOsXRdCS&JUvLk6-G+!##OlW{OW@6N_O1Uv9rm^C}fRlI3!V3xg# z`}c%cYw+%lcw=;o8U#SZ780}w>ok9YaIVYv<2@z}#LWm~a;yHDT6Hc^Kas9b zO2PBuM79C{HMrV2`q}t=o`JnHLP~Xf0HB>8JQL58kGjzGmIwi|M4h^ax*7~n@;>)o zbiBJ~T`{QUNdGN_d=Dk{U2^E|CcXOK`FDZVKsk;b-Mq^luo6bkgbyv(c@Xc5juSj4Prui z*Hka5m<||%&Hn58)q>+=EVTPoZ3I>Gwn%-*fE$t_QfcBz3b~r1tlnKo0)0BbYW;72 z*fDlHaK_1#_b9j=DyadvF4@|PAi_R(*%Of3tP=IF z0zb4SX~}db8Bi~_%nW&D6J)KI2P$37(>fWhBp0LwYH5&Dz5zpZPR<2Qm7jR|i2`Ra zR!_^QQoPrKk8!On0I>ed={jBD0h^6YMy%G=-NLO8rx>>XmYZ*uywZi2bvz(UgH$BG z(APH@A%7`h9R^BCpCoYj`yPXJ3dc>t%w&D`-595~Lc%!zUY*h{H$npTQVmAn<3Bem zEHqBO{=~=!Ar}Q+g008cII=Zul zw}AiHq)ku^H(0?7&bo!hS^bERO)!{oabHX)pU==lF3TWY4j+L0ba(m!OHW`hI%(rw zBlhOQa+p<*M|w7Dhi?h38W} zId0VO+NzQ6+Aj|C2B24GA`81qm+15ma8}_r&g=eW-}&ixe2KdTsgDxKawB> zZeOY@n3_ZX`PS6m3<3Is&%Pa9e3JI>F+2_lNjP3VK}5~eF7?U8zKHE(Y$9Soz-(rC2;%&cbsfi5`YXp}nJb zklW1eo!tx1=>CbyB{Elyytg!w^V4d;usbAo;CPvMglhAOq3QsmR+a$D>-3VwEvK1S z=&lfFO5$!_kj)E^F+VWe>a%7kD@i}W>r1Oh?oT8JtUN{+DuN1enyR=*F3i&EC3zD) z`>&^A6JNez4R5ucngcq8Fv}+8W*G^}l!b);8$8w7{<12u*`<5s!$?Acqx8z6tWVj! znlfN--R;}IEL3#tUc7}>{a*ok$?-{>*93C+D^Sa*oLdy!Y9Oa;ZpC2LMd7Z!I(t8b z)kN*jCU0S8^0pIBRZej#Rq6$hazE z%!X4xGK~%LTMyAShhTYoW#sW=LLezI_z|h9*o`cy?M&_5w40ON)-(5`2M7P{HAMmP z+sx92418K<8gmT^N6Ytd@PC<`H!owWJ|9?y=xC`-sNOGuP7B#@J2TvB7CF)6%2iey zFWyp}tjg)qwLSm}$jD*$!*LE7g*QQ?`E?=&o}oyCpv@9 zT18oBYoa|&fA&vb?7q-9MzRD6fq#U#W^cfE>U@UaA^FA|?5o^n23txLiKs zut&bV`^~?$cmsd$ErbytO%n6YlAzm@s6gJ&=tZ(9ANOVrs~xZj6i^V;1`|R|2R zVBMV@-1ccP(5Koo2zY({-G(Y_Np;{MNs2Wfs0^%<;fh;5ETldNP5!v~H-SxkB+6CZwfNv(2{^J)L{R6pxDIN0sR<<%+=!!0qm2$4pI`l&Z zww7xBlC6^!%kllu0!Gvy-y` 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 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 From 1842a68a0ed58e207ef3ddeed0e5ce0e2ff1e157 Mon Sep 17 00:00:00 2001 From: Andrew Nelless Date: Thu, 20 Oct 2016 14:55:21 +0100 Subject: [PATCH 68/87] #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 69/87] #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 70/87] #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 71/87] #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 72/87] 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 73/87] #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 74/87] 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 75/87] #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 76/87] #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 77/87] #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 78/87] 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 79/87] #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 80/87] #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 81/87] #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 82/87] #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 83/87] #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 84/87] 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 85/87] 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 86/87] 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 87/87] 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)