1 /*
2 * synergy -- mouse and keyboard sharing utility
3 * Copyright (C) 2012-2016 Symless Ltd.
4 * Copyright (C) 2008 Volker Lanz (vl@fidra.de)
5 *
6 * This package is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * found in the file LICENSE that should have accompanied this file.
9 *
10 * This package is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #define DOWNLOAD_URL "http://symless.com/?source=gui"
20 #define HELP_URL "http://symless.com/help?source=gui"
21
22 #include <array>
23
24 #include "MainWindow.h"
25
26 #include "Fingerprint.h"
27 #include "AboutDialog.h"
28 #include "ServerConfigDialog.h"
29 #include "SettingsDialog.h"
30 #include "ActivationDialog.h"
31 #include "DataDownloader.h"
32 #include "CommandProcess.h"
33 #include "LicenseManager.h"
34 #include <shared/EditionType.h>
35 #include "QUtility.h"
36 #include "ProcessorArch.h"
37 #include "SslCertificate.h"
38 #include "Zeroconf.h"
39 #include <QPushButton>
40
41 #if defined(Q_OS_MAC)
42 #include "OSXHelpers.h"
43 #endif
44
45 #include <QtCore>
46 #include <QtGui>
47 #include <QtNetwork>
48 #include <QNetworkAccessManager>
49 #include <QMenu>
50 #include <QMenuBar>
51 #include <QMessageBox>
52 #include <QFileDialog>
53 #include <QDesktopServices>
54 #include <QDesktopWidget>
55
56 #if defined(Q_OS_MAC)
57 #include <ApplicationServices/ApplicationServices.h>
58 #endif
59
60 static const char* tlsCheckString = "network encryption protocol: ";
61
62 static const int debugLogLevel = 1;
63
64 static const char* synergyLightIconFiles[] =
65 {
66 ":/res/icons/64x64/synergy-light-disconnected.png",
67 ":/res/icons/64x64/synergy-light-disconnected.png",
68 ":/res/icons/64x64/synergy-light-connected.png",
69 ":/res/icons/64x64/synergy-light-transfering.png",
70 ":/res/icons/64x64/synergy-light-disconnected.png"
71 };
72
73 static const char* synergyDarkIconFiles[] =
74 {
75 ":/res/icons/64x64/synergy-dark-disconnected.png",
76 ":/res/icons/64x64/synergy-dark-disconnected.png",
77 ":/res/icons/64x64/synergy-dark-connected.png",
78 ":/res/icons/64x64/synergy-dark-transfering.png",
79 ":/res/icons/64x64/synergy-dark-disconnected.png" //synergyPendingRetry
80 };
81
82 static const char* synergyDefaultIconFiles[] =
83 {
84 ":/res/icons/16x16/synergy-disconnected.png", //synergyDisconnected
85 ":/res/icons/16x16/synergy-disconnected.png", //synergyConnecting
86 ":/res/icons/16x16/synergy-connected.png", //synergyConnected
87 ":/res/icons/16x16/synergy-transfering.png", //synergyListening
88 ":/res/icons/16x16/synergy-disconnected.png" //synergyPendingRetry
89 };
90
91 #ifdef SYNERGY_ENTERPRISE
MainWindow(AppConfig & appConfig)92 MainWindow::MainWindow (AppConfig& appConfig)
93 #else
94 MainWindow::MainWindow (AppConfig& appConfig,
95 LicenseManager& licenseManager)
96 #endif
97 :
98 #ifndef SYNERGY_ENTERPRISE
99 m_LicenseManager(&licenseManager),
100 m_ActivationDialogRunning(false),
101 #endif
102 m_pZeroconf(nullptr),
103 m_AppConfig(&appConfig),
104 m_pSynergy(NULL),
105 m_SynergyState(synergyDisconnected),
106 m_ServerConfig(5, 3, m_AppConfig, this),
107 m_AlreadyHidden(false),
108 m_pMenuBar(NULL),
109 m_pMenuFile(NULL),
110 m_pMenuEdit(NULL),
111 m_pMenuWindow(NULL),
112 m_pMenuHelp(NULL),
113 m_pCancelButton(NULL),
114 m_ExpectedRunningState(kStopped),
115 m_SecureSocket(false),
116 m_serverConnection(*this),
117 m_clientConnection(*this)
118 {
119 #if !defined(SYNERGY_ENTERPRISE) && defined(SYNERGY_AUTOCONFIG)
120 m_pZeroconf = new Zeroconf(this);
121 #endif
122
123 setupUi(this);
124 updateAutoConfigWidgets();
125
126 createMenuBar();
127 loadSettings();
128 initConnections();
129
130 m_pWidgetUpdate->hide();
131 m_VersionChecker.setApp(appPath(appConfig.synergycName()));
132
133 updateScreenName();
134 connect(m_AppConfig, SIGNAL(screenNameChanged()), this, SLOT(updateScreenName()));
135 m_pLabelIpAddresses->setText(tr("This computer's IP addresses: %1").arg(getIPAddresses()));
136
137 #if defined(Q_OS_WIN)
138 // ipc must always be enabled, so that we can disable command when switching to desktop mode.
139 connect(&m_IpcClient, SIGNAL(readLogLine(const QString&)), this, SLOT(appendLogRaw(const QString&)));
140 connect(&m_IpcClient, SIGNAL(errorMessage(const QString&)), this, SLOT(appendLogError(const QString&)));
141 connect(&m_IpcClient, SIGNAL(infoMessage(const QString&)), this, SLOT(appendLogInfo(const QString&)));
142 m_IpcClient.connectToHost();
143 #endif
144
145 // change default size based on os
146 #if defined(Q_OS_MAC)
147 resize(720, 550);
148 setMinimumSize(size());
149 #elif defined(Q_OS_LINUX)
150 resize(700, 530);
151 setMinimumSize(size());
152 #endif
153
154 m_trialLabel->hide();
155
156 // hide padlock icon
157 secureSocket(false);
158
159
160
161 connect (this, SIGNAL(windowShown()),
162 this, SLOT(on_windowShown()), Qt::QueuedConnection);
163 #ifndef SYNERGY_ENTERPRISE
164 connect (m_LicenseManager, SIGNAL(editionChanged(Edition)),
165 this, SLOT(setEdition(Edition)), Qt::QueuedConnection);
166
167 connect (m_LicenseManager, SIGNAL(showLicenseNotice(QString)),
168 this, SLOT(showLicenseNotice(QString)), Qt::QueuedConnection);
169
170 connect (m_LicenseManager, SIGNAL(InvalidLicense()),
171 this, SLOT(InvalidLicense()), Qt::QueuedConnection);
172 #endif
173
174 connect (m_AppConfig, SIGNAL(sslToggled()),
175 this, SLOT(updateLocalFingerprint()), Qt::QueuedConnection);
176
177 connect (m_AppConfig, SIGNAL(zeroConfToggled()),
178 this, SLOT(zeroConfToggled()), Qt::QueuedConnection);
179
180 #ifdef SYNERGY_ENTERPRISE
181 setWindowTitle ("Synergy 1 Enterprise");
182 #else
183 setWindowTitle (m_LicenseManager->activeEditionName());
184 m_LicenseManager->refresh();
185 #endif
186
187 QString lastVersion = m_AppConfig->lastVersion();
188 QString currentVersion = m_VersionChecker.getVersion();
189 if (lastVersion != currentVersion) {
190 m_AppConfig->setLastVersion (currentVersion);
191 #ifndef SYNERGY_ENTERPRISE
192 m_LicenseManager->notifyUpdate (lastVersion, currentVersion);
193 #endif
194 }
195
196 #ifdef SYNERGY_ENTERPRISE
197 m_pActivate->setVisible(false);
198 #endif
199
200 #if !defined(SYNERGY_ENTERPRISE) && defined(SYNERGY_AUTOCONFIG)
201 updateZeroconfService();
202
203 addZeroconfServer(m_AppConfig->autoConfigServer());
204
205 updateAutoConfigWidgets();
206 #endif
207 }
208
~MainWindow()209 MainWindow::~MainWindow()
210 {
211 if (appConfig().processMode() == Desktop) {
212 m_ExpectedRunningState = kStopped;
213 stopDesktop();
214 }
215
216 #if !defined(SYNERGY_ENTERPRISE) && defined(SYNERGY_AUTOCONFIG)
217 delete m_pZeroconf;
218 #endif
219 }
220
open()221 void MainWindow::open()
222 {
223 std::array<QAction *, 7> trayMenu = {
224 m_pActionStartSynergy,
225 m_pActionStopSynergy,
226 nullptr,
227 m_pActionMinimize,
228 m_pActionRestore,
229 nullptr,
230 m_pActionQuit
231 };
232
233 m_trayIcon.create(trayMenu, [this](QObject const *o, const char *s) {
234 connect(o, s, this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason)));
235 setIcon(synergyDisconnected);
236 });
237
238 if (appConfig().getAutoHide()) {
239 hide();
240 } else {
241 showNormal();
242 }
243
244 m_VersionChecker.checkLatest();
245
246 // only start if user has previously started. this stops the gui from
247 // auto hiding before the user has configured synergy (which of course
248 // confuses first time users, who think synergy has crashed).
249 if (appConfig().startedBefore() && appConfig().processMode() == Desktop) {
250 startSynergy();
251 }
252 }
253
setStatus(const QString & status)254 void MainWindow::setStatus(const QString &status)
255 {
256 m_pStatusLabel->setText(status);
257 }
258
retranslateMenuBar()259 void MainWindow::retranslateMenuBar()
260 {
261 m_pMenuFile->setTitle(tr("&File"));
262 m_pMenuEdit->setTitle(tr("&Edit"));
263 m_pMenuWindow->setTitle(tr("&Window"));
264 m_pMenuHelp->setTitle(tr("&Help"));
265 }
266
createMenuBar()267 void MainWindow::createMenuBar()
268 {
269 m_pMenuBar = new QMenuBar(this);
270 m_pMenuFile = new QMenu("", m_pMenuBar);
271 m_pMenuEdit = new QMenu("", m_pMenuBar);
272 m_pMenuWindow = new QMenu("", m_pMenuBar);
273 m_pMenuHelp = new QMenu("", m_pMenuBar);
274 retranslateMenuBar();
275
276 m_pMenuBar->addAction(m_pMenuFile->menuAction());
277 m_pMenuBar->addAction(m_pMenuEdit->menuAction());
278 #if !defined(Q_OS_MAC)
279 m_pMenuBar->addAction(m_pMenuWindow->menuAction());
280 #endif
281 m_pMenuBar->addAction(m_pMenuHelp->menuAction());
282
283 m_pMenuFile->addAction(m_pActionStartSynergy);
284 m_pMenuFile->addAction(m_pActionStopSynergy);
285 m_pMenuFile->addSeparator();
286 m_pMenuFile->addAction(m_pActivate);
287 m_pMenuFile->addSeparator();
288 m_pMenuFile->addAction(m_pActionSave);
289 m_pMenuFile->addSeparator();
290 m_pMenuFile->addAction(m_pActionQuit);
291 m_pMenuEdit->addAction(m_pActionSettings);
292 m_pMenuWindow->addAction(m_pActionMinimize);
293 m_pMenuWindow->addAction(m_pActionRestore);
294 m_pMenuHelp->addAction(m_pActionAbout);
295 m_pMenuHelp->addAction(m_pActionHelp);
296
297
298 setMenuBar(m_pMenuBar);
299 }
300
loadSettings()301 void MainWindow::loadSettings()
302 {
303 enableServer(appConfig().getServerGroupChecked());
304 enableClient(appConfig().getClientGroupChecked());
305
306 m_pLineEditHostname->setText(appConfig().getServerHostname());
307 }
308
initConnections()309 void MainWindow::initConnections()
310 {
311 connect(m_pActionMinimize, SIGNAL(triggered()), this, SLOT(hide()));
312 connect(m_pActionRestore, SIGNAL(triggered()), this, SLOT(showNormal()));
313 connect(m_pActionStartSynergy, SIGNAL(triggered()), this, SLOT(actionStart()));
314 connect(m_pActionStopSynergy, SIGNAL(triggered()), this, SLOT(stopSynergy()));
315 connect(m_pActionQuit, SIGNAL(triggered()), qApp, SLOT(quit()));
316 connect(&m_VersionChecker, SIGNAL(updateFound(const QString&)), this, SLOT(updateFound(const QString&)));
317 }
318
saveSettings()319 void MainWindow::saveSettings()
320 {
321 // program settings
322 appConfig().setServerGroupChecked(m_pRadioGroupServer->isChecked());
323 appConfig().setClientGroupChecked(m_pRadioGroupClient->isChecked());
324 appConfig().setServerHostname(m_pLineEditHostname->text());
325
326 /* Save everything */
327 GUI::Config::ConfigWriter::make()->globalSave();
328 }
329
zeroConfToggled()330 void MainWindow::zeroConfToggled() {
331 #if !defined(SYNERGY_ENTERPRISE) && defined(SYNERGY_AUTOCONFIG)
332 updateZeroconfService();
333
334 addZeroconfServer(m_AppConfig->autoConfigServer());
335
336 updateAutoConfigWidgets();
337 #endif
338 }
339
setIcon(qSynergyState state) const340 void MainWindow::setIcon(qSynergyState state) const
341 {
342 QIcon icon;
343
344 #ifdef Q_OS_MAC
345 if (isOSXUseDarkIcons())
346 icon.addFile(synergyDarkIconFiles[state]);
347 else
348 icon.addFile(synergyLightIconFiles[state]);
349 #else
350 icon.addFile(synergyDefaultIconFiles[state]);
351 #endif
352
353 m_trayIcon.set(icon);
354 }
355
trayActivated(QSystemTrayIcon::ActivationReason reason)356 void MainWindow::trayActivated(QSystemTrayIcon::ActivationReason reason)
357 {
358 if (reason == QSystemTrayIcon::DoubleClick)
359 {
360 if (isVisible())
361 {
362 hide();
363 }
364 else
365 {
366 showNormal();
367 activateWindow();
368 }
369 }
370 }
371
logOutput()372 void MainWindow::logOutput()
373 {
374 if (m_pSynergy)
375 {
376 QString text(m_pSynergy->readAllStandardOutput());
377 foreach(QString line, text.split(QRegExp("\r|\n|\r\n")))
378 {
379 if (!line.isEmpty())
380 {
381 appendLogRaw(line);
382 }
383 }
384 }
385 }
386
logError()387 void MainWindow::logError()
388 {
389 if (m_pSynergy)
390 {
391 appendLogRaw(m_pSynergy->readAllStandardError());
392 }
393 }
394
updateFound(const QString & version)395 void MainWindow::updateFound(const QString &version)
396 {
397 m_pWidgetUpdate->show();
398 m_pLabelUpdate->setText(
399 tr("<p>Your version of Synergy is out of date. "
400 "Version <b>%1</b> is now available to "
401 "<a href=\"%2\">download</a>.</p>")
402 .arg(version).arg(DOWNLOAD_URL));
403 }
404
appendLogInfo(const QString & text)405 void MainWindow::appendLogInfo(const QString& text)
406 {
407 appendLogRaw(getTimeStamp() + " INFO: " + text);
408 }
409
appendLogDebug(const QString & text)410 void MainWindow::appendLogDebug(const QString& text) {
411 if (appConfig().logLevel() >= debugLogLevel) {
412 appendLogRaw(getTimeStamp() + " DEBUG: " + text);
413 }
414 }
415
appendLogError(const QString & text)416 void MainWindow::appendLogError(const QString& text)
417 {
418 appendLogRaw(getTimeStamp() + " ERROR: " + text);
419 }
420
appendLogRaw(const QString & text)421 void MainWindow::appendLogRaw(const QString& text)
422 {
423 foreach(QString line, text.split(QRegExp("\r|\n|\r\n"))) {
424 if (!line.isEmpty()) {
425
426 // HACK: macOS 10.13.4+ spamming error lines in logs making them
427 // impossible to read and debug; giving users a red herring.
428 if (line.contains("calling TIS/TSM in non-main thread environment")) {
429 continue;
430 }
431
432 m_pLogOutput->appendPlainText(line);
433 updateFromLogLine(line);
434 }
435 }
436 }
437
updateFromLogLine(const QString & line)438 void MainWindow::updateFromLogLine(const QString &line)
439 {
440 // TODO: This shouldn't be updating from log needs a better way of doing this
441 checkConnected(line);
442 checkFingerprint(line);
443 checkSecureSocket(line);
444
445 #ifndef SYNERGY_ENTERPRISE
446 checkLicense(line);
447 #endif
448 }
449
checkConnected(const QString & line)450 void MainWindow::checkConnected(const QString& line)
451 {
452 // TODO: implement ipc connection state messages to replace this hack.
453 if (m_pRadioGroupServer->isChecked())
454 {
455 m_serverConnection.update(line);
456 m_pLabelServerState->updateServerState(line);
457 }
458 else
459 {
460 m_clientConnection.update(line);
461 m_pLabelClientState->updateClientState(line);
462 }
463
464 if (line.contains("connected to server") || line.contains("has connected"))
465 {
466 setSynergyState(synergyConnected);
467
468 if (!appConfig().startedBefore() && isVisible()) {
469 QMessageBox::information(
470 this, "Synergy",
471 tr("Synergy is now connected. You can close the "
472 "config window and Synergy will remain connected in "
473 "the background."));
474
475 appConfig().setStartedBefore(true);
476 }
477 }
478 else if (line.contains("started server"))
479 {
480 setSynergyState(synergyListening);
481 }
482 else if (line.contains("disconnected from server") || line.contains("process exited"))
483 {
484 setSynergyState(synergyDisconnected);
485 }
486 else if (line.contains("connecting to"))
487 {
488 setSynergyState(synergyConnecting);
489 }
490 }
491
492 #ifndef SYNERGY_ENTERPRISE
checkLicense(const QString & line)493 void MainWindow::checkLicense(const QString &line)
494 {
495 if (line.contains("trial has expired")) {
496 licenseManager().refresh();
497 raiseActivationDialog();
498 }
499 }
500 #endif
501
checkFingerprint(const QString & line)502 void MainWindow::checkFingerprint(const QString& line)
503 {
504 QRegExp fingerprintRegex(".*server fingerprint: ([A-F0-9:]+)");
505 if (!fingerprintRegex.exactMatch(line)) {
506 return;
507 }
508
509 QString fingerprint = fingerprintRegex.cap(1);
510 if (Fingerprint::trustedServers().isTrusted(fingerprint)) {
511 return;
512 }
513
514 static bool messageBoxAlreadyShown = false;
515
516 if (!messageBoxAlreadyShown) {
517 stopSynergy();
518
519 messageBoxAlreadyShown = true;
520 QMessageBox::StandardButton fingerprintReply =
521 QMessageBox::information(
522 this, tr("Security question"),
523 tr("You are connecting to a server. Here is it's fingerprint:\n\n"
524 "%1\n\n"
525 "Compare this fingerprint to the one on your server's screen."
526 "If the two don't match exactly, then it's probably not the server "
527 "you're expecting (it could be a malicious user).\n\n"
528 "To automatically trust this fingerprint for future "
529 "connections, click Yes. To reject this fingerprint and "
530 "disconnect from the server, click No.")
531 .arg(fingerprint),
532 QMessageBox::Yes | QMessageBox::No);
533
534 if (fingerprintReply == QMessageBox::Yes) {
535 // restart core process after trusting fingerprint.
536 Fingerprint::trustedServers().trust(fingerprint);
537 startSynergy();
538 }
539
540 messageBoxAlreadyShown = false;
541 }
542 }
543
checkSecureSocket(const QString & line)544 void MainWindow::checkSecureSocket(const QString& line)
545 {
546 // obviously not very secure, since this can be tricked by injecting something
547 // into the log. however, since we don't have IPC between core and GUI... patches welcome.
548 const int index = line.indexOf(tlsCheckString, 0, Qt::CaseInsensitive);
549 if (index > 0) {
550 secureSocket(true);
551
552 //Get the protocol version from the line
553 m_SecureSocketVersion = line.mid(index + strlen(tlsCheckString)); // Compliant: we made sure that tlsCheckString variable ended with null(static const char* declaration)
554 }
555 }
getTimeStamp()556 QString MainWindow::getTimeStamp()
557 {
558 QDateTime current = QDateTime::currentDateTime();
559 return '[' + current.toString(Qt::ISODate) + ']';
560 }
561
restartSynergy()562 void MainWindow::restartSynergy()
563 {
564 stopSynergy();
565 startSynergy();
566 }
567
proofreadInfo()568 void MainWindow::proofreadInfo()
569 {
570 #ifndef SYNERGY_ENTERPRISE
571 setEdition(m_AppConfig->edition()); // Why is this here?
572 #endif
573 int oldState = m_SynergyState;
574 m_SynergyState = synergyDisconnected;
575 setSynergyState((qSynergyState)oldState);
576 }
577
showEvent(QShowEvent * event)578 void MainWindow::showEvent(QShowEvent* event)
579 {
580 QMainWindow::showEvent(event);
581 emit windowShown();
582 }
583
clearLog()584 void MainWindow::clearLog()
585 {
586 m_pLogOutput->clear();
587 }
588
startSynergy()589 void MainWindow::startSynergy()
590 {
591 saveSettings();
592
593 #ifndef SYNERGY_ENTERPRISE
594 SerialKey serialKey = m_LicenseManager->serialKey();
595 if (!serialKey.isValid()) {
596 if (QDialog::Rejected == raiseActivationDialog()) {
597 return;
598 }
599 }
600 #endif
601 bool desktopMode = appConfig().processMode() == Desktop;
602 bool serviceMode = appConfig().processMode() == Service;
603
604 appendLogDebug("starting process");
605 m_ExpectedRunningState = kStarted;
606 setSynergyState(synergyConnecting);
607
608 QString app;
609 QStringList args;
610
611 args << "-f" << "--no-tray" << "--debug" << appConfig().logLevelText();
612
613
614 args << "--name" << appConfig().screenName();
615
616 if (desktopMode)
617 {
618 setSynergyProcess(new QProcess(this));
619 }
620 else
621 {
622 // tell client/server to talk to daemon through ipc.
623 args << "--ipc";
624
625 #if defined(Q_OS_WIN)
626 // tell the client/server to shut down when a ms windows desk
627 // is switched; this is because we may need to elevate or not
628 // based on which desk the user is in (login always needs
629 // elevation, where as default desk does not).
630 // Note that this is only enabled when synergy is set to elevate
631 // 'as needed' (e.g. on a UAC dialog popup) in order to prevent
632 // unnecessary restarts when synergy was started elevated or
633 // when it is not allowed to elevate. In these cases restarting
634 // the server is fruitless.
635 if (appConfig().elevateMode() == ElevateAsNeeded) {
636 args << "--stop-on-desk-switch";
637 }
638 #endif
639 }
640
641 #ifndef Q_OS_LINUX
642
643 if (m_ServerConfig.enableDragAndDrop()) {
644 args << "--enable-drag-drop";
645 }
646
647 #endif
648
649 #if defined(Q_OS_WIN)
650 if (m_AppConfig->getCryptoEnabled()) {
651 args << "--enable-crypto";
652 args << "--tls-cert" << QString("\"%1\"").arg(m_AppConfig->getTLSCertPath());
653 }
654 // on windows, the profile directory changes depending on the user that
655 // launched the process (e.g. when launched with elevation). setting the
656 // profile dir on launch ensures it uses the same profile dir is used
657 // no matter how its relaunched.
658 args << "--profile-dir" << getProfileRootForArg();
659
660 #else
661 if (m_AppConfig->getCryptoEnabled()) {
662 args << "--enable-crypto";
663 args << "--tls-cert" << m_AppConfig->getTLSCertPath();
664 }
665 #endif
666
667 // put a space between last log output and new instance.
668 if (!m_pLogOutput->toPlainText().isEmpty())
669 appendLogRaw("");
670
671 appendLogInfo("starting " + QString(synergyType() == synergyServer ? "server" : "client"));
672
673 if ((synergyType() == synergyClient && !clientArgs(args, app))
674 || (synergyType() == synergyServer && !serverArgs(args, app)))
675 {
676 stopSynergy();
677 return;
678 }
679
680 if (desktopMode)
681 {
682 connect(synergyProcess(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(synergyFinished(int, QProcess::ExitStatus)));
683 connect(synergyProcess(), SIGNAL(readyReadStandardOutput()), this, SLOT(logOutput()));
684 connect(synergyProcess(), SIGNAL(readyReadStandardError()), this, SLOT(logError()));
685 }
686
687 qDebug() << args;
688
689 // show command if debug log level...
690 if (appConfig().logLevel() >= 4) {
691 appendLogInfo(QString("command: %1 %2").arg(app, args.join(" ")));
692 }
693
694 appendLogInfo("log level: " + appConfig().logLevelText());
695
696 if (appConfig().logToFile())
697 appendLogInfo("log file: " + appConfig().logFilename());
698
699 if (desktopMode)
700 {
701 synergyProcess()->start(app, args);
702 if (!synergyProcess()->waitForStarted())
703 {
704 show();
705 QMessageBox::warning(this, tr("Program can not be started"), QString(tr("The executable<br><br>%1<br><br>could not be successfully started, although it does exist. Please check if you have sufficient permissions to run this program.").arg(app)));
706 return;
707 }
708 }
709
710 if (serviceMode)
711 {
712 QString command(app + " " + args.join(" "));
713 m_IpcClient.sendCommand(command, appConfig().elevateMode());
714 }
715 }
716
actionStart()717 void MainWindow::actionStart()
718 {
719 m_clientConnection.setCheckConnection(true);
720 startSynergy();
721 }
722
retryStart()723 void MainWindow::retryStart()
724 {
725 //This function is only called after a failed start
726 //Only start synergy if the current state is pending retry
727 if (m_SynergyState == synergyPendingRetry)
728 {
729 startSynergy();
730 }
731 }
732
clientArgs(QStringList & args,QString & app)733 bool MainWindow::clientArgs(QStringList& args, QString& app)
734 {
735 app = appPath(appConfig().synergycName());
736
737 if (!QFile::exists(app))
738 {
739 show();
740 QMessageBox::warning(this, tr("Synergy client not found"),
741 tr("The executable for the synergy client does not exist."));
742 return false;
743 }
744
745 #if defined(Q_OS_WIN)
746 // wrap in quotes so a malicious user can't start \Program.exe as admin.
747 app = QString("\"%1\"").arg(app);
748 #endif
749
750 if (appConfig().logToFile())
751 {
752 appConfig().persistLogDir();
753 args << "--log" << appConfig().logFilenameCmd();
754 }
755
756
757 #if !defined(SYNERGY_ENTERPRISE) && defined(SYNERGY_AUTOCONFIG)
758 // check auto config first, if it is disabled or no server detected,
759 // use line edit host name if it is not empty
760 if (appConfig().autoConfig()) {
761 if (m_pComboServerList->count() != 0) {
762 QString serverIp = m_pComboServerList->currentText();
763 args << serverIp + ":" + QString::number(appConfig().port());
764 return true;
765 }
766 else {
767 show();
768 QMessageBox::warning(
769 this, tr("No server selected"),
770 tr("No auto config server was selected, try manual mode instead."));
771 return false;
772 }
773 }
774 #endif
775
776 if (m_pLineEditHostname->text().isEmpty())
777 {
778 #if !defined(SYNERGY_ENTERPRISE) && defined(SYNERGY_AUTOCONFIG)
779 //check if autoconfig mode is enabled
780 if (!appConfig().autoConfig())
781 {
782 #endif
783 show();
784 QMessageBox::warning(
785 this, tr("Hostname is empty"),
786 tr("Please fill in a hostname for the synergy client to connect to."));
787 return false;
788
789 #if !defined(SYNERGY_ENTERPRISE) && defined(SYNERGY_AUTOCONFIG)
790 }
791 else
792 {
793 return false;
794 }
795 #endif
796 }
797
798 QString hostName = m_pLineEditHostname->text();
799 // if interface is IPv6 - ensure that ip is in square brackets
800 if (hostName.count(':') > 1) {
801 if(hostName[0] != '[') {
802 hostName.insert(0, '[');
803 }
804 if(hostName[hostName.size() - 1] != ']') {
805 hostName.push_back(']');
806 }
807 }
808
809 args << hostName + ":" + QString::number(appConfig().port());
810 return true;
811 }
812
configFilename()813 QString MainWindow::configFilename()
814 {
815 QString configFullPath;
816 if (appConfig().getUseExternalConfig())
817 {
818 configFullPath = appConfig().getConfigFile();
819 }
820 else
821 {
822 QStringList errors;
823 for (auto path : {QStandardPaths::AppDataLocation,
824 QStandardPaths::AppConfigLocation})
825 {
826 auto configDirPath = QStandardPaths::writableLocation(path);
827 if (!QDir().mkpath(configDirPath))
828 {
829 errors.push_back(tr("Failed to create config folder \"%1\"").arg(configDirPath));
830 continue;
831 }
832
833 QFile configFile(configDirPath + "/LastConfig.cfg");
834 if (!configFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
835 {
836 errors.push_back(tr("File:\"%1\" Error:%2").arg(configFile.fileName(), configFile.errorString()));
837 continue;
838 }
839
840 serverConfig().save(configFile);
841 configFile.close();
842 configFullPath = configFile.fileName();
843
844 break;
845 }
846
847 if (configFullPath.isEmpty())
848 {
849 QMessageBox::critical(this, tr("Cannot write configuration file"), errors.join('\n'));
850 }
851 }
852
853 return configFullPath;
854 }
855
address() const856 QString MainWindow::address() const
857 {
858 QString i = appConfig().networkInterface();
859 // if interface is IPv6 - ensure that ip is in square brackets
860 if (i.count(':') > 1) {
861 if(i[0] != '[') {
862 i.insert(0, '[');
863 }
864 if(i[i.size() - 1] != ']') {
865 i.push_back(']');
866 }
867 }
868 return (!i.isEmpty() ? i : "") + ":" + QString::number(appConfig().port());
869 }
870
appPath(const QString & name)871 QString MainWindow::appPath(const QString& name)
872 {
873 return appConfig().synergyProgramDir() + name;
874 }
875
serverArgs(QStringList & args,QString & app)876 bool MainWindow::serverArgs(QStringList& args, QString& app)
877 {
878 app = appPath(appConfig().synergysName());
879
880 if (!QFile::exists(app))
881 {
882 QMessageBox::warning(this, tr("Synergy server not found"),
883 tr("The executable for the synergy server does not exist."));
884 return false;
885 }
886
887 #if defined(Q_OS_WIN)
888 // wrap in quotes so a malicious user can't start \Program.exe as admin.
889 app = QString("\"%1\"").arg(app);
890 #endif
891
892 if (appConfig().logToFile())
893 {
894 appConfig().persistLogDir();
895
896 args << "--log" << appConfig().logFilenameCmd();
897 }
898
899 QString configFilename = this->configFilename();
900 if (configFilename.isEmpty())
901 {
902 return false;
903 }
904 #if defined(Q_OS_WIN)
905 // wrap in quotes in case username contains spaces.
906 configFilename = QString("\"%1\"").arg(configFilename);
907 #endif
908 args << "-c" << configFilename << "--address" << address();
909 appendLogInfo("config file: " + configFilename);
910
911 #ifndef SYNERGY_ENTERPRISE
912 if (!appConfig().serialKey().isEmpty()) {
913 args << "--serial-key" << appConfig().serialKey();
914 }
915 #endif
916
917 return true;
918 }
919
stopSynergy()920 void MainWindow::stopSynergy()
921 {
922 appendLogDebug("stopping process");
923
924 m_ExpectedRunningState = kStopped;
925
926 if (appConfig().processMode() == Service)
927 {
928 stopService();
929 }
930 else if (appConfig().processMode() == Desktop)
931 {
932 stopDesktop();
933 }
934
935 setSynergyState(synergyDisconnected);
936
937 // reset so that new connects cause auto-hide.
938 m_AlreadyHidden = false;
939 }
940
stopService()941 void MainWindow::stopService()
942 {
943 // send empty command to stop service from laucning anything.
944 m_IpcClient.sendCommand("", appConfig().elevateMode());
945 }
946
stopDesktop()947 void MainWindow::stopDesktop()
948 {
949 QMutexLocker locker(&m_StopDesktopMutex);
950 if (!synergyProcess()) {
951 return;
952 }
953
954 appendLogInfo("stopping synergy desktop process");
955
956 if (synergyProcess()->isOpen()) {
957 synergyProcess()->close();
958 }
959
960 delete synergyProcess();
961 setSynergyProcess(NULL);
962 }
963
synergyFinished(int exitCode,QProcess::ExitStatus)964 void MainWindow::synergyFinished(int exitCode, QProcess::ExitStatus)
965 {
966 if (exitCode == 0) {
967 appendLogInfo(QString("process exited normally"));
968 }
969 else {
970 appendLogError(QString("process exited with error code: %1").arg(exitCode));
971 }
972
973 if (m_ExpectedRunningState == kStarted) {
974
975 setSynergyState(synergyPendingRetry);
976 QTimer::singleShot(1000, this, SLOT(retryStart()));
977 appendLogInfo(QString("detected process not running, auto restarting"));
978 }
979 else {
980 setSynergyState(synergyDisconnected);
981 }
982 }
983
setSynergyState(qSynergyState state)984 void MainWindow::setSynergyState(qSynergyState state)
985 {
986 // always assume connection is not secure when connection changes
987 // to anything except connected. the only way the padlock shows is
988 // when the correct TLS version string is detected.
989 if (state != synergyConnected) {
990 secureSocket(false);
991 }
992
993 if (synergyState() == state)
994 return;
995
996 if ((state == synergyConnected) || (state == synergyConnecting) || (state == synergyListening) || (state == synergyPendingRetry))
997 {
998 disconnect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger()));
999 connect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStopSynergy, SLOT(trigger()));
1000 m_pButtonToggleStart->setText(tr("&Stop"));
1001 m_pButtonApply->setEnabled(true);
1002 }
1003 else if (state == synergyDisconnected)
1004 {
1005 disconnect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStopSynergy, SLOT(trigger()));
1006 connect (m_pButtonToggleStart, SIGNAL(clicked()), m_pActionStartSynergy, SLOT(trigger()));
1007 m_pButtonToggleStart->setText(tr("&Start"));
1008 m_pButtonApply->setEnabled(false);
1009 }
1010
1011 bool running = false;
1012 if (state == synergyConnected || state == synergyListening) {
1013 running = true;
1014 }
1015
1016 m_pActionStartSynergy->setEnabled(!running);
1017 m_pActionStopSynergy->setEnabled(running);
1018
1019 switch (state)
1020 {
1021 case synergyListening: {
1022 if (synergyType() == synergyServer) {
1023 setStatus(tr("Synergy is waiting for clients").arg(m_SecureSocketVersion));
1024 }
1025
1026 break;
1027 }
1028 case synergyConnected: {
1029 if (m_SecureSocket) {
1030 setStatus(tr("Synergy is connected (with %1)").arg(m_SecureSocketVersion));
1031 }
1032 else {
1033 setStatus(tr("Synergy is running (without TLS encryption)").arg(m_SecureSocketVersion));
1034 }
1035 break;
1036 }
1037 case synergyConnecting:
1038 setStatus(tr("Synergy is starting..."));
1039 break;
1040 case synergyPendingRetry:
1041 setStatus(tr("There was an error, retrying..."));
1042 break;
1043 case synergyDisconnected:
1044 setStatus(tr("Synergy is not running"));
1045 break;
1046 }
1047
1048 setIcon(state);
1049
1050 m_SynergyState = state;
1051 }
1052
setVisible(bool visible)1053 void MainWindow::setVisible(bool visible)
1054 {
1055 QMainWindow::setVisible(visible);
1056 m_pActionMinimize->setEnabled(visible);
1057 m_pActionRestore->setEnabled(!visible);
1058
1059 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 // lion
1060 // dock hide only supported on lion :(
1061 ProcessSerialNumber psn = { 0, kCurrentProcess };
1062 #pragma GCC diagnostic push
1063 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1064 GetCurrentProcess(&psn);
1065 #pragma GCC diagnostic pop
1066 if (visible)
1067 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
1068 else
1069 TransformProcessType(&psn, kProcessTransformToBackgroundApplication);
1070 #endif
1071 }
1072
getIPAddresses()1073 QString MainWindow::getIPAddresses()
1074 {
1075 QStringList result;
1076 bool hinted = false;
1077 const auto localnet = QHostAddress::parseSubnet("192.168.0.0/16");
1078 const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
1079
1080 for (const auto& address : addresses) {
1081 if (address.protocol() == QAbstractSocket::IPv4Protocol &&
1082 address != QHostAddress(QHostAddress::LocalHost) &&
1083 !address.isInSubnet(QHostAddress::parseSubnet("169.254.0.0/16"))) {
1084
1085 // usually 192.168.x.x is a useful ip for the user, so indicate
1086 // this by making it bold.
1087 if (!hinted && address.isInSubnet(localnet)) {
1088 QString format = "<span style=\"color:#4285F4;\">%1</span>";
1089 result.append(format.arg(address.toString()));
1090 hinted = true;
1091 }
1092 else {
1093 result.append(address.toString());
1094 }
1095 }
1096 }
1097
1098 if (result.isEmpty()) {
1099 result.append(tr("Unknown"));
1100 }
1101
1102 return result.join(", ");
1103 }
1104
changeEvent(QEvent * event)1105 void MainWindow::changeEvent(QEvent* event)
1106 {
1107 if (event != 0)
1108 {
1109 switch (event->type())
1110 {
1111 case QEvent::LanguageChange:
1112 {
1113 retranslateUi(this);
1114 retranslateMenuBar();
1115
1116 proofreadInfo();
1117
1118 break;
1119 }
1120 case QEvent::WindowStateChange:
1121 {
1122 windowStateChanged();
1123 break;
1124 }
1125 }
1126 }
1127 // all that do not return are allowing the event to propagate
1128 QMainWindow::changeEvent(event);
1129 }
1130
addZeroconfServer(const QString name)1131 void MainWindow::addZeroconfServer(const QString name)
1132 {
1133 // don't add yourself to the server list.
1134 if (getIPAddresses().contains(name)) {
1135 return;
1136 }
1137
1138 if (m_pComboServerList->findText(name) == -1) {
1139 m_pComboServerList->addItem(name);
1140 }
1141 }
1142
setEdition(Edition edition)1143 void MainWindow::setEdition(Edition edition)
1144 {
1145 #ifndef SYNERGY_ENTERPRISE
1146 setWindowTitle(m_LicenseManager->getEditionName (edition));
1147 #endif
1148 }
1149
1150 #ifndef SYNERGY_ENTERPRISE
InvalidLicense()1151 void MainWindow::InvalidLicense()
1152 {
1153 stopSynergy();
1154 m_AppConfig->activationHasRun(false);
1155 }
1156
showLicenseNotice(const QString & notice)1157 void MainWindow::showLicenseNotice(const QString& notice)
1158 {
1159 this->m_trialLabel->hide();
1160
1161 if (!notice.isEmpty()) {
1162 this->m_trialLabel->setText(notice);
1163 this->m_trialLabel->show();
1164 }
1165
1166 setWindowTitle (m_LicenseManager->activeEditionName());
1167 }
1168 #endif
1169
updateLocalFingerprint()1170 void MainWindow::updateLocalFingerprint()
1171 {
1172 if (m_AppConfig->getCryptoEnabled() &&
1173 Fingerprint::local().fileExists() &&
1174 m_pRadioGroupServer->isChecked())
1175 {
1176 m_pLabelFingerprint->setVisible(true);
1177 }
1178 else
1179 {
1180 m_pLabelFingerprint->setVisible(false);
1181 }
1182 }
1183
1184 #ifndef SYNERGY_ENTERPRISE
1185 LicenseManager&
licenseManager() const1186 MainWindow::licenseManager() const
1187 {
1188 return *m_LicenseManager;
1189 }
1190 #endif
1191
on_m_pActionSave_triggered()1192 bool MainWindow::on_m_pActionSave_triggered()
1193 {
1194 QString fileName = QFileDialog::getSaveFileName(this, tr("Save configuration as..."));
1195
1196 if (!fileName.isEmpty() && !serverConfig().save(fileName))
1197 {
1198 QMessageBox::warning(this, tr("Save failed"), tr("Could not save configuration to file."));
1199 return true;
1200 }
1201
1202 return false;
1203 }
1204
on_m_pActionAbout_triggered()1205 void MainWindow::on_m_pActionAbout_triggered()
1206 {
1207 AboutDialog dlg(this, appPath(appConfig().synergycName()));
1208 dlg.exec();
1209 }
1210
on_m_pActionHelp_triggered()1211 void MainWindow::on_m_pActionHelp_triggered()
1212 {
1213 QDesktopServices::openUrl(QUrl(HELP_URL));
1214 }
1215
updateZeroconfService()1216 void MainWindow::updateZeroconfService()
1217 {
1218 #if !defined(SYNERGY_ENTERPRISE) && defined(SYNERGY_AUTOCONFIG)
1219
1220 // reset the server list in case one has gone away.
1221 // it'll be re-added after the zeroconf service restarts.
1222 m_pComboServerList->clear();
1223
1224 if (m_pZeroconf != nullptr) {
1225 if (appConfig().autoConfig()) {
1226 m_pZeroconf->startService();
1227 }
1228 else {
1229 m_pZeroconf->stopService();
1230 }
1231 }
1232 #endif
1233 }
1234
updateAutoConfigWidgets()1235 void MainWindow::updateAutoConfigWidgets()
1236 {
1237 m_pLabelServerName->show();
1238 m_pLineEditHostname->show();
1239
1240 m_pLabelAutoDetected->hide();
1241 m_pComboServerList->hide();
1242 }
1243
on_m_pActionSettings_triggered()1244 void MainWindow::on_m_pActionSettings_triggered()
1245 {
1246 SettingsDialog(this, appConfig()).exec();
1247 }
1248
autoAddScreen(const QString name)1249 void MainWindow::autoAddScreen(const QString name)
1250 {
1251 if (m_ServerConfig.ignoreAutoConfigClient()) {
1252 appendLogDebug(QString("ignoring zeroconf screen: %1").arg(name));
1253 return;
1254 }
1255
1256 #ifndef SYNERGY_ENTERPRISE
1257 if (m_ActivationDialogRunning) {
1258 // TODO: refactor this code
1259 // add this screen to the pending list and check this list until
1260 // users finish activation dialog
1261 m_PendingClientNames.append(name);
1262 return;
1263 }
1264 #endif
1265
1266 int r = m_ServerConfig.autoAddScreen(name);
1267 if (r != kAutoAddScreenOk) {
1268 switch (r) {
1269 case kAutoAddScreenManualServer:
1270 showConfigureServer(
1271 tr("Please add the server (%1) to the grid.")
1272 .arg(appConfig().screenName()));
1273 break;
1274
1275 case kAutoAddScreenManualClient:
1276 showConfigureServer(
1277 tr("Please drag the new client screen (%1) "
1278 "to the desired position on the grid.")
1279 .arg(name));
1280 break;
1281 }
1282 }
1283 }
1284
showConfigureServer(const QString & message)1285 void MainWindow::showConfigureServer(const QString& message)
1286 {
1287 ServerConfigDialog dlg(this, serverConfig());
1288 dlg.message(message);
1289 dlg.exec();
1290 }
1291
on_m_pButtonConfigureServer_clicked()1292 void MainWindow::on_m_pButtonConfigureServer_clicked()
1293 {
1294 showConfigureServer();
1295 }
1296
on_m_pActivate_triggered()1297 void MainWindow::on_m_pActivate_triggered()
1298 {
1299 #ifndef SYNERGY_ENTERPRISE
1300 raiseActivationDialog();
1301 #endif
1302 }
1303
on_m_pButtonApply_clicked()1304 void MainWindow::on_m_pButtonApply_clicked()
1305 {
1306 m_clientConnection.setCheckConnection(true);
1307 restartSynergy();
1308 }
1309
1310 #ifndef SYNERGY_ENTERPRISE
raiseActivationDialog()1311 int MainWindow::raiseActivationDialog()
1312 {
1313 if (m_ActivationDialogRunning) {
1314 return QDialog::Rejected;
1315 }
1316 ActivationDialog activationDialog (this, appConfig(), licenseManager());
1317 m_ActivationDialogRunning = true;
1318 int result = activationDialog.exec();
1319 m_ActivationDialogRunning = false;
1320 if (!m_PendingClientNames.empty()) {
1321 foreach (const QString& name, m_PendingClientNames) {
1322 autoAddScreen(name);
1323 }
1324
1325 m_PendingClientNames.clear();
1326 }
1327 return result;
1328 }
1329 #endif
1330
on_windowShown()1331 void MainWindow::on_windowShown()
1332 {
1333 #ifndef SYNERGY_ENTERPRISE
1334 if (!m_AppConfig->activationHasRun() &&
1335 !m_LicenseManager->serialKey().isValid()){
1336 raiseActivationDialog();
1337 }
1338 #endif
1339 }
1340
getProfileRootForArg()1341 QString MainWindow::getProfileRootForArg()
1342 {
1343 CoreInterface coreInterface;
1344 QString dir = coreInterface.getProfileDir();
1345
1346 // HACK: strip our app name since we're returning the root dir.
1347 #if defined(Q_OS_WIN)
1348 dir.replace("\\Synergy", "");
1349 #else
1350 dir.replace("/.synergy", "");
1351 #endif
1352
1353 return QString("\"%1\"").arg(dir);
1354 }
1355
secureSocket(bool secureSocket)1356 void MainWindow::secureSocket(bool secureSocket)
1357 {
1358 m_SecureSocket = secureSocket;
1359 if (secureSocket) {
1360 m_pLabelPadlock->show();
1361 }
1362 else {
1363 m_pLabelPadlock->hide();
1364 }
1365 }
1366
on_m_pLabelComputerName_linkActivated(const QString &)1367 void MainWindow::on_m_pLabelComputerName_linkActivated(const QString&)
1368 {
1369 m_pActionSettings->trigger();
1370 }
1371
on_m_pLabelFingerprint_linkActivated(const QString &)1372 void MainWindow::on_m_pLabelFingerprint_linkActivated(const QString&)
1373 {
1374 QMessageBox::information(this, "SSL/TLS fingerprint", Fingerprint::local().readFirst());
1375 }
1376
on_m_pComboServerList_currentIndexChanged(const QString & server)1377 void MainWindow::on_m_pComboServerList_currentIndexChanged(const QString &server)
1378 {
1379 appConfig().setAutoConfigServer(server);
1380 }
1381
windowStateChanged()1382 void MainWindow::windowStateChanged()
1383 {
1384 if (windowState() == Qt::WindowMinimized && appConfig().getMinimizeToTray())
1385 hide();
1386 }
1387
updateScreenName()1388 void MainWindow::updateScreenName()
1389 {
1390 m_pLabelComputerName->setText(tr("This computer's name: %1 (<a href=\"#\" style=\"text-decoration: none; color: #4285F4;\">Preferences</a>)").arg(appConfig().screenName()));
1391 serverConfig().updateServerName();
1392 }
1393
enableServer(bool enable)1394 void MainWindow::enableServer(bool enable)
1395 {
1396 m_pRadioGroupServer->setChecked(enable);
1397
1398 if (enable)
1399 {
1400 m_pButtonConfigureServer->show();
1401 m_pLabelServerState->show();
1402 updateLocalFingerprint();
1403 m_pButtonToggleStart->setEnabled(enable);
1404 }
1405 else
1406 {
1407 m_pLabelFingerprint->hide();
1408 m_pButtonConfigureServer->hide();
1409 m_pLabelServerState->hide();
1410 }
1411 }
1412
enableClient(bool enable)1413 void MainWindow::enableClient(bool enable)
1414 {
1415 m_pRadioGroupClient->setChecked(enable);
1416
1417 if (enable)
1418 {
1419 m_pLabelServerName->show();
1420 m_pLineEditHostname->show();
1421 m_pButtonConnect->show();
1422 m_pButtonToggleStart->setEnabled(enable);
1423 }
1424 else
1425 {
1426 m_pLabelClientState->hide();
1427 m_pLabelServerName->hide();
1428 m_pLineEditHostname->hide();
1429 m_pButtonConnect->hide();
1430 }
1431 }
1432
1433
on_m_pRadioGroupServer_clicked(bool)1434 void MainWindow::on_m_pRadioGroupServer_clicked(bool)
1435 {
1436 enableServer(true);
1437 enableClient(false);
1438 }
1439
on_m_pRadioGroupClient_clicked(bool)1440 void MainWindow::on_m_pRadioGroupClient_clicked(bool)
1441 {
1442 enableClient(true);
1443 enableServer(false);
1444 }
1445
on_m_pButtonConnect_clicked()1446 void MainWindow::on_m_pButtonConnect_clicked()
1447 {
1448 on_m_pButtonApply_clicked();
1449 }
1450
1451