aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/barrier/ClientApp.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/barrier/ClientApp.cpp')
-rw-r--r--src/lib/barrier/ClientApp.cpp560
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);
+ }
+}