1 /*
2  * synergy -- mouse and keyboard sharing utility
3  * Copyright (C) 2012-2016 Symless Ltd.
4  * Copyright (C) 2002 Chris Schoeneman
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 #include "synergy/ClientApp.h"
20 
21 #include "client/Client.h"
22 #include "synergy/ArgParser.h"
23 #include "synergy/protocol_types.h"
24 #include "synergy/Screen.h"
25 #include "synergy/XScreen.h"
26 #include "synergy/ClientArgs.h"
27 #include "net/NetworkAddress.h"
28 #include "net/TCPSocketFactory.h"
29 #include "net/SocketMultiplexer.h"
30 #include "net/XSocket.h"
31 #include "mt/Thread.h"
32 #include "arch/IArchTaskBarReceiver.h"
33 #include "arch/Arch.h"
34 #include "base/String.h"
35 #include "base/Event.h"
36 #include "base/IEventQueue.h"
37 #include "base/TMethodEventJob.h"
38 #include "base/log_outputters.h"
39 #include "base/EventQueue.h"
40 #include "base/TMethodJob.h"
41 #include "base/Log.h"
42 #include "common/Version.h"
43 
44 #if SYSAPI_WIN32
45 #include "arch/win32/ArchMiscWindows.h"
46 #endif
47 
48 #if WINAPI_MSWINDOWS
49 #include "platform/MSWindowsScreen.h"
50 #elif WINAPI_XWINDOWS
51 #include "platform/XWindowsScreen.h"
52 #elif WINAPI_CARBON
53 #include "platform/OSXScreen.h"
54 #endif
55 
56 #if defined(__APPLE__)
57 #include "platform/OSXDragSimulator.h"
58 #endif
59 
60 #include <memory>
61 #include <iostream>
62 #include <stdio.h>
63 
64 #define RETRY_TIME 1.0
65 
ClientApp(IEventQueue * events,CreateTaskBarReceiverFunc createTaskBarReceiver)66 ClientApp::ClientApp(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver) :
67     App(events, createTaskBarReceiver, new lib::synergy::ClientArgs()),
68     m_client(NULL),
69     m_clientScreen(NULL),
70     m_serverAddress(NULL)
71 {
72 }
73 
~ClientApp()74 ClientApp::~ClientApp()
75 {
76 }
77 
78 void
parseArgs(int argc,const char * const * argv)79 ClientApp::parseArgs(int argc, const char* const* argv)
80 {
81     ArgParser argParser(this);
82     bool result = argParser.parseClientArgs(args(), argc, argv);
83 
84     if (!result || args().m_shouldExit) {
85         m_bye(kExitArgs);
86     }
87     else {
88         // save server address
89         if (!args().m_synergyAddress.empty()) {
90             try {
91                 *m_serverAddress = NetworkAddress(args().m_synergyAddress, kDefaultPort);
92                 m_serverAddress->resolve();
93             }
94             catch (XSocketAddress& e) {
95                 // allow an address that we can't look up if we're restartable.
96                 // we'll try to resolve the address each time we connect to the
97                 // server.  a bad port will never get better.  patch by Brent
98                 // Priddy.
99                 if (!args().m_restartable || e.getError() == XSocketAddress::kBadPort) {
100                     LOG((CLOG_PRINT "%s: %s" BYE,
101                         args().m_pname, e.what(), args().m_pname));
102                     m_bye(kExitFailed);
103                 }
104             }
105         }
106     }
107 }
108 
109 void
help()110 ClientApp::help()
111 {
112 #if WINAPI_XWINDOWS
113 #  define WINAPI_ARG \
114     " [--display <display>] [--no-xinitthreads]"
115 #  define WINAPI_INFO \
116     "      --display <display>  connect to the X server at <display>\n" \
117     "      --no-xinitthreads    do not call XInitThreads()\n"
118 #else
119 #  define WINAPI_ARG
120 #  define WINAPI_INFO
121 #endif
122     static const int buffer_size = 2000;
123     char buffer[buffer_size];
124     snprintf(
125         buffer,
126         buffer_size,
127         "Usage: %s"
128         " [--yscroll <delta>]"
129         WINAPI_ARG
130         HELP_SYS_ARGS
131         HELP_COMMON_ARGS
132         " <server-address>"
133         "\n\n"
134         "Connect to a synergy mouse/keyboard sharing server.\n"
135         "\n"
136         HELP_COMMON_INFO_1
137         WINAPI_INFO
138         HELP_SYS_INFO
139         "      --yscroll <delta>    defines the vertical scrolling delta, which is\n"
140         "                             120 by default.\n"
141         HELP_COMMON_INFO_2
142         "\n"
143         "* marks defaults.\n"
144         "\n"
145         "The server address is of the form: [<hostname>][:<port>].  The hostname\n"
146         "must be the address or hostname of the server.  The port overrides the\n"
147         "default port, %d.\n",
148         args().m_pname, kDefaultPort
149     );
150 
151     LOG((CLOG_PRINT "%s", buffer));
152 }
153 
154 const char*
daemonName() const155 ClientApp::daemonName() const
156 {
157 #if SYSAPI_WIN32
158     return "Synergy Client";
159 #elif SYSAPI_UNIX
160     return "synergyc";
161 #endif
162 }
163 
164 const char*
daemonInfo() const165 ClientApp::daemonInfo() const
166 {
167 #if SYSAPI_WIN32
168     return "Allows another computer to share it's keyboard and mouse with this computer.";
169 #elif SYSAPI_UNIX
170     return "";
171 #endif
172 }
173 
174 synergy::Screen*
createScreen()175 ClientApp::createScreen()
176 {
177 #if WINAPI_MSWINDOWS
178     return new synergy::Screen(new MSWindowsScreen(
179         false, args().m_noHooks, args().m_stopOnDeskSwitch, m_events), m_events);
180 #elif WINAPI_XWINDOWS
181     return new synergy::Screen(new XWindowsScreen(
182         args().m_display, false, args().m_disableXInitThreads,
183         args().m_yscroll, m_events), m_events);
184 #elif WINAPI_CARBON
185     return new synergy::Screen(new OSXScreen(m_events, false), m_events);
186 #endif
187 }
188 
189 void
updateStatus()190 ClientApp::updateStatus()
191 {
192     updateStatus("");
193 }
194 
195 
196 void
updateStatus(const String & msg)197 ClientApp::updateStatus(const String& msg)
198 {
199     if (m_taskBarReceiver)
200     {
201         m_taskBarReceiver->updateStatus(m_client, msg);
202     }
203 }
204 
205 
206 void
resetRestartTimeout()207 ClientApp::resetRestartTimeout()
208 {
209     // retry time can nolonger be changed
210     //s_retryTime = 0.0;
211 }
212 
213 
214 double
nextRestartTimeout()215 ClientApp::nextRestartTimeout()
216 {
217     // retry at a constant rate (Issue 52)
218     return RETRY_TIME;
219 
220     /*
221     // choose next restart timeout.  we start with rapid retries
222     // then slow down.
223     if (s_retryTime < 1.0) {
224     s_retryTime = 1.0;
225     }
226     else if (s_retryTime < 3.0) {
227     s_retryTime = 3.0;
228     }
229     else {
230     s_retryTime = 5.0;
231     }
232     return s_retryTime;
233     */
234 }
235 
236 
237 void
handleScreenError(const Event &,void *)238 ClientApp::handleScreenError(const Event&, void*)
239 {
240     LOG((CLOG_CRIT "error on screen"));
241     m_events->addEvent(Event(Event::kQuit));
242 }
243 
244 
245 synergy::Screen*
openClientScreen()246 ClientApp::openClientScreen()
247 {
248     synergy::Screen* screen = createScreen();
249     screen->setEnableDragDrop(argsBase().m_enableDragDrop);
250     m_events->adoptHandler(m_events->forIScreen().error(),
251         screen->getEventTarget(),
252         new TMethodEventJob<ClientApp>(
253         this, &ClientApp::handleScreenError));
254     return screen;
255 }
256 
257 
258 void
closeClientScreen(synergy::Screen * screen)259 ClientApp::closeClientScreen(synergy::Screen* screen)
260 {
261     if (screen != NULL) {
262         m_events->removeHandler(m_events->forIScreen().error(),
263             screen->getEventTarget());
264         delete screen;
265     }
266 }
267 
268 
269 void
handleClientRestart(const Event &,void * vtimer)270 ClientApp::handleClientRestart(const Event&, void* vtimer)
271 {
272     // discard old timer
273     EventQueueTimer* timer = static_cast<EventQueueTimer*>(vtimer);
274     m_events->deleteTimer(timer);
275     m_events->removeHandler(Event::kTimer, timer);
276 
277     // reconnect
278     startClient();
279 }
280 
281 
282 void
scheduleClientRestart(double retryTime)283 ClientApp::scheduleClientRestart(double retryTime)
284 {
285     // install a timer and handler to retry later
286     LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime));
287     EventQueueTimer* timer = m_events->newOneShotTimer(retryTime, NULL);
288     m_events->adoptHandler(Event::kTimer, timer,
289         new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientRestart, timer));
290 }
291 
292 
293 void
handleClientConnected(const Event &,void *)294 ClientApp::handleClientConnected(const Event&, void*)
295 {
296     LOG((CLOG_NOTE "connected to server"));
297     resetRestartTimeout();
298     updateStatus();
299 }
300 
301 
302 void
handleClientFailed(const Event & e,void *)303 ClientApp::handleClientFailed(const Event& e, void*)
304 {
305     if ( (++m_lastServerAddressIndex) < m_client->getLastResolvedAddressesCount()) {
306         std::unique_ptr<Client::FailInfo> info(static_cast<Client::FailInfo*>(e.getData()));
307 
308         updateStatus(String("Failed to connect to server: ") + info->m_what + " Trying next address...");
309         LOG((CLOG_NOTE "Failed to connect to server: %s. Trying next address...", info->m_what.c_str()));
310         if (!m_suspended) {
311             scheduleClientRestart(nextRestartTimeout());
312         }
313     }
314     else {
315         m_lastServerAddressIndex = 0;
316         handleClientRefused(e, nullptr);
317     }
318 
319 }
320 
321 void
handleClientRefused(const Event & e,void *)322 ClientApp::handleClientRefused(const Event& e, void*)
323 {
324     std::unique_ptr<Client::FailInfo> info(static_cast<Client::FailInfo*>(e.getData()));
325 
326     updateStatus(String("Failed to connect to server: ") + info->m_what);
327     if (!args().m_restartable || !info->m_retry) {
328         LOG((CLOG_ERR "failed to connect to server: %s", info->m_what.c_str()));
329         m_events->addEvent(Event(Event::kQuit));
330     }
331     else {
332         LOG((CLOG_WARN "failed to connect to server: %s", info->m_what.c_str()));
333         if (!m_suspended) {
334             scheduleClientRestart(nextRestartTimeout());
335         }
336     }
337 }
338 
339 
340 void
handleClientDisconnected(const Event &,void *)341 ClientApp::handleClientDisconnected(const Event&, void*)
342 {
343     LOG((CLOG_NOTE "disconnected from server"));
344     if (!args().m_restartable) {
345         m_events->addEvent(Event(Event::kQuit));
346     }
347     else if (!m_suspended) {
348         scheduleClientRestart(nextRestartTimeout());
349     }
350     updateStatus();
351 }
352 
353 Client*
openClient(const String & name,const NetworkAddress & address,synergy::Screen * screen)354 ClientApp::openClient(const String& name, const NetworkAddress& address,
355                 synergy::Screen* screen)
356 {
357     Client* client = new Client(
358         m_events,
359         name,
360         address,
361         new TCPSocketFactory(m_events, getSocketMultiplexer()),
362         screen,
363         args());
364 
365     try {
366         m_events->adoptHandler(
367             m_events->forClient().connected(),
368             client->getEventTarget(),
369             new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientConnected));
370 
371         m_events->adoptHandler(
372             m_events->forClient().connectionFailed(),
373             client->getEventTarget(),
374             new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientFailed));
375 
376         m_events->adoptHandler(
377             m_events->forClient().connectionRefused(),
378             client->getEventTarget(),
379             new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientRefused));
380 
381         m_events->adoptHandler(
382             m_events->forClient().disconnected(),
383             client->getEventTarget(),
384             new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientDisconnected));
385 
386     } catch (std::bad_alloc &ba) {
387         delete client;
388         throw ba;
389     }
390 
391     return client;
392 }
393 
394 
395 void
closeClient(Client * client)396 ClientApp::closeClient(Client* client)
397 {
398     if (client == NULL) {
399         return;
400     }
401 
402     m_events->removeHandler(m_events->forClient().connected(), client);
403     m_events->removeHandler(m_events->forClient().connectionFailed(), client);
404     m_events->removeHandler(m_events->forClient().connectionRefused(), client);
405     m_events->removeHandler(m_events->forClient().disconnected(), client);
406     delete client;
407 }
408 
409 int
foregroundStartup(int argc,char ** argv)410 ClientApp::foregroundStartup(int argc, char** argv)
411 {
412     initApp(argc, argv);
413 
414     // never daemonize
415     return mainLoop();
416 }
417 
418 bool
startClient()419 ClientApp::startClient()
420 {
421     double retryTime;
422     synergy::Screen* clientScreen = NULL;
423     try {
424         if (m_clientScreen == NULL) {
425             clientScreen = openClientScreen();
426             m_client     = openClient(args().m_name,
427                 *m_serverAddress, clientScreen);
428             m_clientScreen  = clientScreen;
429             LOG((CLOG_NOTE "started client"));
430         }
431 
432         m_client->connect(m_lastServerAddressIndex);
433 
434         updateStatus();
435         return true;
436     }
437     catch (XScreenUnavailable& e) {
438         LOG((CLOG_WARN "secondary screen unavailable: %s", e.what()));
439         closeClientScreen(clientScreen);
440         updateStatus(String("secondary screen unavailable: ") + e.what());
441         retryTime = e.getRetryTime();
442     }
443     catch (XScreenOpenFailure& e) {
444         LOG((CLOG_CRIT "failed to start client: %s", e.what()));
445         closeClientScreen(clientScreen);
446         return false;
447     }
448     catch (XBase& e) {
449         LOG((CLOG_CRIT "failed to start client: %s", e.what()));
450         closeClientScreen(clientScreen);
451         return false;
452     }
453 
454     if (args().m_restartable) {
455         scheduleClientRestart(retryTime);
456         return true;
457     }
458     else {
459         // don't try again
460         return false;
461     }
462 }
463 
464 
465 void
stopClient()466 ClientApp::stopClient()
467 {
468     closeClient(m_client);
469     closeClientScreen(m_clientScreen);
470     m_client       = NULL;
471     m_clientScreen = NULL;
472 }
473 
474 
475 int
mainLoop()476 ClientApp::mainLoop()
477 {
478     // create socket multiplexer.  this must happen after daemonization
479     // on unix because threads evaporate across a fork().
480     SocketMultiplexer multiplexer;
481     setSocketMultiplexer(&multiplexer);
482 
483     // start client, etc
484     appUtil().startNode();
485 
486     // init ipc client after node start, since create a new screen wipes out
487     // the event queue (the screen ctors call adoptBuffer).
488     if (argsBase().m_enableIpc) {
489         initIpcClient();
490     }
491 
492     // run event loop.  if startClient() failed we're supposed to retry
493     // later.  the timer installed by startClient() will take care of
494     // that.
495     DAEMON_RUNNING(true);
496 
497 #if defined(MAC_OS_X_VERSION_10_7)
498 
499     Thread thread(
500         new TMethodJob<ClientApp>(
501             this, &ClientApp::runEventsLoop,
502             NULL));
503 
504     // wait until carbon loop is ready
505     OSXScreen* screen = dynamic_cast<OSXScreen*>(
506         m_clientScreen->getPlatformScreen());
507     screen->waitForCarbonLoop();
508 
509     runCocoaApp();
510 #else
511     m_events->loop();
512 #endif
513 
514     DAEMON_RUNNING(false);
515 
516     // close down
517     LOG((CLOG_DEBUG1 "stopping client"));
518     stopClient();
519     updateStatus();
520     LOG((CLOG_NOTE "stopped client"));
521 
522     if (argsBase().m_enableIpc) {
523         cleanupIpcClient();
524     }
525 
526     return kExitSuccess;
527 }
528 
529 static
530 int
daemonMainLoopStatic(int argc,const char ** argv)531 daemonMainLoopStatic(int argc, const char** argv)
532 {
533     return ClientApp::instance().daemonMainLoop(argc, argv);
534 }
535 
536 int
standardStartup(int argc,char ** argv)537 ClientApp::standardStartup(int argc, char** argv)
538 {
539     initApp(argc, argv);
540 
541     // daemonize if requested
542     if (args().m_daemon) {
543         return ARCH->daemonize(daemonName(), &daemonMainLoopStatic);
544     }
545     else {
546         return mainLoop();
547     }
548 }
549 
550 int
runInner(int argc,char ** argv,ILogOutputter * outputter,StartupFunc startup)551 ClientApp::runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup)
552 {
553     // general initialization
554     m_serverAddress = new NetworkAddress;
555     args().m_pname         = ARCH->getBasename(argv[0]);
556 
557     // install caller's output filter
558     if (outputter != NULL) {
559         CLOG->insert(outputter);
560     }
561 
562     int result;
563     try
564     {
565         // run
566         result = startup(argc, argv);
567     }
568     catch (...)
569     {
570         if (m_taskBarReceiver)
571         {
572             // done with task bar receiver
573             delete m_taskBarReceiver;
574         }
575 
576         delete m_serverAddress;
577 
578         throw;
579     }
580 
581     return result;
582 }
583 
584 void
startNode()585 ClientApp::startNode()
586 {
587     // start the client.  if this return false then we've failed and
588     // we shouldn't retry.
589     LOG((CLOG_DEBUG1 "starting client"));
590     if (!startClient()) {
591         m_bye(kExitFailed);
592     }
593 }
594