• Main Page
  • Classes
  • Files
  • File List

CClientApp.cpp

00001 /*
00002  * synergy -- mouse and keyboard sharing utility
00003  * Copyright (C) 2012 Bolton Software Ltd.
00004  * Copyright (C) 2002 Chris Schoeneman
00005  * 
00006  * This package is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License
00008  * found in the file COPYING that should have accompanied this file.
00009  * 
00010  * This package is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00017  */
00018 
00019 #include "CClientApp.h"
00020 #include "CLog.h"
00021 #include "CArch.h"
00022 #include "XSocket.h"
00023 #include "Version.h"
00024 #include "ProtocolTypes.h"
00025 #include "CString.h"
00026 #include "CScreen.h"
00027 #include "CEvent.h"
00028 #include "CClient.h"
00029 #include "CNetworkAddress.h"
00030 #include "IArchTaskBarReceiver.h"
00031 #include "IEventQueue.h"
00032 #include "TMethodEventJob.h"
00033 #include "CTCPSocketFactory.h"
00034 #include "XScreen.h"
00035 #include "LogOutputters.h"
00036 #include "CSocketMultiplexer.h"
00037 #include "CEventQueue.h"
00038 #include "CThread.h"
00039 #include "TMethodJob.h"
00040 
00041 #if SYSAPI_WIN32
00042 #include "CArchMiscWindows.h"
00043 #endif
00044 
00045 #if SYSAPI_WIN32 && GAME_DEVICE_SUPPORT
00046 #include <Windows.h>
00047 #include "XInputHook.h"
00048 #endif
00049 
00050 #if WINAPI_MSWINDOWS
00051 #include "CMSWindowsScreen.h"
00052 #elif WINAPI_XWINDOWS
00053 #include "CXWindowsScreen.h"
00054 #elif WINAPI_CARBON
00055 #include "COSXScreen.h"
00056 #endif
00057 
00058 #include <iostream>
00059 #include <stdio.h>
00060 
00061 #define RETRY_TIME 1.0
00062 
00063 CClientApp::CClientApp(CreateTaskBarReceiverFunc createTaskBarReceiver) :
00064 CApp(createTaskBarReceiver, new CArgs()),
00065 s_client(NULL),
00066 s_clientScreen(NULL)
00067 {
00068 }
00069 
00070 CClientApp::~CClientApp()
00071 {
00072 }
00073 
00074 CClientApp::CArgs::CArgs() :
00075 m_yscroll(0),
00076 m_serverAddress(NULL)
00077 {
00078 }
00079 
00080 CClientApp::CArgs::~CArgs()
00081 {
00082 }
00083 
00084 bool
00085 CClientApp::parseArg(const int& argc, const char* const* argv, int& i)
00086 {
00087     if (CApp::parseArg(argc, argv, i)) {
00088         // found common arg
00089         return true;
00090     }
00091 
00092     else if (isArg(i, argc, argv, NULL, "--camp")) {
00093         // ignore -- included for backwards compatibility
00094     }
00095 
00096     else if (isArg(i, argc, argv, NULL, "--no-camp")) {
00097         // ignore -- included for backwards compatibility
00098     }
00099 
00100     else if (isArg(i, argc, argv, NULL, "--yscroll", 1)) {
00101         // define scroll 
00102         args().m_yscroll = atoi(argv[++i]);
00103     }
00104 
00105     else {
00106         // option not supported here
00107         return false;
00108     }
00109 
00110     // argument was valid
00111     return true;
00112 }
00113 
00114 void
00115 CClientApp::parseArgs(int argc, const char* const* argv)
00116 {
00117     // asserts values, sets defaults, and parses args
00118     int i;
00119     CApp::parseArgs(argc, argv, i);
00120 
00121     // exactly one non-option argument (server-address)
00122     if (i == argc) {
00123         LOG((CLOG_PRINT "%s: a server address or name is required" BYE,
00124             args().m_pname, args().m_pname));
00125         m_bye(kExitArgs);
00126     }
00127     if (i + 1 != argc) {
00128         LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE,
00129             args().m_pname, argv[i], args().m_pname));
00130         m_bye(kExitArgs);
00131     }
00132 
00133     // save server address
00134     try {
00135         *args().m_serverAddress = CNetworkAddress(argv[i], kDefaultPort);
00136         args().m_serverAddress->resolve();
00137     }
00138     catch (XSocketAddress& e) {
00139         // allow an address that we can't look up if we're restartable.
00140         // we'll try to resolve the address each time we connect to the
00141         // server.  a bad port will never get better.  patch by Brent
00142         // Priddy.
00143         if (!args().m_restartable || e.getError() == XSocketAddress::kBadPort) {
00144             LOG((CLOG_PRINT "%s: %s" BYE,
00145                 args().m_pname, e.what(), args().m_pname));
00146             m_bye(kExitFailed);
00147         }
00148     }
00149 
00150     // set log filter
00151     if (!CLOG->setFilter(args().m_logFilter)) {
00152         LOG((CLOG_PRINT "%s: unrecognized log level `%s'" BYE,
00153             args().m_pname, args().m_logFilter, args().m_pname));
00154         m_bye(kExitArgs);
00155     }
00156 
00157     // identify system
00158     LOG((CLOG_INFO "%s Client on %s %s", kAppVersion, ARCH->getOSName().c_str(), ARCH->getPlatformName().c_str()));
00159 
00160     loggingFilterWarning();
00161 }
00162 
00163 void
00164 CClientApp::help()
00165 {
00166 #if WINAPI_XWINDOWS
00167 #  define WINAPI_ARG \
00168     " [--display <display>] [--no-xinitthreads]"
00169 #  define WINAPI_INFO \
00170     "      --display <display>  connect to the X server at <display>\n" \
00171     "      --no-xinitthreads    do not call XInitThreads()\n"
00172 #else
00173 #  define WINAPI_ARG
00174 #  define WINAPI_INFO
00175 #endif
00176 
00177     char buffer[2000];
00178     sprintf(
00179         buffer,
00180         "Usage: %s"
00181         " [--yscroll <delta>]"
00182         WINAPI_ARG
00183         HELP_SYS_ARGS
00184         HELP_COMMON_ARGS
00185         " <server-address>"
00186         "\n\n"
00187         "Connect to a synergy mouse/keyboard sharing server.\n"
00188         "\n"
00189         HELP_COMMON_INFO_1
00190         WINAPI_INFO
00191         HELP_SYS_INFO
00192         "      --yscroll <delta>    defines the vertical scrolling delta, which is\n"
00193         "                             120 by default.\n"
00194         HELP_COMMON_INFO_2
00195         "\n"
00196         "* marks defaults.\n"
00197         "\n"
00198         "The server address is of the form: [<hostname>][:<port>].  The hostname\n"
00199         "must be the address or hostname of the server.  The port overrides the\n"
00200         "default port, %d.\n",
00201         args().m_pname, kDefaultPort
00202     );
00203 
00204     LOG((CLOG_PRINT "%s", buffer));
00205 }
00206 
00207 const char*
00208 CClientApp::daemonName() const
00209 {
00210 #if SYSAPI_WIN32
00211     return "Synergy Client";
00212 #elif SYSAPI_UNIX
00213     return "synergyc";
00214 #endif
00215 }
00216 
00217 const char*
00218 CClientApp::daemonInfo() const
00219 {
00220 #if SYSAPI_WIN32
00221     return "Allows another computer to share it's keyboard and mouse with this computer.";
00222 #elif SYSAPI_UNIX
00223     return "";
00224 #endif
00225 }
00226 
00227 CScreen*
00228 CClientApp::createScreen()
00229 {
00230 #if WINAPI_MSWINDOWS
00231     return new CScreen(new CMSWindowsScreen(
00232         false, args().m_noHooks, args().m_gameDevice, args().m_stopOnDeskSwitch));
00233 #elif WINAPI_XWINDOWS
00234     return new CScreen(new CXWindowsScreen(
00235         args().m_display, false, args().m_disableXInitThreads,
00236         args().m_yscroll, *EVENTQUEUE));
00237 #elif WINAPI_CARBON
00238     return new CScreen(new COSXScreen(false));
00239 #endif
00240 }
00241 
00242 void
00243 CClientApp::updateStatus()
00244 {
00245     updateStatus("");
00246 }
00247 
00248 
00249 void
00250 CClientApp::updateStatus(const CString& msg)
00251 {
00252     if (m_taskBarReceiver)
00253     {
00254         m_taskBarReceiver->updateStatus(s_client, msg);
00255     }
00256 }
00257 
00258 
00259 void
00260 CClientApp::resetRestartTimeout()
00261 {
00262     // retry time can nolonger be changed
00263     //s_retryTime = 0.0;
00264 }
00265 
00266 
00267 double
00268 CClientApp::nextRestartTimeout()
00269 {
00270     // retry at a constant rate (Issue 52)
00271     return RETRY_TIME;
00272 
00273     /*
00274     // choose next restart timeout.  we start with rapid retries
00275     // then slow down.
00276     if (s_retryTime < 1.0) {
00277     s_retryTime = 1.0;
00278     }
00279     else if (s_retryTime < 3.0) {
00280     s_retryTime = 3.0;
00281     }
00282     else {
00283     s_retryTime = 5.0;
00284     }
00285     return s_retryTime;
00286     */
00287 }
00288 
00289 
00290 void
00291 CClientApp::handleScreenError(const CEvent&, void*)
00292 {
00293     LOG((CLOG_CRIT "error on screen"));
00294     EVENTQUEUE->addEvent(CEvent(CEvent::kQuit));
00295 }
00296 
00297 
00298 CScreen*
00299 CClientApp::openClientScreen()
00300 {
00301     CScreen* screen = createScreen();
00302     EVENTQUEUE->adoptHandler(IScreen::getErrorEvent(),
00303         screen->getEventTarget(),
00304         new TMethodEventJob<CClientApp>(
00305         this, &CClientApp::handleScreenError));
00306     return screen;
00307 }
00308 
00309 
00310 void
00311 CClientApp::closeClientScreen(CScreen* screen)
00312 {
00313     if (screen != NULL) {
00314         EVENTQUEUE->removeHandler(IScreen::getErrorEvent(),
00315             screen->getEventTarget());
00316         delete screen;
00317     }
00318 }
00319 
00320 
00321 void
00322 CClientApp::handleClientRestart(const CEvent&, void* vtimer)
00323 {
00324     // discard old timer
00325     CEventQueueTimer* timer = reinterpret_cast<CEventQueueTimer*>(vtimer);
00326     EVENTQUEUE->deleteTimer(timer);
00327     EVENTQUEUE->removeHandler(CEvent::kTimer, timer);
00328 
00329     // reconnect
00330     startClient();
00331 }
00332 
00333 
00334 void
00335 CClientApp::scheduleClientRestart(double retryTime)
00336 {
00337     // install a timer and handler to retry later
00338     LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime));
00339     CEventQueueTimer* timer = EVENTQUEUE->newOneShotTimer(retryTime, NULL);
00340     EVENTQUEUE->adoptHandler(CEvent::kTimer, timer,
00341         new TMethodEventJob<CClientApp>(this, &CClientApp::handleClientRestart, timer));
00342 }
00343 
00344 
00345 void
00346 CClientApp::handleClientConnected(const CEvent&, void*)
00347 {
00348     LOG((CLOG_NOTE "connected to server"));
00349     resetRestartTimeout();
00350     updateStatus();
00351 }
00352 
00353 
00354 void
00355 CClientApp::handleClientFailed(const CEvent& e, void*)
00356 {
00357     CClient::CFailInfo* info =
00358         reinterpret_cast<CClient::CFailInfo*>(e.getData());
00359 
00360     updateStatus(CString("Failed to connect to server: ") + info->m_what);
00361     if (!args().m_restartable || !info->m_retry) {
00362         LOG((CLOG_ERR "failed to connect to server: %s", info->m_what.c_str()));
00363         EVENTQUEUE->addEvent(CEvent(CEvent::kQuit));
00364     }
00365     else {
00366         LOG((CLOG_WARN "failed to connect to server: %s", info->m_what.c_str()));
00367         if (!m_suspended) {
00368             scheduleClientRestart(nextRestartTimeout());
00369         }
00370     }
00371     delete info;
00372 }
00373 
00374 
00375 void
00376 CClientApp::handleClientDisconnected(const CEvent&, void*)
00377 {
00378     LOG((CLOG_NOTE "disconnected from server"));
00379     if (!args().m_restartable) {
00380         EVENTQUEUE->addEvent(CEvent(CEvent::kQuit));
00381     }
00382     else if (!m_suspended) {
00383         s_client->connect();
00384     }
00385     updateStatus();
00386 }
00387 
00388 
00389 CClient*
00390 CClientApp::openClient(const CString& name, const CNetworkAddress& address, CScreen* screen, const CCryptoOptions& crypto)
00391 {
00392     CClient* client = new CClient(
00393         EVENTQUEUE, name, address, new CTCPSocketFactory, NULL, screen, crypto);
00394 
00395     try {
00396         EVENTQUEUE->adoptHandler(
00397             CClient::getConnectedEvent(),
00398             client->getEventTarget(),
00399             new TMethodEventJob<CClientApp>(this, &CClientApp::handleClientConnected));
00400 
00401         EVENTQUEUE->adoptHandler(
00402             CClient::getConnectionFailedEvent(),
00403             client->getEventTarget(),
00404             new TMethodEventJob<CClientApp>(this, &CClientApp::handleClientFailed));
00405 
00406         EVENTQUEUE->adoptHandler(
00407             CClient::getDisconnectedEvent(),
00408             client->getEventTarget(),
00409             new TMethodEventJob<CClientApp>(this, &CClientApp::handleClientDisconnected));
00410 
00411     } catch (std::bad_alloc &ba) {
00412         delete client;
00413         throw ba;
00414     }
00415 
00416     return client;
00417 }
00418 
00419 
00420 void
00421 CClientApp::closeClient(CClient* client)
00422 {
00423     if (client == NULL) {
00424         return;
00425     }
00426 
00427     EVENTQUEUE->removeHandler(CClient::getConnectedEvent(), client);
00428     EVENTQUEUE->removeHandler(CClient::getConnectionFailedEvent(), client);
00429     EVENTQUEUE->removeHandler(CClient::getDisconnectedEvent(), client);
00430     delete client;
00431 }
00432 
00433 int
00434 CClientApp::foregroundStartup(int argc, char** argv)
00435 {
00436     initApp(argc, argv);
00437 
00438     // never daemonize
00439     return mainLoop();
00440 }
00441 
00442 bool
00443 CClientApp::startClient()
00444 {
00445     double retryTime;
00446     CScreen* clientScreen = NULL;
00447     try {
00448         if (s_clientScreen == NULL) {
00449             clientScreen = openClientScreen();
00450             s_client     = openClient(args().m_name,
00451                 *args().m_serverAddress, clientScreen, args().m_crypto);
00452             s_clientScreen  = clientScreen;
00453             LOG((CLOG_NOTE "started client"));
00454         }
00455 
00456 #if SYSAPI_WIN32 && GAME_DEVICE_SUPPORT
00457         if (args().m_gameDevice.m_mode == CGameDeviceInfo::kGameModeXInput)
00458         {
00459             // TODO: currently this is failing because we're not
00460             // forcing compile with the DX XInput.h (so the win
00461             // SDK is being used)... we need to figure out how to
00462             // tell cmake to prefer the DX include path.
00463             LOG((CLOG_DEBUG "installing xinput hook"));
00464             InstallXInputHook();
00465         }
00466 #endif
00467 
00468         s_client->connect();
00469         updateStatus();
00470         return true;
00471     }
00472     catch (XScreenUnavailable& e) {
00473         LOG((CLOG_WARN "secondary screen unavailable: %s", e.what()));
00474         closeClientScreen(clientScreen);
00475         updateStatus(CString("secondary screen unavailable: ") + e.what());
00476         retryTime = e.getRetryTime();
00477     }
00478     catch (XScreenOpenFailure& e) {
00479         LOG((CLOG_CRIT "failed to start client: %s", e.what()));
00480         closeClientScreen(clientScreen);
00481         return false;
00482     }
00483     catch (XBase& e) {
00484         LOG((CLOG_CRIT "failed to start client: %s", e.what()));
00485         closeClientScreen(clientScreen);
00486         return false;
00487     }
00488 
00489     if (args().m_restartable) {
00490         scheduleClientRestart(retryTime);
00491         return true;
00492     }
00493     else {
00494         // don't try again
00495         return false;
00496     }
00497 }
00498 
00499 
00500 void
00501 CClientApp::stopClient()
00502 {
00503 #if SYSAPI_WIN32 && GAME_DEVICE_SUPPORT
00504     if (args().m_gameDevice.m_mode == CGameDeviceInfo::kGameModeXInput)
00505     {
00506         LOG((CLOG_DEBUG "removing xinput hook"));
00507         RemoveXInputHook();
00508     }
00509 #endif
00510 
00511     closeClient(s_client);
00512     closeClientScreen(s_clientScreen);
00513     s_client       = NULL;
00514     s_clientScreen = NULL;
00515 }
00516 
00517 
00518 int
00519 CClientApp::mainLoop()
00520 {
00521     // create socket multiplexer.  this must happen after daemonization
00522     // on unix because threads evaporate across a fork().
00523     CSocketMultiplexer multiplexer;
00524 
00525     // start client, etc
00526     appUtil().startNode();
00527     
00528     // init ipc client after node start, since create a new screen wipes out
00529     // the event queue (the screen ctors call adoptBuffer).
00530     if (argsBase().m_enableIpc) {
00531         initIpcClient();
00532     }
00533 
00534     // load all available plugins.
00535     ARCH->plugin().init(s_clientScreen->getEventTarget());
00536 
00537     // run event loop.  if startClient() failed we're supposed to retry
00538     // later.  the timer installed by startClient() will take care of
00539     // that.
00540     DAEMON_RUNNING(true);
00541     EVENTQUEUE->loop();
00542     DAEMON_RUNNING(false);
00543 
00544     // close down
00545     LOG((CLOG_DEBUG1 "stopping client"));
00546     stopClient();
00547     updateStatus();
00548     LOG((CLOG_NOTE "stopped client"));
00549 
00550     if (argsBase().m_enableIpc) {
00551         cleanupIpcClient();
00552     }
00553 
00554     return kExitSuccess;
00555 }
00556 
00557 static
00558 int
00559 daemonMainLoopStatic(int argc, const char** argv)
00560 {
00561     return CClientApp::instance().daemonMainLoop(argc, argv);
00562 }
00563 
00564 int
00565 CClientApp::standardStartup(int argc, char** argv)
00566 {
00567     initApp(argc, argv);
00568 
00569     // daemonize if requested
00570     if (args().m_daemon) {
00571         return ARCH->daemonize(daemonName(), &daemonMainLoopStatic);
00572     }
00573     else {
00574         return mainLoop();
00575     }
00576 }
00577 
00578 int
00579 CClientApp::runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup)
00580 {
00581     // general initialization
00582     args().m_serverAddress = new CNetworkAddress;
00583     args().m_pname         = ARCH->getBasename(argv[0]);
00584 
00585     // install caller's output filter
00586     if (outputter != NULL) {
00587         CLOG->insert(outputter);
00588     }
00589 
00590     int result;
00591     try
00592     {
00593         // run
00594         result = startup(argc, argv);
00595     }
00596     catch (...)
00597     {
00598         if (m_taskBarReceiver)
00599         {
00600             // done with task bar receiver
00601             delete m_taskBarReceiver;
00602         }
00603 
00604         delete args().m_serverAddress;
00605 
00606         throw;
00607     }
00608 
00609     return result;
00610 }
00611 
00612 void 
00613 CClientApp::startNode()
00614 {
00615     // start the client.  if this return false then we've failed and
00616     // we shouldn't retry.
00617     LOG((CLOG_DEBUG1 "starting client"));
00618     if (!startClient()) {
00619         m_bye(kExitFailed);
00620     }
00621 }

Generated on Wed Jun 19 2013 00:00:04 for Synergy by  doxygen 1.7.1