/* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2012 Synergy Si 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 "client/Client.h" #include "synergy/ArgParser.h" #include "synergy/protocol_types.h" #include "synergy/Screen.h" #include "synergy/XScreen.h" #include "synergy/ClientArgs.h" #include "net/NetworkAddress.h" #include "net/TCPSocketFactory.h" #include "net/SocketMultiplexer.h" #include "net/XSocket.h" #include "mt/Thread.h" #include "arch/IArchTaskBarReceiver.h" #include "arch/Arch.h" #include "base/String.h" #include "base/Event.h" #include "base/IEventQueue.h" #include "base/TMethodEventJob.h" #include "base/log_outputters.h" #include "base/EventQueue.h" #include "base/TMethodJob.h" #include "base/Log.h" #include "common/Version.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 #define RETRY_TIME 1.0 ClientApp::ClientApp(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver) : App(events, createTaskBarReceiver, new 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_shouldExit) { m_bye(kExitArgs); } else { // save server address if (!args().m_synergyAddress.empty()) { try { *m_serverAddress = NetworkAddress(args().m_synergyAddress, 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_PRINT "%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 char buffer[2000]; sprintf( buffer, "Usage: %s" " [--yscroll ]" WINAPI_ARG HELP_SYS_ARGS HELP_COMMON_ARGS " " "\n\n" "Connect to a synergy mouse/keyboard sharing server.\n" "\n" HELP_COMMON_INFO_1 WINAPI_INFO HELP_SYS_INFO " --yscroll defines the vertical scrolling delta, which is\n" " 120 by default.\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, %d.\n", args().m_pname, kDefaultPort ); LOG((CLOG_PRINT "%s", buffer)); } 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), m_events); #elif WINAPI_XWINDOWS return new synergy::Screen(new XWindowsScreen( args().m_display, false, args().m_disableXInitThreads, args().m_yscroll, m_events), m_events); #elif WINAPI_CARBON return new synergy::Screen(new OSXScreen(m_events, false), 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 = reinterpret_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*) { Client::FailInfo* info = reinterpret_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()); } } delete info; } 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, new TCPSocketFactory(m_events, getSocketMultiplexer()), 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().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().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(); 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); // load all available plugins. ARCH->plugin().load(); // pass log and arch into plugins. ARCH->plugin().init(Log::getInstance(), Arch::getInstance()); // 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(); } // init event for all available plugins. ARCH->plugin().initEvent(m_clientScreen->getEventTarget(), m_events); // 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(); } // unload all plugins. ARCH->plugin().unload(); 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); } }