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

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

Two classes (CPMScreen and CPMKeyState) + the hook dll left (and debugging of course).

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