diff options
Diffstat (limited to 'src/lib/barrier/ServerApp.cpp')
| -rw-r--r-- | src/lib/barrier/ServerApp.cpp | 859 |
1 files changed, 859 insertions, 0 deletions
diff --git a/src/lib/barrier/ServerApp.cpp b/src/lib/barrier/ServerApp.cpp new file mode 100644 index 0000000..112f290 --- /dev/null +++ b/src/lib/barrier/ServerApp.cpp @@ -0,0 +1,859 @@ +/* + * 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/ServerApp.h" + +#include "server/Server.h" +#include "server/ClientListener.h" +#include "server/ClientProxy.h" +#include "server/PrimaryClient.h" +#include "barrier/ArgParser.h" +#include "barrier/Screen.h" +#include "barrier/XScreen.h" +#include "barrier/ServerTaskBarReceiver.h" +#include "barrier/ServerArgs.h" +#include "net/SocketMultiplexer.h" +#include "net/TCPSocketFactory.h" +#include "net/XSocket.h" +#include "arch/Arch.h" +#include "base/EventQueue.h" +#include "base/log_outputters.h" +#include "base/FunctionEventJob.h" +#include "base/TMethodJob.h" +#include "base/IEventQueue.h" +#include "base/Log.h" +#include "base/TMethodEventJob.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 <fstream> +#include <sstream> + +// +// ServerApp +// + +ServerApp::ServerApp(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver) : + App(events, createTaskBarReceiver, new ServerArgs()), + m_server(NULL), + m_serverState(kUninitialized), + m_serverScreen(NULL), + m_primaryClient(NULL), + m_listener(NULL), + m_timer(NULL), + m_barrierAddress(NULL) +{ +} + +ServerApp::~ServerApp() +{ +} + +void +ServerApp::parseArgs(int argc, const char* const* argv) +{ + ArgParser argParser(this); + bool result = argParser.parseServerArgs(args(), argc, argv); + + if (!result || args().m_shouldExit) { + m_bye(kExitArgs); + } + else { + if (!args().m_barrierAddress.empty()) { + try { + *m_barrierAddress = NetworkAddress(args().m_barrierAddress, + kDefaultPort); + m_barrierAddress->resolve(); + } + catch (XSocketAddress& e) { + LOG((CLOG_PRINT "%s: %s" BYE, + args().m_pname, e.what(), args().m_pname)); + m_bye(kExitArgs); + } + } + } +} + +void +ServerApp::help() +{ + // window api args (windows/x-windows/carbon) +#if WINAPI_XWINDOWS +# define WINAPI_ARGS \ + " [--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_ARGS "" +# define WINAPI_INFO "" +#endif + + std::ostringstream buffer; + buffer << "Start the barrier server component." << std::endl + << std::endl + << "Usage: " << args().m_pname + << " [--address <address>]" + << " [--config <pathname>]" + << WINAPI_ARGS << HELP_SYS_ARGS << HELP_COMMON_ARGS << std::endl + << std::endl + << "Options:" << std::endl + << " -a, --address <address> listen for clients on the given address." << std::endl + << " -c, --config <pathname> use the named configuration file instead." << std::endl + << HELP_COMMON_INFO_1 << WINAPI_INFO << HELP_SYS_INFO << HELP_COMMON_INFO_2 << std::endl + << "Default options are marked with a *" << std::endl + << std::endl + << "The argument for --address is of the form: [<hostname>][:<port>]. The" << std::endl + << "hostname must be the address or hostname of an interface on the system." << std::endl + << "The default is to listen on all interfaces. The port overrides the" << std::endl + << "default port, " << kDefaultPort << "." << std::endl + << std::endl + << "If no configuration file pathname is provided then the first of the" << std::endl + << "following to load successfully sets the configuration:" << std::endl + << " $HOME/" << USR_CONFIG_NAME << std::endl + << " " << ARCH->concatPath(ARCH->getSystemDirectory(), SYS_CONFIG_NAME) << std::endl; + + LOG((CLOG_PRINT "%s", buffer.str().c_str())); +} + +void +ServerApp::reloadSignalHandler(Arch::ESignal, void*) +{ + IEventQueue* events = App::instance().getEvents(); + events->addEvent(Event(events->forServerApp().reloadConfig(), + events->getSystemTarget())); +} + +void +ServerApp::reloadConfig(const Event&, void*) +{ + LOG((CLOG_DEBUG "reload configuration")); + if (loadConfig(args().m_configFile)) { + if (m_server != NULL) { + m_server->setConfig(*args().m_config); + } + LOG((CLOG_NOTE "reloaded configuration")); + } +} + +void +ServerApp::loadConfig() +{ + bool loaded = false; + + // load the config file, if specified + if (!args().m_configFile.empty()) { + loaded = loadConfig(args().m_configFile); + } + + // load the default configuration if no explicit file given + else { + // get the user's home directory + String path = ARCH->getUserDirectory(); + if (!path.empty()) { + // complete path + path = ARCH->concatPath(path, USR_CONFIG_NAME); + + // now try loading the user's configuration + if (loadConfig(path)) { + loaded = true; + args().m_configFile = path; + } + } + if (!loaded) { + // try the system-wide config file + path = ARCH->getSystemDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, SYS_CONFIG_NAME); + if (loadConfig(path)) { + loaded = true; + args().m_configFile = path; + } + } + } + } + + if (!loaded) { + LOG((CLOG_PRINT "%s: no configuration available", args().m_pname)); + m_bye(kExitConfig); + } +} + +bool +ServerApp::loadConfig(const String& pathname) +{ + try { + // load configuration + LOG((CLOG_DEBUG "opening configuration \"%s\"", pathname.c_str())); + std::ifstream configStream(pathname.c_str()); + if (!configStream.is_open()) { + // report failure to open configuration as a debug message + // since we try several paths and we expect some to be + // missing. + LOG((CLOG_DEBUG "cannot open configuration \"%s\"", + pathname.c_str())); + return false; + } + configStream >> *args().m_config; + LOG((CLOG_DEBUG "configuration read successfully")); + return true; + } + catch (XConfigRead& e) { + // report error in configuration file + LOG((CLOG_ERR "cannot read configuration \"%s\": %s", + pathname.c_str(), e.what())); + } + return false; +} + +void +ServerApp::forceReconnect(const Event&, void*) +{ + if (m_server != NULL) { + m_server->disconnect(); + } +} + +void +ServerApp::handleClientConnected(const Event&, void* vlistener) +{ + ClientListener* listener = static_cast<ClientListener*>(vlistener); + ClientProxy* client = listener->getNextClient(); + if (client != NULL) { + m_server->adoptClient(client); + updateStatus(); + } +} + +void +ServerApp::handleClientsDisconnected(const Event&, void*) +{ + m_events->addEvent(Event(Event::kQuit)); +} + +void +ServerApp::closeServer(Server* server) +{ + if (server == NULL) { + return; + } + + // tell all clients to disconnect + server->disconnect(); + + // wait for clients to disconnect for up to timeout seconds + double timeout = 3.0; + EventQueueTimer* timer = m_events->newOneShotTimer(timeout, NULL); + m_events->adoptHandler(Event::kTimer, timer, + new TMethodEventJob<ServerApp>(this, &ServerApp::handleClientsDisconnected)); + m_events->adoptHandler(m_events->forServer().disconnected(), server, + new TMethodEventJob<ServerApp>(this, &ServerApp::handleClientsDisconnected)); + + m_events->loop(); + + m_events->removeHandler(Event::kTimer, timer); + m_events->deleteTimer(timer); + m_events->removeHandler(m_events->forServer().disconnected(), server); + + // done with server + delete server; +} + +void +ServerApp::stopRetryTimer() +{ + if (m_timer != NULL) { + m_events->deleteTimer(m_timer); + m_events->removeHandler(Event::kTimer, NULL); + m_timer = NULL; + } +} + +void +ServerApp::updateStatus() +{ + updateStatus(""); +} + +void ServerApp::updateStatus(const String& msg) +{ + if (m_taskBarReceiver) + { + m_taskBarReceiver->updateStatus(m_server, msg); + } +} + +void +ServerApp::closeClientListener(ClientListener* listen) +{ + if (listen != NULL) { + m_events->removeHandler(m_events->forClientListener().connected(), listen); + delete listen; + } +} + +void +ServerApp::stopServer() +{ + if (m_serverState == kStarted) { + closeServer(m_server); + closeClientListener(m_listener); + m_server = NULL; + m_listener = NULL; + m_serverState = kInitialized; + } + else if (m_serverState == kStarting) { + stopRetryTimer(); + m_serverState = kInitialized; + } + assert(m_server == NULL); + assert(m_listener == NULL); +} + +void +ServerApp::closePrimaryClient(PrimaryClient* primaryClient) +{ + delete primaryClient; +} + +void +ServerApp::closeServerScreen(barrier::Screen* screen) +{ + if (screen != NULL) { + m_events->removeHandler(m_events->forIScreen().error(), + screen->getEventTarget()); + m_events->removeHandler(m_events->forIScreen().suspend(), + screen->getEventTarget()); + m_events->removeHandler(m_events->forIScreen().resume(), + screen->getEventTarget()); + delete screen; + } +} + +void ServerApp::cleanupServer() +{ + stopServer(); + if (m_serverState == kInitialized) { + closePrimaryClient(m_primaryClient); + closeServerScreen(m_serverScreen); + m_primaryClient = NULL; + m_serverScreen = NULL; + m_serverState = kUninitialized; + } + else if (m_serverState == kInitializing || + m_serverState == kInitializingToStart) { + stopRetryTimer(); + m_serverState = kUninitialized; + } + assert(m_primaryClient == NULL); + assert(m_serverScreen == NULL); + assert(m_serverState == kUninitialized); +} + +void +ServerApp::retryHandler(const Event&, void*) +{ + // discard old timer + assert(m_timer != NULL); + stopRetryTimer(); + + // try initializing/starting the server again + switch (m_serverState) { + case kUninitialized: + case kInitialized: + case kStarted: + assert(0 && "bad internal server state"); + break; + + case kInitializing: + LOG((CLOG_DEBUG1 "retry server initialization")); + m_serverState = kUninitialized; + if (!initServer()) { + m_events->addEvent(Event(Event::kQuit)); + } + break; + + case kInitializingToStart: + LOG((CLOG_DEBUG1 "retry server initialization")); + m_serverState = kUninitialized; + if (!initServer()) { + m_events->addEvent(Event(Event::kQuit)); + } + else if (m_serverState == kInitialized) { + LOG((CLOG_DEBUG1 "starting server")); + if (!startServer()) { + m_events->addEvent(Event(Event::kQuit)); + } + } + break; + + case kStarting: + LOG((CLOG_DEBUG1 "retry starting server")); + m_serverState = kInitialized; + if (!startServer()) { + m_events->addEvent(Event(Event::kQuit)); + } + break; + } +} + +bool ServerApp::initServer() +{ + // skip if already initialized or initializing + if (m_serverState != kUninitialized) { + return true; + } + + double retryTime; + barrier::Screen* serverScreen = NULL; + PrimaryClient* primaryClient = NULL; + try { + String name = args().m_config->getCanonicalName(args().m_name); + serverScreen = openServerScreen(); + primaryClient = openPrimaryClient(name, serverScreen); + m_serverScreen = serverScreen; + m_primaryClient = primaryClient; + m_serverState = kInitialized; + updateStatus(); + return true; + } + catch (XScreenUnavailable& e) { + LOG((CLOG_WARN "primary screen unavailable: %s", e.what())); + closePrimaryClient(primaryClient); + closeServerScreen(serverScreen); + updateStatus(String("primary screen unavailable: ") + e.what()); + retryTime = e.getRetryTime(); + } + catch (XScreenOpenFailure& e) { + LOG((CLOG_CRIT "failed to start server: %s", e.what())); + closePrimaryClient(primaryClient); + closeServerScreen(serverScreen); + return false; + } + catch (XBase& e) { + LOG((CLOG_CRIT "failed to start server: %s", e.what())); + closePrimaryClient(primaryClient); + closeServerScreen(serverScreen); + return false; + } + + if (args().m_restartable) { + // install a timer and handler to retry later + assert(m_timer == NULL); + LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); + m_timer = m_events->newOneShotTimer(retryTime, NULL); + m_events->adoptHandler(Event::kTimer, m_timer, + new TMethodEventJob<ServerApp>(this, &ServerApp::retryHandler)); + m_serverState = kInitializing; + return true; + } + else { + // don't try again + return false; + } +} + +barrier::Screen* +ServerApp::openServerScreen() +{ + barrier::Screen* screen = createScreen(); + screen->setEnableDragDrop(argsBase().m_enableDragDrop); + m_events->adoptHandler(m_events->forIScreen().error(), + screen->getEventTarget(), + new TMethodEventJob<ServerApp>( + this, &ServerApp::handleScreenError)); + m_events->adoptHandler(m_events->forIScreen().suspend(), + screen->getEventTarget(), + new TMethodEventJob<ServerApp>( + this, &ServerApp::handleSuspend)); + m_events->adoptHandler(m_events->forIScreen().resume(), + screen->getEventTarget(), + new TMethodEventJob<ServerApp>( + this, &ServerApp::handleResume)); + return screen; +} + +bool +ServerApp::startServer() +{ + // skip if already started or starting + if (m_serverState == kStarting || m_serverState == kStarted) { + return true; + } + + // initialize if necessary + if (m_serverState != kInitialized) { + if (!initServer()) { + // hard initialization failure + return false; + } + if (m_serverState == kInitializing) { + // not ready to start + m_serverState = kInitializingToStart; + return true; + } + assert(m_serverState == kInitialized); + } + + double retryTime; + ClientListener* listener = NULL; + try { + listener = openClientListener(args().m_config->getBarrierAddress()); + m_server = openServer(*args().m_config, m_primaryClient); + listener->setServer(m_server); + m_server->setListener(listener); + m_listener = listener; + updateStatus(); + LOG((CLOG_NOTE "started server, waiting for clients")); + m_serverState = kStarted; + return true; + } + catch (XSocketAddressInUse& e) { + LOG((CLOG_WARN "cannot listen for clients: %s", e.what())); + closeClientListener(listener); + updateStatus(String("cannot listen for clients: ") + e.what()); + retryTime = 10.0; + } + catch (XBase& e) { + LOG((CLOG_CRIT "failed to start server: %s", e.what())); + closeClientListener(listener); + return false; + } + + if (args().m_restartable) { + // install a timer and handler to retry later + assert(m_timer == NULL); + LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); + m_timer = m_events->newOneShotTimer(retryTime, NULL); + m_events->adoptHandler(Event::kTimer, m_timer, + new TMethodEventJob<ServerApp>(this, &ServerApp::retryHandler)); + m_serverState = kStarting; + return true; + } + else { + // don't try again + return false; + } +} + +barrier::Screen* +ServerApp::createScreen() +{ +#if WINAPI_MSWINDOWS + return new barrier::Screen(new MSWindowsScreen( + true, args().m_noHooks, args().m_stopOnDeskSwitch, m_events), m_events); +#elif WINAPI_XWINDOWS + return new barrier::Screen(new XWindowsScreen( + args().m_display, true, args().m_disableXInitThreads, 0, m_events), m_events); +#elif WINAPI_CARBON + return new barrier::Screen(new OSXScreen(m_events, true), m_events); +#endif +} + +PrimaryClient* +ServerApp::openPrimaryClient(const String& name, barrier::Screen* screen) +{ + LOG((CLOG_DEBUG1 "creating primary screen")); + return new PrimaryClient(name, screen); + +} + +void +ServerApp::handleScreenError(const Event&, void*) +{ + LOG((CLOG_CRIT "error on screen")); + m_events->addEvent(Event(Event::kQuit)); +} + +void +ServerApp::handleSuspend(const Event&, void*) +{ + if (!m_suspended) { + LOG((CLOG_INFO "suspend")); + stopServer(); + m_suspended = true; + } +} + +void +ServerApp::handleResume(const Event&, void*) +{ + if (m_suspended) { + LOG((CLOG_INFO "resume")); + startServer(); + m_suspended = false; + } +} + +ClientListener* +ServerApp::openClientListener(const NetworkAddress& address) +{ + ClientListener* listen = new ClientListener( + address, + new TCPSocketFactory(m_events, getSocketMultiplexer()), + m_events, + args().m_enableCrypto); + + m_events->adoptHandler( + m_events->forClientListener().connected(), listen, + new TMethodEventJob<ServerApp>( + this, &ServerApp::handleClientConnected, listen)); + + return listen; +} + +Server* +ServerApp::openServer(Config& config, PrimaryClient* primaryClient) +{ + Server* server = new Server(config, primaryClient, m_serverScreen, m_events, args()); + try { + m_events->adoptHandler( + m_events->forServer().disconnected(), server, + new TMethodEventJob<ServerApp>(this, &ServerApp::handleNoClients)); + + m_events->adoptHandler( + m_events->forServer().screenSwitched(), server, + new TMethodEventJob<ServerApp>(this, &ServerApp::handleScreenSwitched)); + + } catch (std::bad_alloc &ba) { + delete server; + throw ba; + } + + return server; +} + +void +ServerApp::handleNoClients(const Event&, void*) +{ + updateStatus(); +} + +void +ServerApp::handleScreenSwitched(const Event& e, void*) +{ +} + +int +ServerApp::mainLoop() +{ + // create socket multiplexer. this must happen after daemonization + // on unix because threads evaporate across a fork(). + SocketMultiplexer multiplexer; + setSocketMultiplexer(&multiplexer); + + // if configuration has no screens then add this system + // as the default + if (args().m_config->begin() == args().m_config->end()) { + args().m_config->addScreen(args().m_name); + } + + // set the contact address, if provided, in the config. + // otherwise, if the config doesn't have an address, use + // the default. + if (m_barrierAddress->isValid()) { + args().m_config->setBarrierAddress(*m_barrierAddress); + } + else if (!args().m_config->getBarrierAddress().isValid()) { + args().m_config->setBarrierAddress(NetworkAddress(kDefaultPort)); + } + + // canonicalize the primary screen name + String primaryName = args().m_config->getCanonicalName(args().m_name); + if (primaryName.empty()) { + LOG((CLOG_CRIT "unknown screen name `%s'", args().m_name.c_str())); + return kExitFailed; + } + + // start server, 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(); + } + + // handle hangup signal by reloading the server's configuration + ARCH->setSignalHandler(Arch::kHANGUP, &reloadSignalHandler, NULL); + m_events->adoptHandler(m_events->forServerApp().reloadConfig(), + m_events->getSystemTarget(), + new TMethodEventJob<ServerApp>(this, &ServerApp::reloadConfig)); + + // handle force reconnect event by disconnecting clients. they'll + // reconnect automatically. + m_events->adoptHandler(m_events->forServerApp().forceReconnect(), + m_events->getSystemTarget(), + new TMethodEventJob<ServerApp>(this, &ServerApp::forceReconnect)); + + // to work around the sticky meta keys problem, we'll give users + // the option to reset the state of barriers + m_events->adoptHandler(m_events->forServerApp().resetServer(), + m_events->getSystemTarget(), + new TMethodEventJob<ServerApp>(this, &ServerApp::resetServer)); + + // run event loop. if startServer() failed we're supposed to retry + // later. the timer installed by startServer() will take care of + // that. + DAEMON_RUNNING(true); + +#if defined(MAC_OS_X_VERSION_10_7) + + Thread thread( + new TMethodJob<ServerApp>( + this, &ServerApp::runEventsLoop, + NULL)); + + // wait until carbon loop is ready + OSXScreen* screen = dynamic_cast<OSXScreen*>( + m_serverScreen->getPlatformScreen()); + screen->waitForCarbonLoop(); + + runCocoaApp(); +#else + m_events->loop(); +#endif + + DAEMON_RUNNING(false); + + // close down + LOG((CLOG_DEBUG1 "stopping server")); + m_events->removeHandler(m_events->forServerApp().forceReconnect(), + m_events->getSystemTarget()); + m_events->removeHandler(m_events->forServerApp().reloadConfig(), + m_events->getSystemTarget()); + cleanupServer(); + updateStatus(); + LOG((CLOG_NOTE "stopped server")); + + if (argsBase().m_enableIpc) { + cleanupIpcClient(); + } + + return kExitSuccess; +} + +void ServerApp::resetServer(const Event&, void*) +{ + LOG((CLOG_DEBUG1 "resetting server")); + stopServer(); + cleanupServer(); + startServer(); +} + +int +ServerApp::runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup) +{ + // general initialization + m_barrierAddress = new NetworkAddress; + args().m_config = new Config(m_events); + args().m_pname = ARCH->getBasename(argv[0]); + + // install caller's output filter + if (outputter != NULL) { + CLOG->insert(outputter); + } + + // run + int result = startup(argc, argv); + + if (m_taskBarReceiver) + { + // done with task bar receiver + delete m_taskBarReceiver; + } + + delete args().m_config; + delete m_barrierAddress; + return result; +} + +int daemonMainLoopStatic(int argc, const char** argv) { + return ServerApp::instance().daemonMainLoop(argc, argv); +} + +int +ServerApp::standardStartup(int argc, char** argv) +{ + initApp(argc, argv); + + // daemonize if requested + if (args().m_daemon) { + return ARCH->daemonize(daemonName(), daemonMainLoopStatic); + } + else { + return mainLoop(); + } +} + +int +ServerApp::foregroundStartup(int argc, char** argv) +{ + initApp(argc, argv); + + // never daemonize + return mainLoop(); +} + +const char* +ServerApp::daemonName() const +{ +#if SYSAPI_WIN32 + return "Barrier Server"; +#elif SYSAPI_UNIX + return "barriers"; +#endif +} + +const char* +ServerApp::daemonInfo() const +{ +#if SYSAPI_WIN32 + return "Shares this computers mouse and keyboard with other computers."; +#elif SYSAPI_UNIX + return ""; +#endif +} + +void +ServerApp::startNode() +{ + // start the server. if this return false then we've failed and + // we shouldn't retry. + LOG((CLOG_DEBUG1 "starting server")); + if (!startServer()) { + m_bye(kExitFailed); + } +} |
