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