Your trial has " + "expired. Buy now!" + "
") + .arg (m_appConfig->serialKey())); +} + +ActivationDialog::~ActivationDialog() +{ + delete ui; +} + +void ActivationDialog::reject() +{ + if (m_LicenseManager->activeEdition() == kUnregistered) { + CancelActivationDialog cancelActivationDialog(this); + if (QDialog::Accepted == cancelActivationDialog.exec()) { + m_LicenseManager->skipActivation(); + m_appConfig->activationHasRun(true); + m_appConfig->saveSettings(); + } else { + return; + } + } + QDialog::reject(); +} + +void ActivationDialog::accept() +{ + QMessageBox message; + m_appConfig->activationHasRun(true); + m_appConfig->saveSettings(); + + std::pairYour 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); +} + +void MainWindow::appendLogDebug(const QString& text) { + if (appConfig().logLevel() >= 4) { + appendLogRaw(getTimeStamp() + " DEBUG: " + text); + } +} + +void MainWindow::appendLogError(const QString& 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); + updateFromLogLine(line); + } + } +} + +void MainWindow::updateFromLogLine(const QString &line) +{ + // TODO: this code makes Andrew cry + checkConnected(line); + checkFingerprint(line); + checkLicense(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); + + 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(); + } + } +} + +void MainWindow::checkLicense(const QString &line) +{ + if (line.contains("trial has expired")) { + licenseManager().refresh(); + raiseActivationDialog(); + } +} + +void MainWindow::checkFingerprint(const QString& line) +{ + QRegExp fingerprintRegex(".*server fingerprint: ([A-F0-9:]+)"); + if (!fingerprintRegex.exactMatch(line)) { + return; + } + + QString fingerprint = fingerprintRegex.cap(1); + if (Fingerprint::trustedServers().isTrusted(fingerprint)) { + return; + } + + static bool messageBoxAlreadyShown = false; + + 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); + + if (fingerprintReply == QMessageBox::Yes) { + // restart core process after trusting fingerprint. + Fingerprint::trustedServers().trust(fingerprint); + startSynergy(); + } + + messageBoxAlreadyShown = false; + } +} + +bool MainWindow::autoHide() +{ + if ((appConfig().processMode() == Desktop) && + appConfig().getAutoHide()) { + hide(); + return true; + } + + return false; +} + +QString MainWindow::getTimeStamp() +{ + QDateTime current = QDateTime::currentDateTime(); + return '[' + current.toString(Qt::ISODate) + ']'; +} + +void MainWindow::restartSynergy() +{ + stopSynergy(); + startSynergy(); +} + +void MainWindow::proofreadInfo() +{ + setEdition(m_AppConfig->edition()); // Why is this here? + + int oldState = m_SynergyState; + m_SynergyState = synergyDisconnected; + setSynergyState((qSynergyState)oldState); +} + +void MainWindow::showEvent(QShowEvent* event) +{ + QMainWindow::showEvent(event); + emit windowShown(); +} + +void MainWindow::clearLog() +{ + m_pLogOutput->clear(); +} + +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; + + appendLogDebug("starting process"); + m_ExpectedRunningState = kStarted; + setSynergyState(synergyConnecting); + + QString app; + QStringList args; + + args << "-f" << "--no-tray" << "--debug" << appConfig().logLevelText(); + + + args << "--name" << getScreenName(); + + 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"; + } +#endif + } + +#ifndef Q_OS_LINUX + + if (m_ServerConfig.enableDragAndDrop()) { + args << "--enable-drag-drop"; + } + +#endif + + 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(); +#endif + + 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())); + } + + // put a space between last log output and new instance. + if (!m_pLogOutput->toPlainText().isEmpty()) + appendLogRaw(""); + + appendLogInfo("starting " + QString(synergyType() == synergyServer ? "server" : "client")); + + qDebug() << args; + + // 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()); + + 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 day%3 of " + "your %2 trial remain%5. " + "Buy now!" + "
"); + expiringNotice = expiringNotice + .arg (daysLeft) + .arg (LicenseManager::getEditionName + (m_LicenseManager->activeEdition())) + .arg ((daysLeft == 1) ? "" : "s") + .arg (QString::fromStdString + (m_LicenseManager->serialKey().toString())) + .arg ((daysLeft == 1) ? "s" : ""); + this->m_trialLabel->setText(expiringNotice); + this->m_trialWidget->show(); + //} + setWindowTitle (m_LicenseManager->activeEditionName()); +} + +void MainWindow::endTrial(bool 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(); + stopSynergy(); + m_AppConfig->activationHasRun(false); + } else { + this->m_trialWidget->hide(); + } + setWindowTitle (m_LicenseManager->activeEditionName()); +} + +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); + } +} + +LicenseManager& +MainWindow::licenseManager() const +{ + return *m_LicenseManager; +} + +void MainWindow::on_m_pGroupClient_toggled(bool on) +{ + m_pGroupServer->setChecked(!on); + if (on) { + updateZeroconfService(); + } +} + +void MainWindow::on_m_pGroupServer_toggled(bool on) +{ + 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); + + if (!fileName.isEmpty()) + { + m_pLineEditConfigFile->setText(fileName); + return true; + } + + return false; +} + +bool MainWindow::on_m_pActionSave_triggered() +{ + 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; + } + + return false; +} + +void MainWindow::on_m_pActionAbout_triggered() +{ + AboutDialog dlg(this, appPath(appConfig().synergycName())); + dlg.exec(); +} + +void MainWindow::on_m_pActionSettings_triggered() +{ + ProcessMode lastProcessMode = appConfig().processMode(); + + SettingsDialog dlg(this, appConfig()); + dlg.exec(); + + if (lastProcessMode != appConfig().processMode()) + { + onModeChanged(true, true); + } +} + +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) { + 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(); + } + } +} + +void MainWindow::showConfigureServer(const QString& message) +{ + ServerConfigDialog dlg(this, serverConfig(), appConfig().screenName()); + dlg.message(message); + dlg.exec(); +} + +void MainWindow::on_m_pButtonConfigureServer_clicked() +{ + showConfigureServer(); +} + +void MainWindow::on_m_pActivate_triggered() +{ + raiseActivationDialog(); +} + +void MainWindow::on_m_pButtonApply_clicked() +{ + 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; + } + + auto array = name.toLocal8Bit(); + SC_HANDLE hService = OpenService(hSCManager, array.data(), SERVICE_QUERY_STATUS); + + 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; + } + } +#else +bool MainWindow::isServiceRunning() +{ +#endif + return false; +} + +bool MainWindow::isBonjourRunning() +{ + bool result = false; + +#if defined(Q_OS_WIN) + result = isServiceRunning("Bonjour Service"); +#else + result = true; +#endif + + 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; + } + + if (m_pDataDownloader == NULL) { + m_pDataDownloader = new DataDownloader(this); + connect(m_pDataDownloader, SIGNAL(isComplete()), SLOT(installBonjour())); + } + + 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); + } + + m_DownloadMessageBox->exec(); + + if (m_DownloadMessageBox->clickedButton() == m_pCancelButton) { + m_pDataDownloader->cancel(); + } +#endif +} + +void MainWindow::installBonjour() +{ +#if defined(Q_OS_WIN) +#if QT_VERSION >= 0x050000 + QString tempLocation = QStandardPaths::writableLocation(QStandardPaths::TempLocation); +#else + QString tempLocation = QDesktopServices::storageLocation( + QDesktopServices::TempLocation); +#endif + 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; + } + + 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); + } + + 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(); + + QMetaObject::invokeMethod(m_BonjourInstall, "run", Qt::QueuedConnection); + + 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 (r == QMessageBox::Yes) { + m_AppConfig->setAutoConfig(true); + downloadBonjour(); + } + else { + m_AppConfig->setAutoConfig(false); + m_pCheckBoxAutoConfig->setChecked(false); + } + } + + m_AppConfig->setAutoConfigPrompted(true); +} + +void MainWindow::on_m_pComboServerList_currentIndexChanged(QString ) +{ + 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 (r == QMessageBox::Yes) { + downloadBonjour(); + } + } + + m_pCheckBoxAutoConfig->setChecked(false); + return; + } + + m_pLineEditHostname->setDisabled(checked); + appConfig().setAutoConfig(checked); + updateZeroconfService(); + + if (!checked) { + m_pComboServerList->clear(); + m_pComboServerList->hide(); + } +} + +void MainWindow::bonjourInstallFinished() +{ + appendLogInfo("Bonjour install finished"); + + m_pCheckBoxAutoConfig->setChecked(true); +} + +int MainWindow::raiseActivationDialog() +{ + if (m_ActivationDialogRunning) { + return QDialog::Rejected; + } + 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) { + autoAddScreen(name); + } + + 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() +{ + CoreInterface coreInterface; + QString dir = coreInterface.getProfileDir(); + + // HACK: strip our app name since we're returning the root dir. +#if defined(Q_OS_WIN) + dir.replace("\\Synergy", ""); +#else + dir.replace("/.synergy", ""); +#endif + + return QString("\"%1\"").arg(dir); +} diff --git a/src/gui/src/MainWindow.h b/src/gui/src/MainWindow.h new file mode 100644 index 00000000..75920caa --- /dev/null +++ b/src/gui/src/MainWindow.h @@ -0,0 +1,241 @@ +/* + * 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 + * 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