source: trunk/synergy/cmd/synergys/synergys.cpp@ 2751

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

synergy v1.3.1 sources (zip).

File size: 30.1 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 "CClientListener.h"
16#include "CClientProxy.h"
17#include "CConfig.h"
18#include "CPrimaryClient.h"
19#include "CServer.h"
20#include "CScreen.h"
21#include "ProtocolTypes.h"
22#include "Version.h"
23#include "XScreen.h"
24#include "CSocketMultiplexer.h"
25#include "CTCPSocketFactory.h"
26#include "XSocket.h"
27#include "CThread.h"
28#include "CEventQueue.h"
29#include "CFunctionEventJob.h"
30#include "CLog.h"
31#include "CString.h"
32#include "CStringUtil.h"
33#include "LogOutputters.h"
34#include "CArch.h"
35#include "XArch.h"
36#include "stdfstream.h"
37#include <cstring>
38
39#define DAEMON_RUNNING(running_)
40#if WINAPI_MSWINDOWS
41#include "CArchMiscWindows.h"
42#include "CMSWindowsScreen.h"
43#include "CMSWindowsUtil.h"
44#include "CMSWindowsServerTaskBarReceiver.h"
45#include "resource.h"
46#undef DAEMON_RUNNING
47#define DAEMON_RUNNING(running_) CArchMiscWindows::daemonRunning(running_)
48#elif WINAPI_XWINDOWS
49#include "CXWindowsScreen.h"
50#include "CXWindowsServerTaskBarReceiver.h"
51#elif WINAPI_CARBON
52#include "COSXScreen.h"
53#include "COSXServerTaskBarReceiver.h"
54#endif
55
56// platform dependent name of a daemon
57#if SYSAPI_WIN32
58#define DAEMON_NAME "Synergy Server"
59#elif SYSAPI_UNIX
60#define DAEMON_NAME "synergys"
61#endif
62
63// configuration file name
64#if SYSAPI_WIN32
65#define USR_CONFIG_NAME "synergy.sgc"
66#define SYS_CONFIG_NAME "synergy.sgc"
67#elif SYSAPI_UNIX
68#define USR_CONFIG_NAME ".synergy.conf"
69#define SYS_CONFIG_NAME "synergy.conf"
70#endif
71
72typedef int (*StartupFunc)(int, char**);
73static void parse(int argc, const char* const* argv);
74static bool loadConfig(const CString& pathname);
75static void loadConfig();
76
77//
78// program arguments
79//
80
81#define ARG CArgs::s_instance
82
83class CArgs {
84public:
85 CArgs() :
86 m_pname(NULL),
87 m_backend(false),
88 m_restartable(true),
89 m_daemon(true),
90 m_configFile(),
91 m_logFilter(NULL),
92 m_display(NULL),
93 m_synergyAddress(NULL),
94 m_config(NULL)
95 { s_instance = this; }
96 ~CArgs() { s_instance = NULL; }
97
98public:
99 static CArgs* s_instance;
100 const char* m_pname;
101 bool m_backend;
102 bool m_restartable;
103 bool m_daemon;
104 CString m_configFile;
105 const char* m_logFilter;
106 const char* m_display;
107 CString m_name;
108 CNetworkAddress* m_synergyAddress;
109 CConfig* m_config;
110};
111
112CArgs* CArgs::s_instance = NULL;
113
114
115//
116// platform dependent factories
117//
118
119static
120CScreen*
121createScreen()
122{
123#if WINAPI_MSWINDOWS
124 return new CScreen(new CMSWindowsScreen(true));
125#elif WINAPI_XWINDOWS
126 return new CScreen(new CXWindowsScreen(ARG->m_display, true));
127#elif WINAPI_CARBON
128 return new CScreen(new COSXScreen(true));
129#endif
130}
131
132static
133CServerTaskBarReceiver*
134createTaskBarReceiver(const CBufferedLogOutputter* logBuffer)
135{
136#if WINAPI_MSWINDOWS
137 return new CMSWindowsServerTaskBarReceiver(
138 CMSWindowsScreen::getInstance(), logBuffer);
139#elif WINAPI_XWINDOWS
140 return new CXWindowsServerTaskBarReceiver(logBuffer);
141#elif WINAPI_CARBON
142 return new COSXServerTaskBarReceiver(logBuffer);
143#endif
144}
145
146
147//
148// platform independent main
149//
150
151enum EServerState {
152 kUninitialized,
153 kInitializing,
154 kInitializingToStart,
155 kInitialized,
156 kStarting,
157 kStarted
158};
159
160static EServerState s_serverState = kUninitialized;
161static CServer* s_server = NULL;
162static CScreen* s_serverScreen = NULL;
163static CPrimaryClient* s_primaryClient = NULL;
164static CClientListener* s_listener = NULL;
165static CServerTaskBarReceiver* s_taskBarReceiver = NULL;
166static CEvent::Type s_reloadConfigEvent = CEvent::kUnknown;
167static CEvent::Type s_forceReconnectEvent = CEvent::kUnknown;
168static bool s_suspended = false;
169static CEventQueueTimer* s_timer = NULL;
170
171CEvent::Type
172getReloadConfigEvent()
173{
174 return CEvent::registerTypeOnce(s_reloadConfigEvent, "reloadConfig");
175}
176
177CEvent::Type
178getForceReconnectEvent()
179{
180 return CEvent::registerTypeOnce(s_forceReconnectEvent, "forceReconnect");
181}
182
183static
184void
185updateStatus()
186{
187 s_taskBarReceiver->updateStatus(s_server, "");
188}
189
190static
191void
192updateStatus(const CString& msg)
193{
194 s_taskBarReceiver->updateStatus(s_server, msg);
195}
196
197static
198void
199handleClientConnected(const CEvent&, void* vlistener)
200{
201 CClientListener* listener = reinterpret_cast<CClientListener*>(vlistener);
202 CClientProxy* client = listener->getNextClient();
203 if (client != NULL) {
204 s_server->adoptClient(client);
205 updateStatus();
206 }
207}
208
209static
210CClientListener*
211openClientListener(const CNetworkAddress& address)
212{
213 CClientListener* listen =
214 new CClientListener(address, new CTCPSocketFactory, NULL);
215 EVENTQUEUE->adoptHandler(CClientListener::getConnectedEvent(), listen,
216 new CFunctionEventJob(
217 &handleClientConnected, listen));
218 return listen;
219}
220
221static
222void
223closeClientListener(CClientListener* listen)
224{
225 if (listen != NULL) {
226 EVENTQUEUE->removeHandler(CClientListener::getConnectedEvent(), listen);
227 delete listen;
228 }
229}
230
231static
232void
233handleScreenError(const CEvent&, void*)
234{
235 LOG((CLOG_CRIT "error on screen"));
236 EVENTQUEUE->addEvent(CEvent(CEvent::kQuit));
237}
238
239
240static void handleSuspend(const CEvent& event, void*);
241static void handleResume(const CEvent& event, void*);
242
243static
244CScreen*
245openServerScreen()
246{
247 CScreen* screen = createScreen();
248 EVENTQUEUE->adoptHandler(IScreen::getErrorEvent(),
249 screen->getEventTarget(),
250 new CFunctionEventJob(
251 &handleScreenError));
252 EVENTQUEUE->adoptHandler(IScreen::getSuspendEvent(),
253 screen->getEventTarget(),
254 new CFunctionEventJob(
255 &handleSuspend));
256 EVENTQUEUE->adoptHandler(IScreen::getResumeEvent(),
257 screen->getEventTarget(),
258 new CFunctionEventJob(
259 &handleResume));
260 return screen;
261}
262
263static
264void
265closeServerScreen(CScreen* screen)
266{
267 if (screen != NULL) {
268 EVENTQUEUE->removeHandler(IScreen::getErrorEvent(),
269 screen->getEventTarget());
270 EVENTQUEUE->removeHandler(IScreen::getSuspendEvent(),
271 screen->getEventTarget());
272 EVENTQUEUE->removeHandler(IScreen::getResumeEvent(),
273 screen->getEventTarget());
274 delete screen;
275 }
276}
277
278static
279CPrimaryClient*
280openPrimaryClient(const CString& name, CScreen* screen)
281{
282 LOG((CLOG_DEBUG1 "creating primary screen"));
283 return new CPrimaryClient(name, screen);
284}
285
286static
287void
288closePrimaryClient(CPrimaryClient* primaryClient)
289{
290 delete primaryClient;
291}
292
293static
294void
295handleNoClients(const CEvent&, void*)
296{
297 updateStatus();
298}
299
300static
301void
302handleClientsDisconnected(const CEvent&, void*)
303{
304 EVENTQUEUE->addEvent(CEvent(CEvent::kQuit));
305}
306
307static
308CServer*
309openServer(const CConfig& config, CPrimaryClient* primaryClient)
310{
311 CServer* server = new CServer(config, primaryClient);
312 EVENTQUEUE->adoptHandler(CServer::getDisconnectedEvent(), server,
313 new CFunctionEventJob(handleNoClients));
314 return server;
315}
316
317static
318void
319closeServer(CServer* server)
320{
321 if (server == NULL) {
322 return;
323 }
324
325 // tell all clients to disconnect
326 server->disconnect();
327
328 // wait for clients to disconnect for up to timeout seconds
329 double timeout = 3.0;
330 CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(timeout, NULL);
331 EVENTQUEUE->adoptHandler(CEvent::kTimer, timer,
332 new CFunctionEventJob(handleClientsDisconnected));
333 EVENTQUEUE->adoptHandler(CServer::getDisconnectedEvent(), server,
334 new CFunctionEventJob(handleClientsDisconnected));
335 CEvent event;
336 EVENTQUEUE->getEvent(event);
337 while (event.getType() != CEvent::kQuit) {
338 EVENTQUEUE->dispatchEvent(event);
339 CEvent::deleteData(event);
340 EVENTQUEUE->getEvent(event);
341 }
342 EVENTQUEUE->removeHandler(CEvent::kTimer, timer);
343 EVENTQUEUE->deleteTimer(timer);
344 EVENTQUEUE->removeHandler(CServer::getDisconnectedEvent(), server);
345
346 // done with server
347 delete server;
348}
349
350static bool initServer();
351static bool startServer();
352
353static
354void
355stopRetryTimer()
356{
357 if (s_timer != NULL) {
358 EVENTQUEUE->deleteTimer(s_timer);
359 EVENTQUEUE->removeHandler(CEvent::kTimer, NULL);
360 s_timer = NULL;
361 }
362}
363
364static
365void
366retryHandler(const CEvent&, void*)
367{
368 // discard old timer
369 assert(s_timer != NULL);
370 stopRetryTimer();
371
372 // try initializing/starting the server again
373 switch (s_serverState) {
374 case kUninitialized:
375 case kInitialized:
376 case kStarted:
377 assert(0 && "bad internal server state");
378 break;
379
380 case kInitializing:
381 LOG((CLOG_DEBUG1 "retry server initialization"));
382 s_serverState = kUninitialized;
383 if (!initServer()) {
384 EVENTQUEUE->addEvent(CEvent(CEvent::kQuit));
385 }
386 break;
387
388 case kInitializingToStart:
389 LOG((CLOG_DEBUG1 "retry server initialization"));
390 s_serverState = kUninitialized;
391 if (!initServer()) {
392 EVENTQUEUE->addEvent(CEvent(CEvent::kQuit));
393 }
394 else if (s_serverState == kInitialized) {
395 LOG((CLOG_DEBUG1 "starting server"));
396 if (!startServer()) {
397 EVENTQUEUE->addEvent(CEvent(CEvent::kQuit));
398 }
399 }
400 break;
401
402 case kStarting:
403 LOG((CLOG_DEBUG1 "retry starting server"));
404 s_serverState = kInitialized;
405 if (!startServer()) {
406 EVENTQUEUE->addEvent(CEvent(CEvent::kQuit));
407 }
408 break;
409 }
410}
411
412static
413bool
414initServer()
415{
416 // skip if already initialized or initializing
417 if (s_serverState != kUninitialized) {
418 return true;
419 }
420
421 double retryTime;
422 CScreen* serverScreen = NULL;
423 CPrimaryClient* primaryClient = NULL;
424 try {
425 CString name = ARG->m_config->getCanonicalName(ARG->m_name);
426 serverScreen = openServerScreen();
427 primaryClient = openPrimaryClient(name, serverScreen);
428 s_serverScreen = serverScreen;
429 s_primaryClient = primaryClient;
430 s_serverState = kInitialized;
431 updateStatus();
432 return true;
433 }
434 catch (XScreenUnavailable& e) {
435 LOG((CLOG_WARN "cannot open primary screen: %s", e.what()));
436 closePrimaryClient(primaryClient);
437 closeServerScreen(serverScreen);
438 updateStatus(CString("cannot open primary screen: ") + e.what());
439 retryTime = e.getRetryTime();
440 }
441 catch (XScreenOpenFailure& e) {
442 LOG((CLOG_CRIT "cannot open primary screen: %s", e.what()));
443 closePrimaryClient(primaryClient);
444 closeServerScreen(serverScreen);
445 return false;
446 }
447 catch (XBase& e) {
448 LOG((CLOG_CRIT "failed to start server: %s", e.what()));
449 closePrimaryClient(primaryClient);
450 closeServerScreen(serverScreen);
451 return false;
452 }
453
454 if (ARG->m_restartable) {
455 // install a timer and handler to retry later
456 assert(s_timer == NULL);
457 LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime));
458 s_timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL);
459 EVENTQUEUE->adoptHandler(CEvent::kTimer, s_timer,
460 new CFunctionEventJob(&retryHandler, NULL));
461 s_serverState = kInitializing;
462 return true;
463 }
464 else {
465 // don't try again
466 return false;
467 }
468}
469
470static
471bool
472startServer()
473{
474 // skip if already started or starting
475 if (s_serverState == kStarting || s_serverState == kStarted) {
476 return true;
477 }
478
479 // initialize if necessary
480 if (s_serverState != kInitialized) {
481 if (!initServer()) {
482 // hard initialization failure
483 return false;
484 }
485 if (s_serverState == kInitializing) {
486 // not ready to start
487 s_serverState = kInitializingToStart;
488 return true;
489 }
490 assert(s_serverState == kInitialized);
491 }
492
493 double retryTime;
494 CClientListener* listener = NULL;
495 try {
496 listener = openClientListener(ARG->m_config->getSynergyAddress());
497 s_server = openServer(*ARG->m_config, s_primaryClient);
498 s_listener = listener;
499 updateStatus();
500 LOG((CLOG_NOTE "started server"));
501 s_serverState = kStarted;
502 return true;
503 }
504 catch (XSocketAddressInUse& e) {
505 LOG((CLOG_WARN "cannot listen for clients: %s", e.what()));
506 closeClientListener(listener);
507 updateStatus(CString("cannot listen for clients: ") + e.what());
508 retryTime = 10.0;
509 }
510 catch (XBase& e) {
511 LOG((CLOG_CRIT "failed to start server: %s", e.what()));
512 closeClientListener(listener);
513 return false;
514 }
515
516 if (ARG->m_restartable) {
517 // install a timer and handler to retry later
518 assert(s_timer == NULL);
519 LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime));
520 s_timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL);
521 EVENTQUEUE->adoptHandler(CEvent::kTimer, s_timer,
522 new CFunctionEventJob(&retryHandler, NULL));
523 s_serverState = kStarting;
524 return true;
525 }
526 else {
527 // don't try again
528 return false;
529 }
530}
531
532static
533void
534stopServer()
535{
536 if (s_serverState == kStarted) {
537 closeClientListener(s_listener);
538 closeServer(s_server);
539 s_server = NULL;
540 s_listener = NULL;
541 s_serverState = kInitialized;
542 }
543 else if (s_serverState == kStarting) {
544 stopRetryTimer();
545 s_serverState = kInitialized;
546 }
547 assert(s_server == NULL);
548 assert(s_listener == NULL);
549}
550
551static
552void
553cleanupServer()
554{
555 stopServer();
556 if (s_serverState == kInitialized) {
557 closePrimaryClient(s_primaryClient);
558 closeServerScreen(s_serverScreen);
559 s_primaryClient = NULL;
560 s_serverScreen = NULL;
561 s_serverState = kUninitialized;
562 }
563 else if (s_serverState == kInitializing ||
564 s_serverState == kInitializingToStart) {
565 stopRetryTimer();
566 s_serverState = kUninitialized;
567 }
568 assert(s_primaryClient == NULL);
569 assert(s_serverScreen == NULL);
570 assert(s_serverState == kUninitialized);
571}
572
573static
574void
575handleSuspend(const CEvent&, void*)
576{
577 if (!s_suspended) {
578 LOG((CLOG_INFO "suspend"));
579 stopServer();
580 s_suspended = true;
581 }
582}
583
584static
585void
586handleResume(const CEvent&, void*)
587{
588 if (s_suspended) {
589 LOG((CLOG_INFO "resume"));
590 startServer();
591 s_suspended = false;
592 }
593}
594
595static
596void
597reloadSignalHandler(CArch::ESignal, void*)
598{
599 EVENTQUEUE->addEvent(CEvent(getReloadConfigEvent(),
600 IEventQueue::getSystemTarget()));
601}
602
603static
604void
605reloadConfig(const CEvent&, void*)
606{
607 LOG((CLOG_DEBUG "reload configuration"));
608 if (loadConfig(ARG->m_configFile)) {
609 if (s_server != NULL) {
610 s_server->setConfig(*ARG->m_config);
611 }
612 LOG((CLOG_NOTE "reloaded configuration"));
613 }
614}
615
616static
617void
618forceReconnect(const CEvent&, void*)
619{
620 if (s_server != NULL) {
621 s_server->disconnect();
622 }
623}
624
625static
626int
627mainLoop()
628{
629 // create socket multiplexer. this must happen after daemonization
630 // on unix because threads evaporate across a fork().
631 CSocketMultiplexer multiplexer;
632
633 // create the event queue
634 CEventQueue eventQueue;
635
636 // if configuration has no screens then add this system
637 // as the default
638 if (ARG->m_config->begin() == ARG->m_config->end()) {
639 ARG->m_config->addScreen(ARG->m_name);
640 }
641
642 // set the contact address, if provided, in the config.
643 // otherwise, if the config doesn't have an address, use
644 // the default.
645 if (ARG->m_synergyAddress->isValid()) {
646 ARG->m_config->setSynergyAddress(*ARG->m_synergyAddress);
647 }
648 else if (!ARG->m_config->getSynergyAddress().isValid()) {
649 ARG->m_config->setSynergyAddress(CNetworkAddress(kDefaultPort));
650 }
651
652 // canonicalize the primary screen name
653 CString primaryName = ARG->m_config->getCanonicalName(ARG->m_name);
654 if (primaryName.empty()) {
655 LOG((CLOG_CRIT "unknown screen name `%s'", ARG->m_name.c_str()));
656 return kExitFailed;
657 }
658
659 // start the server. if this return false then we've failed and
660 // we shouldn't retry.
661 LOG((CLOG_DEBUG1 "starting server"));
662 if (!startServer()) {
663 return kExitFailed;
664 }
665
666 // handle hangup signal by reloading the server's configuration
667 ARCH->setSignalHandler(CArch::kHANGUP, &reloadSignalHandler, NULL);
668 EVENTQUEUE->adoptHandler(getReloadConfigEvent(),
669 IEventQueue::getSystemTarget(),
670 new CFunctionEventJob(&reloadConfig));
671
672 // handle force reconnect event by disconnecting clients. they'll
673 // reconnect automatically.
674 EVENTQUEUE->adoptHandler(getForceReconnectEvent(),
675 IEventQueue::getSystemTarget(),
676 new CFunctionEventJob(&forceReconnect));
677
678 // run event loop. if startServer() failed we're supposed to retry
679 // later. the timer installed by startServer() will take care of
680 // that.
681 CEvent event;
682 DAEMON_RUNNING(true);
683 EVENTQUEUE->getEvent(event);
684 while (event.getType() != CEvent::kQuit) {
685 EVENTQUEUE->dispatchEvent(event);
686 CEvent::deleteData(event);
687 EVENTQUEUE->getEvent(event);
688 }
689 DAEMON_RUNNING(false);
690
691 // close down
692 LOG((CLOG_DEBUG1 "stopping server"));
693 EVENTQUEUE->removeHandler(getForceReconnectEvent(),
694 IEventQueue::getSystemTarget());
695 EVENTQUEUE->removeHandler(getReloadConfigEvent(),
696 IEventQueue::getSystemTarget());
697 cleanupServer();
698 updateStatus();
699 LOG((CLOG_NOTE "stopped server"));
700
701 return kExitSuccess;
702}
703
704static
705int
706daemonMainLoop(int, const char**)
707{
708#if SYSAPI_WIN32
709 CSystemLogger sysLogger(DAEMON_NAME, false);
710#else
711 CSystemLogger sysLogger(DAEMON_NAME, true);
712#endif
713 return mainLoop();
714}
715
716static
717int
718standardStartup(int argc, char** argv)
719{
720 if (!ARG->m_daemon) {
721 ARCH->showConsole(false);
722 }
723
724 // parse command line
725 parse(argc, argv);
726
727 // load configuration
728 loadConfig();
729
730 // daemonize if requested
731 if (ARG->m_daemon) {
732 return ARCH->daemonize(DAEMON_NAME, &daemonMainLoop);
733 }
734 else {
735 return mainLoop();
736 }
737}
738
739static
740int
741run(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup)
742{
743 // general initialization
744 ARG->m_synergyAddress = new CNetworkAddress;
745 ARG->m_config = new CConfig;
746 ARG->m_pname = ARCH->getBasename(argv[0]);
747
748 // install caller's output filter
749 if (outputter != NULL) {
750 CLOG->insert(outputter);
751 }
752
753 // save log messages
754 CBufferedLogOutputter logBuffer(1000);
755 CLOG->insert(&logBuffer, true);
756
757 // make the task bar receiver. the user can control this app
758 // through the task bar.
759 s_taskBarReceiver = createTaskBarReceiver(&logBuffer);
760
761 // run
762 int result = startup(argc, argv);
763
764 // done with task bar receiver
765 delete s_taskBarReceiver;
766
767 // done with log buffer
768 CLOG->remove(&logBuffer);
769
770 delete ARG->m_config;
771 delete ARG->m_synergyAddress;
772 return result;
773}
774
775
776//
777// command line parsing
778//
779
780#define BYE "\nTry `%s --help' for more information."
781
782static void (*bye)(int) = &exit;
783
784static
785void
786version()
787{
788 LOG((CLOG_PRINT
789"%s %s, protocol version %d.%d\n"
790"%s",
791 ARG->m_pname,
792 kVersion,
793 kProtocolMajorVersion,
794 kProtocolMinorVersion,
795 kCopyright));
796}
797
798static
799void
800help()
801{
802#if WINAPI_XWINDOWS
803# define USAGE_DISPLAY_ARG \
804" [--display <display>]"
805# define USAGE_DISPLAY_INFO \
806" --display <display> connect to the X server at <display>\n"
807#else
808# define USAGE_DISPLAY_ARG
809# define USAGE_DISPLAY_INFO
810#endif
811
812#if SYSAPI_WIN32
813
814# define PLATFORM_ARGS \
815" [--daemon|--no-daemon]"
816# define PLATFORM_DESC
817# define PLATFORM_EXTRA \
818"At least one command line argument is required. If you don't otherwise\n" \
819"need an argument use `--daemon'.\n" \
820"\n"
821
822#else
823
824# define PLATFORM_ARGS \
825" [--daemon|--no-daemon]"
826# define PLATFORM_DESC
827# define PLATFORM_EXTRA
828
829#endif
830
831 LOG((CLOG_PRINT
832"Usage: %s"
833" [--address <address>]"
834" [--config <pathname>]"
835" [--debug <level>]"
836USAGE_DISPLAY_ARG
837" [--name <screen-name>]"
838" [--restart|--no-restart]"
839PLATFORM_ARGS
840"\n\n"
841"Start the synergy mouse/keyboard sharing server.\n"
842"\n"
843" -a, --address <address> listen for clients on the given address.\n"
844" -c, --config <pathname> use the named configuration file instead.\n"
845" -d, --debug <level> filter out log messages with priorty below level.\n"
846" level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n"
847" DEBUG, DEBUG1, DEBUG2.\n"
848USAGE_DISPLAY_INFO
849" -f, --no-daemon run the server in the foreground.\n"
850"* --daemon run the server as a daemon.\n"
851" -n, --name <screen-name> use screen-name instead the hostname to identify\n"
852" this screen in the configuration.\n"
853" -1, --no-restart do not try to restart the server if it fails for\n"
854" some reason.\n"
855"* --restart restart the server automatically if it fails.\n"
856PLATFORM_DESC
857" -h, --help display this help and exit.\n"
858" --version display version information and exit.\n"
859"\n"
860"* marks defaults.\n"
861"\n"
862PLATFORM_EXTRA
863"The argument for --address is of the form: [<hostname>][:<port>]. The\n"
864"hostname must be the address or hostname of an interface on the system.\n"
865"The default is to listen on all interfaces. The port overrides the\n"
866"default port, %d.\n"
867"\n"
868"If no configuration file pathname is provided then the first of the\n"
869"following to load successfully sets the configuration:\n"
870" %s\n"
871" %s\n"
872"If no configuration file can be loaded then the configuration uses its\n"
873"defaults with just the server screen.\n"
874"\n"
875"Where log messages go depends on the platform and whether or not the\n"
876"server is running as a daemon.",
877 ARG->m_pname,
878 kDefaultPort,
879 ARCH->concatPath(
880 ARCH->getUserDirectory(),
881 USR_CONFIG_NAME).c_str(),
882 ARCH->concatPath(
883 ARCH->getSystemDirectory(),
884 SYS_CONFIG_NAME).c_str()));
885}
886
887static
888bool
889isArg(int argi, int argc, const char* const* argv,
890 const char* name1, const char* name2,
891 int minRequiredParameters = 0)
892{
893 if ((name1 != NULL && strcmp(argv[argi], name1) == 0) ||
894 (name2 != NULL && strcmp(argv[argi], name2) == 0)) {
895 // match. check args left.
896 if (argi + minRequiredParameters >= argc) {
897 LOG((CLOG_PRINT "%s: missing arguments for `%s'" BYE,
898 ARG->m_pname, argv[argi], ARG->m_pname));
899 bye(kExitArgs);
900 }
901 return true;
902 }
903
904 // no match
905 return false;
906}
907
908static
909void
910parse(int argc, const char* const* argv)
911{
912 assert(ARG->m_pname != NULL);
913 assert(argv != NULL);
914 assert(argc >= 1);
915
916 // set defaults
917 ARG->m_name = ARCH->getHostName();
918
919 // parse options
920 int i = 1;
921 for (; i < argc; ++i) {
922 if (isArg(i, argc, argv, "-d", "--debug", 1)) {
923 // change logging level
924 ARG->m_logFilter = argv[++i];
925 }
926
927 else if (isArg(i, argc, argv, "-a", "--address", 1)) {
928 // save listen address
929 try {
930 *ARG->m_synergyAddress = CNetworkAddress(argv[i + 1],
931 kDefaultPort);
932 ARG->m_synergyAddress->resolve();
933 }
934 catch (XSocketAddress& e) {
935 LOG((CLOG_PRINT "%s: %s" BYE,
936 ARG->m_pname, e.what(), ARG->m_pname));
937 bye(kExitArgs);
938 }
939 ++i;
940 }
941
942 else if (isArg(i, argc, argv, "-n", "--name", 1)) {
943 // save screen name
944 ARG->m_name = argv[++i];
945 }
946
947 else if (isArg(i, argc, argv, "-c", "--config", 1)) {
948 // save configuration file path
949 ARG->m_configFile = argv[++i];
950 }
951
952#if WINAPI_XWINDOWS
953 else if (isArg(i, argc, argv, "-display", "--display", 1)) {
954 // use alternative display
955 ARG->m_display = argv[++i];
956 }
957#endif
958
959 else if (isArg(i, argc, argv, "-f", "--no-daemon")) {
960 // not a daemon
961 ARG->m_daemon = false;
962 }
963
964 else if (isArg(i, argc, argv, NULL, "--daemon")) {
965 // daemonize
966 ARG->m_daemon = true;
967 }
968
969 else if (isArg(i, argc, argv, "-1", "--no-restart")) {
970 // don't try to restart
971 ARG->m_restartable = false;
972 }
973
974 else if (isArg(i, argc, argv, NULL, "--restart")) {
975 // try to restart
976 ARG->m_restartable = true;
977 }
978
979 else if (isArg(i, argc, argv, "-z", NULL)) {
980 ARG->m_backend = true;
981 }
982
983 else if (isArg(i, argc, argv, "-h", "--help")) {
984 help();
985 bye(kExitSuccess);
986 }
987
988 else if (isArg(i, argc, argv, NULL, "--version")) {
989 version();
990 bye(kExitSuccess);
991 }
992
993 else if (isArg(i, argc, argv, "--", NULL)) {
994 // remaining arguments are not options
995 ++i;
996 break;
997 }
998
999 else if (argv[i][0] == '-') {
1000 LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE,
1001 ARG->m_pname, argv[i], ARG->m_pname));
1002 bye(kExitArgs);
1003 }
1004
1005 else {
1006 // this and remaining arguments are not options
1007 break;
1008 }
1009 }
1010
1011 // no non-option arguments are allowed
1012 if (i != argc) {
1013 LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE,
1014 ARG->m_pname, argv[i], ARG->m_pname));
1015 bye(kExitArgs);
1016 }
1017
1018 // increase default filter level for daemon. the user must
1019 // explicitly request another level for a daemon.
1020 if (ARG->m_daemon && ARG->m_logFilter == NULL) {
1021#if SYSAPI_WIN32
1022 if (CArchMiscWindows::isWindows95Family()) {
1023 // windows 95 has no place for logging so avoid showing
1024 // the log console window.
1025 ARG->m_logFilter = "FATAL";
1026 }
1027 else
1028#endif
1029 {
1030 ARG->m_logFilter = "NOTE";
1031 }
1032 }
1033
1034 // set log filter
1035 if (!CLOG->setFilter(ARG->m_logFilter)) {
1036 LOG((CLOG_PRINT "%s: unrecognized log level `%s'" BYE,
1037 ARG->m_pname, ARG->m_logFilter, ARG->m_pname));
1038 bye(kExitArgs);
1039 }
1040
1041 // identify system
1042 LOG((CLOG_INFO "Synergy server %s on %s", kVersion, ARCH->getOSName().c_str()));
1043}
1044
1045static
1046bool
1047loadConfig(const CString& pathname)
1048{
1049 try {
1050 // load configuration
1051 LOG((CLOG_DEBUG "opening configuration \"%s\"", pathname.c_str()));
1052 std::ifstream configStream(pathname.c_str());
1053 if (!configStream.is_open()) {
1054 // report failure to open configuration as a debug message
1055 // since we try several paths and we expect some to be
1056 // missing.
1057 LOG((CLOG_DEBUG "cannot open configuration \"%s\"",
1058 pathname.c_str()));
1059 return false;
1060 }
1061 configStream >> *ARG->m_config;
1062 LOG((CLOG_DEBUG "configuration read successfully"));
1063 return true;
1064 }
1065 catch (XConfigRead& e) {
1066 // report error in configuration file
1067 LOG((CLOG_ERR "cannot read configuration \"%s\": %s",
1068 pathname.c_str(), e.what()));
1069 }
1070 return false;
1071}
1072
1073static
1074void
1075loadConfig()
1076{
1077 bool loaded = false;
1078
1079 // load the config file, if specified
1080 if (!ARG->m_configFile.empty()) {
1081 loaded = loadConfig(ARG->m_configFile);
1082 }
1083
1084 // load the default configuration if no explicit file given
1085 else {
1086 // get the user's home directory
1087 CString path = ARCH->getUserDirectory();
1088 if (!path.empty()) {
1089 // complete path
1090 path = ARCH->concatPath(path, USR_CONFIG_NAME);
1091
1092 // now try loading the user's configuration
1093 if (loadConfig(path)) {
1094 loaded = true;
1095 ARG->m_configFile = path;
1096 }
1097 }
1098 if (!loaded) {
1099 // try the system-wide config file
1100 path = ARCH->getSystemDirectory();
1101 if (!path.empty()) {
1102 path = ARCH->concatPath(path, SYS_CONFIG_NAME);
1103 if (loadConfig(path)) {
1104 loaded = true;
1105 ARG->m_configFile = path;
1106 }
1107 }
1108 }
1109 }
1110
1111 if (!loaded) {
1112 LOG((CLOG_PRINT "%s: no configuration available", ARG->m_pname));
1113 bye(kExitConfig);
1114 }
1115}
1116
1117
1118//
1119// platform dependent entry points
1120//
1121
1122#if SYSAPI_WIN32
1123
1124static bool s_hasImportantLogMessages = false;
1125
1126//
1127// CMessageBoxOutputter
1128//
1129// This class writes severe log messages to a message box
1130//
1131
1132class CMessageBoxOutputter : public ILogOutputter {
1133public:
1134 CMessageBoxOutputter() { }
1135 virtual ~CMessageBoxOutputter() { }
1136
1137 // ILogOutputter overrides
1138 virtual void open(const char*) { }
1139 virtual void close() { }
1140 virtual void show(bool) { }
1141 virtual bool write(ELevel level, const char* message);
1142 virtual const char* getNewline() const { return ""; }
1143};
1144
1145bool
1146CMessageBoxOutputter::write(ELevel level, const char* message)
1147{
1148 // note any important messages the user may need to know about
1149 if (level <= CLog::kWARNING) {
1150 s_hasImportantLogMessages = true;
1151 }
1152
1153 // FATAL and PRINT messages get a dialog box if not running as
1154 // backend. if we're running as a backend the user will have
1155 // a chance to see the messages when we exit.
1156 if (!ARG->m_backend && level <= CLog::kFATAL) {
1157 MessageBox(NULL, message, ARG->m_pname, MB_OK | MB_ICONWARNING);
1158 return false;
1159 }
1160 else {
1161 return true;
1162 }
1163}
1164
1165static
1166void
1167byeThrow(int x)
1168{
1169 CArchMiscWindows::daemonFailed(x);
1170}
1171
1172static
1173int
1174daemonNTMainLoop(int argc, const char** argv)
1175{
1176 parse(argc, argv);
1177 ARG->m_backend = false;
1178 loadConfig();
1179 return CArchMiscWindows::runDaemon(mainLoop);
1180}
1181
1182static
1183int
1184daemonNTStartup(int, char**)
1185{
1186 CSystemLogger sysLogger(DAEMON_NAME, false);
1187 bye = &byeThrow;
1188 return ARCH->daemonize(DAEMON_NAME, &daemonNTMainLoop);
1189}
1190
1191static
1192int
1193foregroundStartup(int argc, char** argv)
1194{
1195 ARCH->showConsole(false);
1196
1197 // parse command line
1198 parse(argc, argv);
1199
1200 // load configuration
1201 loadConfig();
1202
1203 // never daemonize
1204 return mainLoop();
1205}
1206
1207static
1208void
1209showError(HINSTANCE instance, const char* title, UINT id, const char* arg)
1210{
1211 CString fmt = CMSWindowsUtil::getString(instance, id);
1212 CString msg = CStringUtil::format(fmt.c_str(), arg);
1213 MessageBox(NULL, msg.c_str(), title, MB_OK | MB_ICONWARNING);
1214}
1215
1216int WINAPI
1217WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int)
1218{
1219 try {
1220 CArchMiscWindows::setIcons((HICON)LoadImage(instance,
1221 MAKEINTRESOURCE(IDI_SYNERGY),
1222 IMAGE_ICON,
1223 32, 32, LR_SHARED),
1224 (HICON)LoadImage(instance,
1225 MAKEINTRESOURCE(IDI_SYNERGY),
1226 IMAGE_ICON,
1227 16, 16, LR_SHARED));
1228 CArch arch(instance);
1229 CMSWindowsScreen::init(instance);
1230 CLOG;
1231 CThread::getCurrentThread().setPriority(-14);
1232 CArgs args;
1233
1234 // set title on log window
1235 ARCH->openConsole((CString(kAppVersion) + " " + "Server").c_str());
1236
1237 // windows NT family starts services using no command line options.
1238 // since i'm not sure how to tell the difference between that and
1239 // a user providing no options we'll assume that if there are no
1240 // arguments and we're on NT then we're being invoked as a service.
1241 // users on NT can use `--daemon' or `--no-daemon' to force us out
1242 // of the service code path.
1243 StartupFunc startup = &standardStartup;
1244 if (!CArchMiscWindows::isWindows95Family()) {
1245 if (__argc <= 1) {
1246 startup = &daemonNTStartup;
1247 }
1248 else {
1249 startup = &foregroundStartup;
1250 }
1251 }
1252
1253 // send PRINT and FATAL output to a message box
1254 int result = run(__argc, __argv, new CMessageBoxOutputter, startup);
1255
1256 // let user examine any messages if we're running as a backend
1257 // by putting up a dialog box before exiting.
1258 if (args.m_backend && s_hasImportantLogMessages) {
1259 showError(instance, args.m_pname, IDS_FAILED, "");
1260 }
1261
1262 delete CLOG;
1263 return result;
1264 }
1265 catch (XBase& e) {
1266 showError(instance, __argv[0], IDS_UNCAUGHT_EXCEPTION, e.what());
1267 throw;
1268 }
1269 catch (XArch& e) {
1270 showError(instance, __argv[0], IDS_INIT_FAILED, e.what().c_str());
1271 return kExitFailed;
1272 }
1273 catch (...) {
1274 showError(instance, __argv[0], IDS_UNCAUGHT_EXCEPTION, "<unknown>");
1275 throw;
1276 }
1277}
1278
1279#elif SYSAPI_UNIX
1280
1281int
1282main(int argc, char** argv)
1283{
1284 CArgs args;
1285 try {
1286 int result;
1287 CArch arch;
1288 CLOG;
1289 CArgs args;
1290 result = run(argc, argv, NULL, &standardStartup);
1291 delete CLOG;
1292 return result;
1293 }
1294 catch (XBase& e) {
1295 LOG((CLOG_CRIT "Uncaught exception: %s\n", e.what()));
1296 throw;
1297 }
1298 catch (XArch& e) {
1299 LOG((CLOG_CRIT "Initialization failed: %s" BYE, e.what().c_str()));
1300 return kExitFailed;
1301 }
1302 catch (...) {
1303 LOG((CLOG_CRIT "Uncaught exception: <unknown exception>\n"));
1304 throw;
1305 }
1306}
1307
1308#else
1309
1310#error no main() for platform
1311
1312#endif
Note: See TracBrowser for help on using the repository browser.