diff options
Diffstat (limited to 'src/lib/barrier/ClientApp.cpp')
| -rw-r--r-- | src/lib/barrier/ClientApp.cpp | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/src/lib/barrier/ClientApp.cpp b/src/lib/barrier/ClientApp.cpp new file mode 100644 index 0000000..87686f2 --- /dev/null +++ b/src/lib/barrier/ClientApp.cpp @@ -0,0 +1,560 @@ +/* + * barrier -- 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 <http://www.gnu.org/licenses/>. + */ + +#include "barrier/ClientApp.h" + +#include "client/Client.h" +#include "barrier/ArgParser.h" +#include "barrier/protocol_types.h" +#include "barrier/Screen.h" +#include "barrier/XScreen.h" +#include "barrier/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 <iostream> +#include <stdio.h> +#include <sstream> + +#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_barrierAddress.empty()) { + try { + *m_serverAddress = NetworkAddress(args().m_barrierAddress, 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 <display>] [--no-xinitthreads]" +# define WINAPI_INFO \ + " --display <display> connect to the X server at <display>\n" \ + " --no-xinitthreads do not call XInitThreads()\n" +#else +# define WINAPI_ARG "" +# define WINAPI_INFO "" +#endif + + std::ostringstream buffer; + buffer << "Start the barrier client and connect to a remote server component." << std::endl + << std::endl + << "Usage: " << args().m_pname << " [--yscroll <delta>]" << WINAPI_ARG << HELP_SYS_ARGS + << HELP_COMMON_ARGS << " <server-address>" << std::endl + << std::endl + << "Options:" << std::endl + << HELP_COMMON_INFO_1 << WINAPI_INFO << HELP_SYS_INFO + << " --yscroll <delta> defines the vertical scrolling delta, which is" << std::endl + << " 120 by default." << std::endl + << HELP_COMMON_INFO_2 + << std::endl + << "Default options are marked with a *" << std::endl + << std::endl + << "The server address is of the form: [<hostname>][:<port>]. The hostname" << std::endl + << "must be the address or hostname of the server. The port overrides the" << std::endl + << "default port, " << kDefaultPort << "." << std::endl; + + LOG((CLOG_PRINT "%s", buffer.str().c_str())); +} + +const char* +ClientApp::daemonName() const +{ +#if SYSAPI_WIN32 + return "Barrier Client"; +#elif SYSAPI_UNIX + return "barrierc"; +#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 +} + +barrier::Screen* +ClientApp::createScreen() +{ +#if WINAPI_MSWINDOWS + return new barrier::Screen(new MSWindowsScreen( + false, args().m_noHooks, args().m_stopOnDeskSwitch, m_events), m_events); +#elif WINAPI_XWINDOWS + return new barrier::Screen(new XWindowsScreen( + args().m_display, false, args().m_disableXInitThreads, + args().m_yscroll, m_events), m_events); +#elif WINAPI_CARBON + return new barrier::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)); +} + + +barrier::Screen* +ClientApp::openClientScreen() +{ + barrier::Screen* screen = createScreen(); + screen->setEnableDragDrop(argsBase().m_enableDragDrop); + m_events->adoptHandler(m_events->forIScreen().error(), + screen->getEventTarget(), + new TMethodEventJob<ClientApp>( + this, &ClientApp::handleScreenError)); + return screen; +} + + +void +ClientApp::closeClientScreen(barrier::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<EventQueueTimer*>(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<ClientApp>(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 = + static_cast<Client::FailInfo*>(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, + barrier::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<ClientApp>(this, &ClientApp::handleClientConnected)); + + m_events->adoptHandler( + m_events->forClient().connectionFailed(), + client->getEventTarget(), + new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientFailed)); + + m_events->adoptHandler( + m_events->forClient().disconnected(), + client->getEventTarget(), + new TMethodEventJob<ClientApp>(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; + barrier::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); + + // 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<ClientApp>( + this, &ClientApp::runEventsLoop, + NULL)); + + // wait until carbon loop is ready + OSXScreen* screen = dynamic_cast<OSXScreen*>( + 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); + } +} |
