source: trunk/synergy/cmd/launcher/launcher.cpp@ 3232

Last change on this file since 3232 was 2749, checked in by bird, 19 years ago

synergy v1.3.1 sources (zip).

File size: 17.8 KB
Line 
1/*
2 * synergy -- mouse and keyboard sharing utility
3 * Copyright (C) 2002 Chris Schoeneman
4 *
5 * This package is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * found in the file COPYING that should have accompanied this file.
8 *
9 * This package is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15#include "CConfig.h"
16#include "KeyTypes.h"
17#include "OptionTypes.h"
18#include "ProtocolTypes.h"
19#include "CLog.h"
20#include "CStringUtil.h"
21#include "CArch.h"
22#include "CArchMiscWindows.h"
23#include "XArch.h"
24#include "Version.h"
25#include "stdvector.h"
26#include "resource.h"
27
28// these must come after the above because it includes windows.h
29#include "LaunchUtil.h"
30#include "CAddScreen.h"
31#include "CAdvancedOptions.h"
32#include "CAutoStart.h"
33#include "CGlobalOptions.h"
34#include "CHotkeyOptions.h"
35#include "CInfo.h"
36#include "CScreensLinks.h"
37
38typedef std::vector<CString> CStringList;
39
40class CChildWaitInfo {
41public:
42 HWND m_dialog;
43 HANDLE m_child;
44 DWORD m_childID;
45 HANDLE m_ready;
46 HANDLE m_stop;
47};
48
49static const char* s_debugName[][2] = {
50 { TEXT("Error"), "ERROR" },
51 { TEXT("Warning"), "WARNING" },
52 { TEXT("Note"), "NOTE" },
53 { TEXT("Info"), "INFO" },
54 { TEXT("Debug"), "DEBUG" },
55 { TEXT("Debug1"), "DEBUG1" },
56 { TEXT("Debug2"), "DEBUG2" }
57};
58static const int s_defaultDebug = 1; // WARNING
59static const int s_minTestDebug = 3; // INFO
60
61HINSTANCE s_instance = NULL;
62
63static CGlobalOptions* s_globalOptions = NULL;
64static CAdvancedOptions* s_advancedOptions = NULL;
65static CHotkeyOptions* s_hotkeyOptions = NULL;
66static CScreensLinks* s_screensLinks = NULL;
67static CInfo* s_info = NULL;
68
69static bool s_userConfig = true;
70static time_t s_configTime = 0;
71static CConfig s_lastConfig;
72
73static const TCHAR* s_mainClass = TEXT("GoSynergy");
74static const TCHAR* s_layoutClass = TEXT("SynergyLayout");
75
76enum SaveMode {
77 SAVE_QUITING,
78 SAVE_NORMAL,
79 SAVE_QUIET
80};
81
82//
83// program arguments
84//
85
86#define ARG CArgs::s_instance
87
88class CArgs {
89public:
90 CArgs() { s_instance = this; }
91 ~CArgs() { s_instance = NULL; }
92
93public:
94 static CArgs* s_instance;
95 CConfig m_config;
96 CStringList m_screens;
97};
98
99CArgs* CArgs::s_instance = NULL;
100
101
102static
103BOOL CALLBACK
104addDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
105
106static
107bool
108isClientChecked(HWND hwnd)
109{
110 HWND child = getItem(hwnd, IDC_MAIN_CLIENT_RADIO);
111 return isItemChecked(child);
112}
113
114static
115void
116enableMainWindowControls(HWND hwnd)
117{
118 bool client = isClientChecked(hwnd);
119 enableItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_LABEL, client);
120 enableItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT, client);
121 enableItem(hwnd, IDC_MAIN_SERVER_SCREENS_LABEL, !client);
122 enableItem(hwnd, IDC_MAIN_SCREENS, !client);
123 enableItem(hwnd, IDC_MAIN_OPTIONS, !client);
124 enableItem(hwnd, IDC_MAIN_HOTKEYS, !client);
125}
126
127static
128bool
129execApp(const char* app, const CString& cmdLine, PROCESS_INFORMATION* procInfo)
130{
131 // prepare startup info
132 STARTUPINFO startup;
133 startup.cb = sizeof(startup);
134 startup.lpReserved = NULL;
135 startup.lpDesktop = NULL;
136 startup.lpTitle = NULL;
137 startup.dwX = (DWORD)CW_USEDEFAULT;
138 startup.dwY = (DWORD)CW_USEDEFAULT;
139 startup.dwXSize = (DWORD)CW_USEDEFAULT;
140 startup.dwYSize = (DWORD)CW_USEDEFAULT;
141 startup.dwXCountChars = 0;
142 startup.dwYCountChars = 0;
143 startup.dwFillAttribute = 0;
144 startup.dwFlags = STARTF_FORCEONFEEDBACK;
145 startup.wShowWindow = SW_SHOWDEFAULT;
146 startup.cbReserved2 = 0;
147 startup.lpReserved2 = NULL;
148 startup.hStdInput = NULL;
149 startup.hStdOutput = NULL;
150 startup.hStdError = NULL;
151
152 // prepare path to app
153 CString appPath = getAppPath(app);
154
155 // put path to app in command line
156 CString commandLine = "\"";
157 commandLine += appPath;
158 commandLine += "\" ";
159 commandLine += cmdLine;
160
161 // start child
162 if (CreateProcess(NULL, (char*)commandLine.c_str(),
163 NULL,
164 NULL,
165 FALSE,
166 CREATE_DEFAULT_ERROR_MODE |
167 CREATE_NEW_PROCESS_GROUP |
168 NORMAL_PRIORITY_CLASS,
169 NULL,
170 NULL,
171 &startup,
172 procInfo) == 0) {
173 return false;
174 }
175 else {
176 return true;
177 }
178}
179
180static
181CString
182getCommandLine(HWND hwnd, bool testing, bool silent)
183{
184 CString cmdLine;
185
186 // add constant testing args
187 if (testing) {
188 cmdLine += " -z --no-restart --no-daemon";
189 }
190
191 // can't start as service on NT
192 else if (!CArchMiscWindows::isWindows95Family()) {
193 cmdLine += " --no-daemon";
194 }
195
196 // get the server name
197 CString server;
198 bool isClient = isClientChecked(hwnd);
199 if (isClient) {
200 // check server name
201 HWND child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT);
202 server = getWindowText(child);
203 if (!ARG->m_config.isValidScreenName(server)) {
204 if (!silent) {
205 showError(hwnd, CStringUtil::format(
206 getString(IDS_INVALID_SERVER_NAME).c_str(),
207 server.c_str()));
208 }
209 SetFocus(child);
210 return CString();
211 }
212
213 // compare server name to local host. a common error
214 // is to provide the client's name for the server. we
215 // don't bother to check the addresses though that'd be
216 // more accurate.
217 if (CStringUtil::CaselessCmp::equal(ARCH->getHostName(), server)) {
218 if (!silent) {
219 showError(hwnd, CStringUtil::format(
220 getString(IDS_SERVER_IS_CLIENT).c_str(),
221 server.c_str()));
222 }
223 SetFocus(child);
224 return CString();
225 }
226 }
227
228 // debug level. always include this.
229 if (true) {
230 HWND child = getItem(hwnd, IDC_MAIN_DEBUG);
231 int debug = (int)SendMessage(child, CB_GETCURSEL, 0, 0);
232
233 // if testing then we force the debug level to be no less than
234 // s_minTestDebug. what's the point of testing if you can't
235 // see the debugging info?
236 if (testing && debug < s_minTestDebug) {
237 debug = s_minTestDebug;
238 }
239
240 cmdLine += " --debug ";
241 cmdLine += s_debugName[debug][1];
242 }
243
244 // add advanced options
245 cmdLine += s_advancedOptions->getCommandLine(isClient, server);
246
247 return cmdLine;
248}
249
250static
251bool
252launchApp(HWND hwnd, bool testing, HANDLE* thread, DWORD* threadID)
253{
254 if (thread != NULL) {
255 *thread = NULL;
256 }
257 if (threadID != NULL) {
258 *threadID = 0;
259 }
260
261 // start daemon if it's installed and we're not testing
262 if (!testing && CAutoStart::startDaemon()) {
263 return true;
264 }
265
266 // decide if client or server
267 const bool isClient = isClientChecked(hwnd);
268 const char* app = isClient ? CLIENT_APP : SERVER_APP;
269
270 // prepare command line
271 CString cmdLine = getCommandLine(hwnd, testing, false);
272 if (cmdLine.empty()) {
273 return false;
274 }
275
276 // start child
277 PROCESS_INFORMATION procInfo;
278 if (!execApp(app, cmdLine, &procInfo)) {
279 showError(hwnd, CStringUtil::format(
280 getString(IDS_STARTUP_FAILED).c_str(),
281 getErrorString(GetLastError()).c_str()));
282 return false;
283 }
284
285 // don't need process handle
286 CloseHandle(procInfo.hProcess);
287
288 // save thread handle and thread ID if desired
289 if (thread != NULL) {
290 *thread = procInfo.hThread;
291 }
292 if (threadID != NULL) {
293 *threadID = procInfo.dwThreadId;
294 }
295
296 return true;
297}
298
299static
300BOOL CALLBACK
301waitDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
302{
303 // only one wait dialog at a time!
304 static CChildWaitInfo* info = NULL;
305
306 switch (message) {
307 case WM_INITDIALOG:
308 // save info pointer
309 info = reinterpret_cast<CChildWaitInfo*>(lParam);
310
311 // save hwnd
312 info->m_dialog = hwnd;
313
314 // signal ready
315 SetEvent(info->m_ready);
316
317 return TRUE;
318
319 case WM_COMMAND:
320 switch (LOWORD(wParam)) {
321 case IDCANCEL:
322 case IDOK:
323 // signal stop
324 SetEvent(info->m_stop);
325
326 // done
327 EndDialog(hwnd, 0);
328 return TRUE;
329 }
330 }
331
332 return FALSE;
333}
334
335static
336DWORD WINAPI
337waitForChildThread(LPVOID vinfo)
338{
339 CChildWaitInfo* info = reinterpret_cast<CChildWaitInfo*>(vinfo);
340
341 // wait for ready
342 WaitForSingleObject(info->m_ready, INFINITE);
343
344 // wait for thread to complete or stop event
345 HANDLE handles[2];
346 handles[0] = info->m_child;
347 handles[1] = info->m_stop;
348 DWORD n = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
349
350 // if stop was raised then terminate child and wait for it
351 if (n == WAIT_OBJECT_0 + 1) {
352 PostThreadMessage(info->m_childID, WM_QUIT, 0, 0);
353 WaitForSingleObject(info->m_child, INFINITE);
354 }
355
356 // otherwise post IDOK to dialog box
357 else {
358 PostMessage(info->m_dialog, WM_COMMAND, MAKEWPARAM(IDOK, 0), 0);
359 }
360
361 return 0;
362}
363
364static
365void
366waitForChild(HWND hwnd, HANDLE thread, DWORD threadID)
367{
368 // prepare info for child wait dialog and thread
369 CChildWaitInfo info;
370 info.m_dialog = NULL;
371 info.m_child = thread;
372 info.m_childID = threadID;
373 info.m_ready = CreateEvent(NULL, TRUE, FALSE, NULL);
374 info.m_stop = CreateEvent(NULL, TRUE, FALSE, NULL);
375
376 // create a thread to wait on the child thread and event
377 DWORD id;
378 HANDLE waiter = CreateThread(NULL, 0, &waitForChildThread, &info,0, &id);
379
380 // do dialog that let's the user terminate the test
381 DialogBoxParam(s_instance, MAKEINTRESOURCE(IDD_WAIT), hwnd,
382 waitDlgProc, (LPARAM)&info);
383
384 // force the waiter thread to finish and wait for it
385 SetEvent(info.m_ready);
386 SetEvent(info.m_stop);
387 WaitForSingleObject(waiter, INFINITE);
388
389 // clean up
390 CloseHandle(waiter);
391 CloseHandle(info.m_ready);
392 CloseHandle(info.m_stop);
393}
394
395static
396void
397initMainWindow(HWND hwnd)
398{
399 // append version number to title
400 CString titleFormat = getString(IDS_TITLE);
401 setWindowText(hwnd, CStringUtil::format(titleFormat.c_str(), VERSION));
402
403 // load configuration
404 bool configLoaded =
405 loadConfig(ARG->m_config, s_configTime, s_userConfig);
406 if (configLoaded) {
407 s_lastConfig = ARG->m_config;
408 }
409
410 // get settings from registry
411 bool isServer = configLoaded;
412 int debugLevel = s_defaultDebug;
413 CString server;
414 HKEY key = CArchMiscWindows::openKey(HKEY_CURRENT_USER, getSettingsPath());
415 if (key != NULL) {
416 if (isServer && CArchMiscWindows::hasValue(key, "isServer")) {
417 isServer = (CArchMiscWindows::readValueInt(key, "isServer") != 0);
418 }
419 if (CArchMiscWindows::hasValue(key, "debug")) {
420 debugLevel = static_cast<int>(
421 CArchMiscWindows::readValueInt(key, "debug"));
422 if (debugLevel < 0) {
423 debugLevel = 0;
424 }
425 else if (debugLevel > CLog::kDEBUG2) {
426 debugLevel = CLog::kDEBUG2;
427 }
428 }
429 server = CArchMiscWindows::readValueString(key, "server");
430 CArchMiscWindows::closeKey(key);
431 }
432
433 // choose client/server radio buttons
434 HWND child;
435 child = getItem(hwnd, IDC_MAIN_CLIENT_RADIO);
436 setItemChecked(child, !isServer);
437 child = getItem(hwnd, IDC_MAIN_SERVER_RADIO);
438 setItemChecked(child, isServer);
439
440 // set server name
441 child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT);
442 setWindowText(child, server);
443
444 // debug level
445 child = getItem(hwnd, IDC_MAIN_DEBUG);
446 for (unsigned int i = 0; i < sizeof(s_debugName) /
447 sizeof(s_debugName[0]); ++i) {
448 SendMessage(child, CB_ADDSTRING, 0, (LPARAM)s_debugName[i][0]);
449 }
450 SendMessage(child, CB_SETCURSEL, debugLevel, 0);
451
452 // update controls
453 enableMainWindowControls(hwnd);
454}
455
456static
457bool
458saveMainWindow(HWND hwnd, SaveMode mode, CString* cmdLineOut = NULL)
459{
460 DWORD errorID = 0;
461 CString arg;
462 CString cmdLine;
463
464 // save dialog state
465 bool isClient = isClientChecked(hwnd);
466 HKEY key = CArchMiscWindows::addKey(HKEY_CURRENT_USER, getSettingsPath());
467 if (key != NULL) {
468 HWND child;
469 child = getItem(hwnd, IDC_MAIN_CLIENT_SERVER_NAME_EDIT);
470 CArchMiscWindows::setValue(key, "server", getWindowText(child));
471 child = getItem(hwnd, IDC_MAIN_DEBUG);
472 CArchMiscWindows::setValue(key, "debug",
473 SendMessage(child, CB_GETCURSEL, 0, 0));
474 CArchMiscWindows::setValue(key, "isServer", isClient ? 0 : 1);
475 CArchMiscWindows::closeKey(key);
476 }
477
478 // save user's configuration
479 if (!s_userConfig || ARG->m_config != s_lastConfig) {
480 time_t t;
481 if (!saveConfig(ARG->m_config, false, t)) {
482 errorID = IDS_SAVE_FAILED;
483 arg = getErrorString(GetLastError());
484 goto failed;
485 }
486 if (s_userConfig) {
487 s_configTime = t;
488 s_lastConfig = ARG->m_config;
489 }
490 }
491
492 // save autostart configuration
493 if (CAutoStart::isDaemonInstalled()) {
494 if (s_userConfig || ARG->m_config != s_lastConfig) {
495 time_t t;
496 if (!saveConfig(ARG->m_config, true, t)) {
497 errorID = IDS_AUTOSTART_SAVE_FAILED;
498 arg = getErrorString(GetLastError());
499 goto failed;
500 }
501 if (!s_userConfig) {
502 s_configTime = t;
503 s_lastConfig = ARG->m_config;
504 }
505 }
506 }
507
508 // get autostart command
509 cmdLine = getCommandLine(hwnd, false, mode == SAVE_QUITING);
510 if (cmdLineOut != NULL) {
511 *cmdLineOut = cmdLine;
512 }
513 if (cmdLine.empty()) {
514 return (mode == SAVE_QUITING);
515 }
516
517 // save autostart command
518 if (CAutoStart::isDaemonInstalled()) {
519 try {
520 CAutoStart::reinstallDaemon(isClient, cmdLine);
521 CAutoStart::uninstallDaemons(!isClient);
522 }
523 catch (XArchDaemon& e) {
524 errorID = IDS_INSTALL_GENERIC_ERROR;
525 arg = e.what();
526 goto failed;
527 }
528 }
529
530 return true;
531
532failed:
533 CString errorMessage =
534 CStringUtil::format(getString(errorID).c_str(), arg.c_str());
535 if (mode == SAVE_QUITING) {
536 errorMessage += "\n";
537 errorMessage += getString(IDS_UNSAVED_DATA_REALLY_QUIT);
538 if (askVerify(hwnd, errorMessage)) {
539 return true;
540 }
541 }
542 else if (mode == SAVE_NORMAL) {
543 showError(hwnd, errorMessage);
544 }
545 return false;
546}
547
548static
549LRESULT CALLBACK
550mainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
551{
552 switch (message) {
553 case WM_ACTIVATE:
554 if (LOWORD(wParam) != WA_INACTIVE) {
555 // activated
556
557 // see if the configuration changed
558 if (isConfigNewer(s_configTime, s_userConfig)) {
559 CString message = getString(IDS_CONFIG_CHANGED);
560 if (askVerify(hwnd, message)) {
561 time_t configTime;
562 bool userConfig;
563 CConfig newConfig;
564 if (loadConfig(newConfig, configTime, userConfig) &&
565 userConfig == s_userConfig) {
566 ARG->m_config = newConfig;
567 s_lastConfig = ARG->m_config;
568 }
569 else {
570 message = getString(IDS_LOAD_FAILED);
571 showError(hwnd, message);
572 s_lastConfig = CConfig();
573 }
574 }
575 }
576 }
577 else {
578 // deactivated; write configuration
579 if (!isShowingDialog()) {
580 saveMainWindow(hwnd, SAVE_QUIET);
581 }
582 }
583 break;
584
585 case WM_COMMAND:
586 switch (LOWORD(wParam)) {
587 case IDCANCEL:
588 // save data
589 if (saveMainWindow(hwnd, SAVE_QUITING)) {
590 // quit
591 PostQuitMessage(0);
592 }
593 return 0;
594
595 case IDOK:
596 case IDC_MAIN_TEST: {
597 // note if testing
598 const bool testing = (LOWORD(wParam) == IDC_MAIN_TEST);
599
600 // save data
601 if (saveMainWindow(hwnd, SAVE_NORMAL)) {
602 // launch child app
603 DWORD threadID;
604 HANDLE thread;
605 if (!launchApp(hwnd, testing, &thread, &threadID)) {
606 return 0;
607 }
608
609 // handle child program
610 if (testing) {
611 // wait for process to stop, allowing the user to kill it
612 waitForChild(hwnd, thread, threadID);
613
614 // clean up
615 CloseHandle(thread);
616 }
617 else {
618 // don't need thread handle
619 if (thread != NULL) {
620 CloseHandle(thread);
621 }
622
623 // notify of success
624 askOkay(hwnd, getString(IDS_STARTED_TITLE),
625 getString(IDS_STARTED));
626
627 // quit
628 PostQuitMessage(0);
629 }
630 }
631 return 0;
632 }
633
634 case IDC_MAIN_AUTOSTART: {
635 CString cmdLine;
636 if (saveMainWindow(hwnd, SAVE_NORMAL, &cmdLine)) {
637 // run dialog
638 CAutoStart autoStart(hwnd, !isClientChecked(hwnd), cmdLine);
639 autoStart.doModal();
640 }
641 return 0;
642 }
643
644 case IDC_MAIN_CLIENT_RADIO:
645 case IDC_MAIN_SERVER_RADIO:
646 enableMainWindowControls(hwnd);
647 return 0;
648
649 case IDC_MAIN_SCREENS:
650 s_screensLinks->doModal();
651 break;
652
653 case IDC_MAIN_OPTIONS:
654 s_globalOptions->doModal();
655 break;
656
657 case IDC_MAIN_ADVANCED:
658 s_advancedOptions->doModal(isClientChecked(hwnd));
659 break;
660
661 case IDC_MAIN_HOTKEYS:
662 s_hotkeyOptions->doModal();
663 break;
664
665 case IDC_MAIN_INFO:
666 s_info->doModal();
667 break;
668 }
669
670 default:
671 break;
672 }
673 return DefDlgProc(hwnd, message, wParam, lParam);
674}
675
676int WINAPI
677WinMain(HINSTANCE instance, HINSTANCE, LPSTR cmdLine, int nCmdShow)
678{
679 CArch arch(instance);
680 CLOG;
681 CArgs args;
682
683 s_instance = instance;
684
685 // if "/uninstall" is on the command line then just stop and
686 // uninstall the service and quit. this is the only option
687 // but we ignore any others.
688 if (CString(cmdLine).find("/uninstall") != CString::npos) {
689 CAutoStart::uninstallDaemons(false);
690 CAutoStart::uninstallDaemons(true);
691 return 0;
692 }
693
694 // register main window (dialog) class
695 WNDCLASSEX classInfo;
696 classInfo.cbSize = sizeof(classInfo);
697 classInfo.style = CS_HREDRAW | CS_VREDRAW;
698 classInfo.lpfnWndProc = &mainWndProc;
699 classInfo.cbClsExtra = 0;
700 classInfo.cbWndExtra = DLGWINDOWEXTRA;
701 classInfo.hInstance = instance;
702 classInfo.hIcon = (HICON)LoadImage(instance,
703 MAKEINTRESOURCE(IDI_SYNERGY),
704 IMAGE_ICON,
705 32, 32, LR_SHARED);
706 classInfo.hCursor = LoadCursor(NULL, IDC_ARROW);
707 classInfo.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1);
708 classInfo.lpszMenuName = NULL;
709 classInfo.lpszClassName = s_mainClass;
710 classInfo.hIconSm = (HICON)LoadImage(instance,
711 MAKEINTRESOURCE(IDI_SYNERGY),
712 IMAGE_ICON,
713 16, 16, LR_SHARED);
714 RegisterClassEx(&classInfo);
715
716 // create main window
717 HWND mainWindow = CreateDialog(s_instance,
718 MAKEINTRESOURCE(IDD_MAIN), 0, NULL);
719
720 // prep windows
721 initMainWindow(mainWindow);
722 s_globalOptions = new CGlobalOptions(mainWindow, &ARG->m_config);
723 s_advancedOptions = new CAdvancedOptions(mainWindow, &ARG->m_config);
724 s_hotkeyOptions = new CHotkeyOptions(mainWindow, &ARG->m_config);
725 s_screensLinks = new CScreensLinks(mainWindow, &ARG->m_config);
726 s_info = new CInfo(mainWindow);
727
728 // show window
729 ShowWindow(mainWindow, nCmdShow);
730
731 // main loop
732 MSG msg;
733 bool done = false;
734 do {
735 switch (GetMessage(&msg, NULL, 0, 0)) {
736 case -1:
737 // error
738 break;
739
740 case 0:
741 // quit
742 done = true;
743 break;
744
745 default:
746 if (!IsDialogMessage(mainWindow, &msg)) {
747 TranslateMessage(&msg);
748 DispatchMessage(&msg);
749 }
750 break;
751 }
752 } while (!done);
753
754 return msg.wParam;
755}
Note: See TracBrowser for help on using the repository browser.