/* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2012-2016 Symless Ltd. * Copyright (C) 2002 Chris Schoeneman * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file LICENSE that should have accompanied this file. * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "synergy/ClientApp.h" #include "arch/Arch.h" #include "arch/IArchTaskBarReceiver.h" #include "base/Event.h" #include "base/EventQueue.h" #include "base/IEventQueue.h" #include "base/Log.h" #include "base/String.h" #include "base/TMethodEventJob.h" #include "base/TMethodJob.h" #include "base/log_outputters.h" #include "client/Client.h" #include "common/Version.h" #include "mt/Thread.h" #include "net/InverseSockets/InverseSocketFactory.h" #include "net/NetworkAddress.h" #include "net/SocketMultiplexer.h" #include "net/TCPSocketFactory.h" #include "net/XSocket.h" #include "synergy/ArgParser.h" #include "synergy/ClientArgs.h" #include "synergy/Screen.h" #include "synergy/XScreen.h" #include "synergy/protocol_types.h" #if SYSAPI_WIN32 #include "arch/win32/ArchMiscWindows.h" #endif #if WINAPI_MSWINDOWS #include "platform/MSWindowsScreen.h" #elif WINAPI_XWINDOWS #include "platform/XWindowsScreen.h" #elif WINAPI_CARBON #include "platform/OSXScreen.h" #endif #if defined(__APPLE__) #include "platform/OSXDragSimulator.h" #endif #include #include #include #define RETRY_TIME 1.0 ClientApp::ClientApp( IEventQueue *events, CreateTaskBarReceiverFunc createTaskBarReceiver) : App(events, createTaskBarReceiver, new synergy::ClientArgs()), m_client(NULL), m_clientScreen(NULL), m_serverAddress(NULL) {} ClientApp::~ClientApp() {} void ClientApp::parseArgs(int argc, const char *const *argv) { ArgParser argParser(this); bool result = argParser.parseClientArgs(args(), argc, argv); if (!result || args().m_shouldExitOk || args().m_shouldExitFail) { if (args().m_shouldExitOk) { m_bye(kExitSuccess); } else { m_bye(kExitArgs); } } else { // save server address if (!args().m_serverAddress.empty()) { try { *m_serverAddress = NetworkAddress(args().m_serverAddress, kDefaultPort); m_serverAddress->resolve(); } catch (XSocketAddress &e) { // allow an address that we can't look up if we're restartable. // we'll try to resolve the address each time we connect to the // server. a bad port will never get better. patch by Brent // Priddy. if (!args().m_restartable || e.getError() == XSocketAddress::kBadPort) { LOG( (CLOG_CRIT "%s: %s" BYE, args().m_pname, e.what(), args().m_pname)); m_bye(kExitFailed); } } } } } void ClientApp::help() { #if WINAPI_XWINDOWS #define WINAPI_ARG " [--display ] [--no-xinitthreads]" #define WINAPI_INFO \ " --display connect to the X server at \n" \ " --no-xinitthreads do not call XInitThreads()\n" #else #define WINAPI_ARG "" #define WINAPI_INFO "" #endif std::stringstream help; help << "Usage: " << args().m_pname << " [--address
]" << " [--yscroll ]" << " [--sync-language]" << " [--invert-scroll]" << " [--host]" << WINAPI_ARG << HELP_SYS_ARGS << HELP_COMMON_ARGS << " " << "\n\n" << "Connect to a synergy mouse/keyboard sharing server.\n" << "\n" << " -a, --address
local network interface address.\n" << HELP_COMMON_INFO_1 << WINAPI_INFO << HELP_SYS_INFO << " --yscroll defines the vertical scrolling delta, " "which is\n" << " 120 by default.\n" << " --sync-language set this parameter to enable language " "synchronization.\n" << " --invert-scroll invert scroll direction on this " "computer.\n" << " --host client starts a listener and waits for a " "server connection.\n" << HELP_COMMON_INFO_2 << "\n" << "* marks defaults.\n" << "\n" << "The server address is of the form: [][:]. The " "hostname\n" << "must be the address or hostname of the server. The port overrides " "the\n" << "default port, " << kDefaultPort << ".\n"; LOG((CLOG_PRINT "%s", help.str().c_str())); } const char *ClientApp::daemonName() const { #if SYSAPI_WIN32 return "Synergy Client"; #elif SYSAPI_UNIX return "synergyc"; #endif } const char *ClientApp::daemonInfo() const { #if SYSAPI_WIN32 return "Allows another computer to share it's keyboard and mouse with this " "computer."; #elif SYSAPI_UNIX return ""; #endif } synergy::Screen *ClientApp::createScreen() { #if WINAPI_MSWINDOWS return new synergy::Screen( new MSWindowsScreen( false, args().m_noHooks, args().m_stopOnDeskSwitch, m_events, args().m_enableLangSync, args().m_clientScrollDirection), m_events); #elif WINAPI_XWINDOWS return new synergy::Screen( new XWindowsScreen( args().m_display, false, args().m_disableXInitThreads, args().m_yscroll, m_events, args().m_clientScrollDirection), m_events); #elif WINAPI_CARBON return new synergy::Screen( new OSXScreen( m_events, false, args().m_enableLangSync, args().m_clientScrollDirection), m_events); #endif } void ClientApp::updateStatus() { updateStatus(""); } void ClientApp::updateStatus(const String &msg) { if (m_taskBarReceiver) { m_taskBarReceiver->updateStatus(m_client, msg); } } void ClientApp::resetRestartTimeout() { // retry time can nolonger be changed // s_retryTime = 0.0; } double ClientApp::nextRestartTimeout() { // retry at a constant rate (Issue 52) return RETRY_TIME; /* // choose next restart timeout. we start with rapid retries // then slow down. if (s_retryTime < 1.0) { s_retryTime = 1.0; } else if (s_retryTime < 3.0) { s_retryTime = 3.0; } else { s_retryTime = 5.0; } return s_retryTime; */ } void ClientApp::handleScreenError(const Event &, void *) { LOG((CLOG_CRIT "error on screen")); m_events->addEvent(Event(Event::kQuit)); } synergy::Screen *ClientApp::openClientScreen() { synergy::Screen *screen = createScreen(); screen->setEnableDragDrop(argsBase().m_enableDragDrop); m_events->adoptHandler( m_events->forIScreen().error(), screen->getEventTarget(), new TMethodEventJob(this, &ClientApp::handleScreenError)); return screen; } void ClientApp::closeClientScreen(synergy::Screen *screen) { if (screen != NULL) { m_events->removeHandler( m_events->forIScreen().error(), screen->getEventTarget()); delete screen; } } void ClientApp::handleClientRestart(const Event &, void *vtimer) { // discard old timer EventQueueTimer *timer = static_cast(vtimer); m_events->deleteTimer(timer); m_events->removeHandler(Event::kTimer, timer); // reconnect startClient(); } void ClientApp::scheduleClientRestart(double retryTime) { // install a timer and handler to retry later LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); EventQueueTimer *timer = m_events->newOneShotTimer(retryTime, NULL); m_events->adoptHandler( Event::kTimer, timer, new TMethodEventJob( this, &ClientApp::handleClientRestart, timer)); } void ClientApp::handleClientConnected(const Event &, void *) { LOG((CLOG_NOTE "connected to server")); resetRestartTimeout(); updateStatus(); } void ClientApp::handleClientFailed(const Event &e, void *) { if ((++m_lastServerAddressIndex) < m_client->getLastResolvedAddressesCount()) { std::unique_ptr info( static_cast(e.getData())); updateStatus( String("Failed to connect to server: ") + info->m_what + " Trying next address..."); LOG( (CLOG_NOTE "failed to connect to server=%s, trying next address", info->m_what.c_str())); if (!m_suspended) { scheduleClientRestart(nextRestartTimeout()); } } else { m_lastServerAddressIndex = 0; handleClientRefused(e, nullptr); } } void ClientApp::handleClientRefused(const Event &e, void *) { std::unique_ptr info( static_cast(e.getData())); updateStatus(String("Failed to connect to server: ") + info->m_what); if (!args().m_restartable || !info->m_retry) { LOG((CLOG_ERR "failed to connect to server: %s", info->m_what.c_str())); m_events->addEvent(Event(Event::kQuit)); } else { LOG((CLOG_WARN "failed to connect to server: %s", info->m_what.c_str())); if (!m_suspended) { scheduleClientRestart(nextRestartTimeout()); } } } void ClientApp::handleClientDisconnected(const Event &, void *) { LOG((CLOG_NOTE "disconnected from server")); if (!args().m_restartable) { m_events->addEvent(Event(Event::kQuit)); } else if (!m_suspended) { scheduleClientRestart(nextRestartTimeout()); } updateStatus(); } Client *ClientApp::openClient( const String &name, const NetworkAddress &address, synergy::Screen *screen) { Client *client = new Client(m_events, name, address, getSocketFactory(), screen, args()); try { m_events->adoptHandler( m_events->forClient().connected(), client->getEventTarget(), new TMethodEventJob( this, &ClientApp::handleClientConnected)); m_events->adoptHandler( m_events->forClient().connectionFailed(), client->getEventTarget(), new TMethodEventJob(this, &ClientApp::handleClientFailed)); m_events->adoptHandler( m_events->forClient().connectionRefused(), client->getEventTarget(), new TMethodEventJob(this, &ClientApp::handleClientRefused)); m_events->adoptHandler( m_events->forClient().disconnected(), client->getEventTarget(), new TMethodEventJob( this, &ClientApp::handleClientDisconnected)); } catch (std::bad_alloc &ba) { delete client; throw ba; } return client; } void ClientApp::closeClient(Client *client) { if (client == NULL) { return; } m_events->removeHandler(m_events->forClient().connected(), client); m_events->removeHandler(m_events->forClient().connectionFailed(), client); m_events->removeHandler(m_events->forClient().connectionRefused(), client); m_events->removeHandler(m_events->forClient().disconnected(), client); delete client; } int ClientApp::foregroundStartup(int argc, char **argv) { initApp(argc, argv); // never daemonize return mainLoop(); } bool ClientApp::startClient() { double retryTime; synergy::Screen *clientScreen = NULL; try { if (m_clientScreen == NULL) { clientScreen = openClientScreen(); m_client = openClient(args().m_name, *m_serverAddress, clientScreen); m_clientScreen = clientScreen; LOG((CLOG_NOTE "started client")); } m_client->connect(m_lastServerAddressIndex); updateStatus(); return true; } catch (XScreenUnavailable &e) { LOG((CLOG_WARN "secondary screen unavailable: %s", e.what())); closeClientScreen(clientScreen); updateStatus(String("secondary screen unavailable: ") + e.what()); retryTime = e.getRetryTime(); } catch (XScreenOpenFailure &e) { LOG((CLOG_CRIT "failed to start client: %s", e.what())); closeClientScreen(clientScreen); return false; } catch (XBase &e) { LOG((CLOG_CRIT "failed to start client: %s", e.what())); closeClientScreen(clientScreen); return false; } if (args().m_restartable) { scheduleClientRestart(retryTime); return true; } else { // don't try again return false; } } void ClientApp::stopClient() { closeClient(m_client); closeClientScreen(m_clientScreen); m_client = NULL; m_clientScreen = NULL; } int ClientApp::mainLoop() { // create socket multiplexer. this must happen after daemonization // on unix because threads evaporate across a fork(). SocketMultiplexer multiplexer; setSocketMultiplexer(&multiplexer); // start client, etc appUtil().startNode(); // init ipc client after node start, since create a new screen wipes out // the event queue (the screen ctors call adoptBuffer). if (argsBase().m_enableIpc) { initIpcClient(); } // run event loop. if startClient() failed we're supposed to retry // later. the timer installed by startClient() will take care of // that. DAEMON_RUNNING(true); #if defined(MAC_OS_X_VERSION_10_7) Thread thread( new TMethodJob(this, &ClientApp::runEventsLoop, NULL)); // wait until carbon loop is ready OSXScreen *screen = dynamic_cast(m_clientScreen->getPlatformScreen()); screen->waitForCarbonLoop(); runCocoaApp(); #else m_events->loop(); #endif DAEMON_RUNNING(false); // close down LOG((CLOG_DEBUG1 "stopping client")); stopClient(); updateStatus(); LOG((CLOG_NOTE "stopped client")); if (argsBase().m_enableIpc) { cleanupIpcClient(); } return kExitSuccess; } static int daemonMainLoopStatic(int argc, const char **argv) { return ClientApp::instance().daemonMainLoop(argc, argv); } int ClientApp::standardStartup(int argc, char **argv) { initApp(argc, argv); // daemonize if requested if (args().m_daemon) { return ARCH->daemonize(daemonName(), &daemonMainLoopStatic); } else { return mainLoop(); } } int ClientApp::runInner( int argc, char **argv, ILogOutputter *outputter, StartupFunc startup) { // general initialization m_serverAddress = new NetworkAddress; args().m_pname = ARCH->getBasename(argv[0]); // install caller's output filter if (outputter != NULL) { CLOG->insert(outputter); } int result; try { // run result = startup(argc, argv); } catch (...) { if (m_taskBarReceiver) { // done with task bar receiver delete m_taskBarReceiver; } delete m_serverAddress; throw; } return result; } void ClientApp::startNode() { // start the client. if this return false then we've failed and // we shouldn't retry. LOG((CLOG_DEBUG1 "starting client")); if (!startClient()) { m_bye(kExitFailed); } } ISocketFactory *ClientApp::getSocketFactory() const { ISocketFactory *socketFactory = nullptr; if (args().m_hostMode) { socketFactory = new InverseSocketFactory(m_events, getSocketMultiplexer()); } else { socketFactory = new TCPSocketFactory(m_events, getSocketMultiplexer()); } return socketFactory; }