diff --git a/base/CLog.cpp b/base/CLog.cpp index a9098c8e..fb5f5efb 100644 --- a/base/CLog.cpp +++ b/base/CLog.cpp @@ -201,7 +201,7 @@ void CLog::output(int priority, char* msg) // print it CHoldLock lock(s_lock); if (s_outputter) { - s_outputter(msg + g_maxPriorityLength - n); + s_outputter(priority, msg + g_maxPriorityLength - n); } else { #if defined(CONFIG_PLATFORM_WIN32) diff --git a/base/CLog.h b/base/CLog.h index 6a1189bd..0e53e7c1 100644 --- a/base/CLog.h +++ b/base/CLog.h @@ -6,7 +6,18 @@ class CLog { public: - typedef void (*Outputter)(const char*); + enum { + kFATAL, + kERROR, + kWARNING, + kNOTE, + kINFO, + kDEBUG, + kDEBUG1, + kDEBUG2 + }; + + typedef void (*Outputter)(int priority, const char*); typedef void (*Lock)(bool lock); // diff --git a/server/CConfig.h b/server/CConfig.h index cbb5f9ae..22016182 100644 --- a/server/CConfig.h +++ b/server/CConfig.h @@ -30,7 +30,7 @@ private: public: CString m_neighbor[kLastDirection - kFirstDirection + 1]; }; - typedef std::map CCellMap; + typedef std::map CCellMap; public: typedef CCellMap::const_iterator internal_const_iterator; @@ -65,8 +65,8 @@ public: // manipulators - // note that case is preserved in screen names but has no effect - // FIXME -- make that true + // note that case is preserved in screen names but is ignored when + // comparing names. // add/remove screens void addScreen(const CString& name); diff --git a/server/CMSWindowsPrimaryScreen.cpp b/server/CMSWindowsPrimaryScreen.cpp index 6e084fdd..ae4ea098 100644 --- a/server/CMSWindowsPrimaryScreen.cpp +++ b/server/CMSWindowsPrimaryScreen.cpp @@ -141,7 +141,7 @@ log((CLOG_INFO "failed to release hot key: %d", GetLastError())); nextMark(); } -void CMSWindowsPrimaryScreen::leave() +bool CMSWindowsPrimaryScreen::leave() { log((CLOG_INFO "leaving primary")); assert(m_active == false); @@ -232,6 +232,8 @@ log((CLOG_INFO "failed to get hot key: %d", GetLastError())); // ignore } } + + return true; } void CMSWindowsPrimaryScreen::onConfigure() diff --git a/server/CMSWindowsPrimaryScreen.h b/server/CMSWindowsPrimaryScreen.h index a3f3afe0..fa54388c 100644 --- a/server/CMSWindowsPrimaryScreen.h +++ b/server/CMSWindowsPrimaryScreen.h @@ -19,7 +19,7 @@ public: virtual void open(CServer*); virtual void close(); virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute); - virtual void leave(); + virtual bool leave(); virtual void onConfigure(); virtual void warpCursor(SInt32 xAbsolute, SInt32 yAbsolute); virtual void setClipboard(ClipboardID, const IClipboard*); diff --git a/server/CServer.cpp b/server/CServer.cpp index 02ed0bd1..ad91dc1a 100644 --- a/server/CServer.cpp +++ b/server/CServer.cpp @@ -63,6 +63,7 @@ CServer::CServer() : m_cleanupSize(&m_mutex, 0), CServer::~CServer() { + // do nothing } void CServer::run() @@ -108,7 +109,9 @@ void CServer::run() cleanupThreads(); delete m_httpServer; m_httpServer = NULL; - closePrimaryScreen(); + if (m_primary != NULL) { + closePrimaryScreen(); + } } catch (XThread&) { // clean up @@ -709,6 +712,10 @@ void CServer::switchScreen(CScreenInfo* dst, log((CLOG_NOTE "switch from \"%s\" to \"%s\" at %d,%d", m_active->m_name.c_str(), dst->m_name.c_str(), x, y)); // FIXME -- we're not locked here but we probably should be + // record new position + m_x = x; + m_y = y; + // wrapping means leaving the active screen and entering it again. // since that's a waste of time we skip that and just warp the // mouse. @@ -716,18 +723,19 @@ void CServer::switchScreen(CScreenInfo* dst, // note if we're leaving the primary screen const bool leavingPrimary = (m_active->m_protocol == NULL); - // if leaving the primary screen then update the clipboards - // that it owns + // leave active screen if (leavingPrimary) { + if (!m_primary->leave()) { + // cannot leave primary screen + log((CLOG_WARN "can't leave primary screen")); + return; + } + + // update the clipboards that the primary screen owns for (ClipboardID id = 0; id < kClipboardEnd; ++id) { updatePrimaryClipboard(id); } } - - // leave active screen - if (leavingPrimary) { - m_primary->leave(); - } else { m_active->m_protocol->sendLeave(); } @@ -760,10 +768,6 @@ void CServer::switchScreen(CScreenInfo* dst, m_active->m_protocol->sendMouseMove(x, y); } } - - // record new position - m_x = x; - m_y = y; } CServer::CScreenInfo* CServer::getNeighbor(CScreenInfo* src, @@ -1333,8 +1337,10 @@ void CServer::openPrimaryScreen() m_primary->open(this); } catch (...) { - delete m_primary; - removeConnection(CString("primary"/* FIXME */)); + if (m_primary != NULL) { + removeConnection(CString("primary"/* FIXME */)); + delete m_primary; + } m_primary = NULL; m_primaryInfo = NULL; m_active = NULL; diff --git a/server/CXWindowsPrimaryScreen.cpp b/server/CXWindowsPrimaryScreen.cpp index 9fe5d801..9012e089 100644 --- a/server/CXWindowsPrimaryScreen.cpp +++ b/server/CXWindowsPrimaryScreen.cpp @@ -2,6 +2,7 @@ #include "CXWindowsClipboard.h" #include "CXWindowsUtil.h" #include "CServer.h" +#include "CStopwatch.h" #include "CThread.h" #include "CLog.h" #include @@ -253,7 +254,7 @@ void CXWindowsPrimaryScreen::enter(SInt32 x, SInt32 y) m_active = false; } -void CXWindowsPrimaryScreen::leave() +bool CXWindowsPrimaryScreen::leave() { log((CLOG_INFO "leaving primary")); assert(m_active == false); @@ -266,31 +267,43 @@ void CXWindowsPrimaryScreen::leave() // grab the mouse and keyboard. keep trying until we get them. // if we can't grab one after grabbing the other then ungrab - // and wait before retrying. + // and wait before retrying. give up after s_timeout seconds. + static const double s_timeout = 1.0; int result; + CStopwatch timer; do { - // mouse first + // keyboard first do { - result = XGrabPointer(display, m_window, True, 0, - GrabModeAsync, GrabModeAsync, - m_window, None, CurrentTime); - assert(result != GrabNotViewable); - if (result != GrabSuccess) { - log((CLOG_DEBUG2 "waiting to grab pointer")); - CThread::sleep(0.1); - } - } while (result != GrabSuccess); - log((CLOG_DEBUG2 "grabbed pointer")); - - // now the keyboard result = XGrabKeyboard(display, m_window, True, GrabModeAsync, GrabModeAsync, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) { + log((CLOG_DEBUG2 "waiting to grab keyboard")); + CThread::sleep(0.05); + if (timer.getTime() >= s_timeout) { + log((CLOG_DEBUG2 "grab keyboard timed out")); + XUnmapWindow(display, m_window); + return false; + } + } + } while (result != GrabSuccess); + log((CLOG_DEBUG2 "grabbed keyboard")); + + // now the mouse + result = XGrabPointer(display, m_window, True, 0, + GrabModeAsync, GrabModeAsync, + m_window, None, CurrentTime); assert(result != GrabNotViewable); if (result != GrabSuccess) { // back off to avoid grab deadlock - XUngrabPointer(display, CurrentTime); - log((CLOG_DEBUG2 "ungrabbed pointer, waiting to grab keyboard")); - CThread::sleep(0.1); + XUngrabKeyboard(display, CurrentTime); + log((CLOG_DEBUG2 "ungrabbed keyboard, waiting to grab pointer")); + CThread::sleep(0.05); + if (timer.getTime() >= s_timeout) { + log((CLOG_DEBUG2 "grab pointer timed out")); + XUnmapWindow(display, m_window); + return false; + } } } while (result != GrabSuccess); log((CLOG_DEBUG1 "grabbed pointer and keyboard")); @@ -302,6 +315,8 @@ void CXWindowsPrimaryScreen::leave() // local client now active m_active = true; + + return true; } void CXWindowsPrimaryScreen::onConfigure() diff --git a/server/CXWindowsPrimaryScreen.h b/server/CXWindowsPrimaryScreen.h index 0590deaa..aa30c327 100644 --- a/server/CXWindowsPrimaryScreen.h +++ b/server/CXWindowsPrimaryScreen.h @@ -17,7 +17,7 @@ public: virtual void open(CServer*); virtual void close(); virtual void enter(SInt32 xAbsolute, SInt32 yAbsolute); - virtual void leave(); + virtual bool leave(); virtual void onConfigure(); virtual void warpCursor(SInt32 xAbsolute, SInt32 yAbsolute); virtual void setClipboard(ClipboardID, const IClipboard*); diff --git a/server/server.cpp b/server/server.cpp index 3cc4a081..7ea320b2 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -5,14 +5,36 @@ #include "CNetwork.h" #include "CThread.h" #include "XThread.h" +#include "ProtocolTypes.h" #include "stdfstream.h" #include +static const char* s_copyright = "Copyright (C) 2002 Chris Schoeneman"; +static const SInt32 s_majorVersion = 0; +static const SInt32 s_minorVersion = 5; +static const char s_releaseVersion = ' '; + +// configuration file name +#if defined(CONFIG_PLATFORM_WIN32) +#define CONFIG_NAME "synergy.sgc" +#define CONFIG_USER_DIR "%HOME%/" +#define CONFIG_SYS_DIR "" +#elif defined(CONFIG_PLATFORM_UNIX) +#define CONFIG_NAME "synergy.conf" +#define CONFIG_USER_DIR "~/" +#define CONFIG_SYS_DIR "/etc/" +#endif + // -// config file stuff +// program arguments // -static const char* s_configFileName = "synergy.conf"; +static const char* pname = NULL; +static bool s_restartable = true; +static bool s_daemon = true; +static const char* s_configFile = NULL; +static const char* s_logFilter = NULL; +static CConfig s_config; // @@ -53,21 +75,15 @@ void realMain() // initialize network library CNetwork::init(); - // load configuration - CConfig config; - { - log((CLOG_DEBUG "opening configuration")); - std::ifstream configStream(s_configFileName); - if (!configStream) { - throw XConfigRead("cannot open configuration"); - } - configStream >> config; - log((CLOG_DEBUG "configuration read successfully")); + // if configuration has no screens then add this system + // as the default + if (s_config.begin() == s_config.end()) { + s_config.addScreen("primary"); } // run server server = new CServer(); - server->setConfig(config); + server->setConfig(s_config); server->run(); // clean up @@ -86,6 +102,202 @@ void realMain() } +// +// command line parsing +// + +static void bye() +{ + log((CLOG_PRINT "Try `%s --help' for more information.", pname)); + exit(1); +} + +static void version() +{ + log((CLOG_PRINT +"%s %d.%d%c protocol version %d.%d\n" +"%s", + pname, + s_majorVersion, + s_minorVersion, + s_releaseVersion, + kMajorVersion, + kMinorVersion, + s_copyright)); +} + +static void help() +{ + log((CLOG_PRINT +"Usage: %s" +" [--config ]" +" [--debug ]" +" [--daemon|--no-daemon]" +" [--restart|--no-restart]\n" +"Start the synergy mouse/keyboard sharing server.\n" +"\n" +" -c, --config use the named configuration file instead\n" +" where ~ represents the user's home directory.\n" +" -d, --debug filter out log messages with priorty below level.\n" +" level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n" +" DEBUG, DEBUG1, DEBUG2.\n" +" -f, --no-daemon run the server in the foreground.\n" +" --daemon run the server as a daemon.\n" +" -1, --no-restart do not try to restart the server if it fails for\n" +" some reason.\n" +" --restart restart the server automatically if it fails.\n" +" -h, --help display this help and exit.\n" +" --version display version information and exit.\n" +"\n" +"By default, the server is a restartable daemon. If no configuration file\n" +"pathname is provided then the first of the following to load sets the\n" +"configuration:\n" +" " CONFIG_USER_DIR CONFIG_NAME "\n" +" " CONFIG_SYS_DIR CONFIG_NAME "\n" +"If no configuration file can be loaded then the configuration uses its\n" +"defaults with just the server screen.\n" +"\n" +"Where log messages go depends on the platform and whether or not the\n" +"server is running as a daemon.", + pname)); + +} + +static bool loadConfig(const char* pathname, bool require) +{ + assert(pathname != NULL); + + try { + // load configuration + log((CLOG_DEBUG "opening configuration \"%s\"", pathname)); + std::ifstream configStream(pathname); + if (!configStream) { + throw XConfigRead("cannot open configuration"); + } + configStream >> s_config; + log((CLOG_DEBUG "configuration read successfully")); + return true; + } + catch (XConfigRead& e) { + if (require) { + log((CLOG_PRINT "%s: cannot read configuration '%s'", + pname, pathname)); + exit(1); + } + else { + log((CLOG_DEBUG "cannot read configuration \"%s\"", pathname)); + } + } + return false; +} + +static bool isArg(int argi, + int argc, char** argv, + const char* name1, + const char* name2, + int minRequiredParameters = 0) +{ + if ((name1 != NULL && strcmp(argv[argi], name1) == 0) || + (name2 != NULL && strcmp(argv[argi], name2) == 0)) { + // match. check args left. + if (argi + minRequiredParameters >= argc) { + log((CLOG_PRINT "%s: missing arguments for `%s'", + pname, argv[argi])); + bye(); + } + return true; + } + + // no match + return false; +} + +static void parse(int argc, char** argv) +{ + assert(pname != NULL); + assert(argv != NULL); + assert(argc >= 1); + + // parse options + int i; + for (i = 1; i < argc; ++i) { + if (isArg(i, argc, argv, "-d", "--debug", 1)) { + // change logging level + s_logFilter = argv[++i]; + } + + else if (isArg(i, argc, argv, "-c", "--config", 1)) { + // save configuration file path + s_configFile = argv[++i]; + } + + else if (isArg(i, argc, argv, "-f", "--no-daemon")) { + // not a daemon + s_daemon = false; + } + + else if (isArg(i, argc, argv, NULL, "--daemon")) { + // daemonize + s_daemon = true; + } + + else if (isArg(i, argc, argv, "-1", "--no-restart")) { + // don't try to restart + s_restartable = false; + } + + else if (isArg(i, argc, argv, NULL, "--restart")) { + // try to restart + s_restartable = true; + } + + else if (isArg(i, argc, argv, "-h", "--help")) { + help(); + exit(1); + } + + else if (isArg(i, argc, argv, NULL, "--version")) { + version(); + exit(1); + } + + else if (isArg(i, argc, argv, "--", NULL)) { + // remaining arguments are not options + ++i; + break; + } + + else if (argv[i][0] == '-') { + log((CLOG_PRINT "%s: unrecognized option `%s'", pname, argv[i])); + bye(); + } + + else { + // this and remaining arguments are not options + break; + } + } + + // no non-option arguments are allowed + if (i != argc) { + log((CLOG_PRINT "%s: unrecognized option `%s'", pname, argv[i])); + bye(); + } + + // set log filter + if (!CLog::setFilter(s_logFilter)) { + log((CLOG_PRINT "%s: unrecognized log level `%s'", pname, s_logFilter)); + bye(); + } + + // load the config file, if any + if (s_configFile != NULL) { + // require the user specified file to load correctly + loadConfig(s_configFile, true); + } +} + + // // platform dependent entry points // @@ -93,11 +305,39 @@ void realMain() #if defined(CONFIG_PLATFORM_WIN32) #include "CMSWindowsScreen.h" +#include int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) { CMSWindowsScreen::init(instance); + // get program name + pname = strrchr(argv[0], '/'); + if (pname == NULL) { + pname = argv[0]; + } + else { + ++pname; + } + const char* pname2 = strrchr(argv[0], '\\'); + if (pname2 != NULL && pname2 > pname) { + pname = pname2 + 1; + } + +// FIXME -- direct CLog to MessageBox + + parse(__argc, __argv); + +// FIXME -- undirect CLog from MessageBox +// FIXME -- if daemon then use win32 event log (however that's done), +// otherwise do what? want to use console window for debugging but +// not otherwise. + + // load the configuration file if we haven't already + if (s_configFile == NULL) { + } + +// FIXME if (__argc != 1) { CString msg = "no arguments allowed. exiting."; MessageBox(NULL, msg.c_str(), "error", MB_OK | MB_ICONERROR); @@ -121,30 +361,189 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) } } -#else +#elif defined(CONFIG_PLATFORM_UNIX) #include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char* s_configFileDefault = CONFIG_SYS_DIR CONFIG_NAME; + +static void daemonize() +{ + // fork so shell thinks we're done and so we're not a process + // group leader + switch (fork()) { + case -1: + // failed + log((CLOG_PRINT "failed to daemonize")); + exit(1); + + case 0: + // child + break; + + default: + // parent exits + exit(0); + } + + // become leader of a new session + setsid(); + + // chdir to root so we don't keep mounted filesystems points busy + chdir("/"); + + // mask off permissions for any but owner + umask(077); + + // close open files. we only expect stdin, stdout, stderr to be open. + close(0); + close(1); + close(2); + + // attach file descriptors 0, 1, 2 to /dev/null so inadvertent use + // of standard I/O safely goes in the bit bucket. + open("/dev/null", O_RDWR); + dup(0); + dup(0); +} + +static void syslogOutputter(int priority, const char* msg) +{ + // convert priority + switch (priority) { + case CLog::kFATAL: + case CLog::kERROR: + priority = LOG_ERR; + break; + + case CLog::kWARNING: + priority = LOG_WARNING; + break; + + case CLog::kNOTE: + priority = LOG_NOTICE; + break; + + case CLog::kINFO: + priority = LOG_INFO; + break; + + default: + priority = LOG_DEBUG; + break; + } + + // log it + syslog(priority, "%s", msg); +} int main(int argc, char** argv) { - if (argc != 1) { - fprintf(stderr, "usage: %s\n", argv[0]); - return 1; + // get program name + pname = strrchr(argv[0], '/'); + if (pname == NULL) { + pname = argv[0]; + } + else { + ++pname; } - try { - realMain(); - return 0; + // parse command line + parse(argc, argv); + + // load the configuration file if we haven't already + if (s_configFile == NULL) { + // get the user's home directory. use the effective user id + // so a user can't get a setuid root program to load his file. + bool loaded = false; + struct passwd* pwent = getpwuid(geteuid()); + if (pwent != NULL && pwent->pw_dir != NULL) { + // construct path if it isn't too long + if (strlen(pwent->pw_dir) + strlen(CONFIG_NAME) + 2 <= PATH_MAX) { + char path[PATH_MAX]; + strcpy(path, pwent->pw_dir); + strcat(path, "/"); + strcat(path, CONFIG_NAME); + + // now try loading the user's configuration + loaded = loadConfig(path, false); + } + } + if (!loaded) { + // try the system-wide config file + loadConfig(s_configFileDefault, false); + } } - catch (XBase& e) { - log((CLOG_CRIT "failed: %s", e.what())); - fprintf(stderr, "failed: %s\n", e.what()); - return 1; + + // daemonize if requested + if (s_daemon) { + daemonize(); + + // send log to syslog + openlog("synergy", 0, LOG_DAEMON); + CLog::setOutputter(&syslogOutputter); } - catch (XThread&) { - // terminated - return 1; + + // run the server. if running as a daemon then run it in a child + // process and restart it as necessary. we have to do this in case + // the X server restarts because our process cannot recover from + // that. + for (;;) { + // don't fork if not restartable + switch (s_restartable ? fork() : 0) { + default: { + // parent process. wait for child to exit. + int status; + if (wait(&status) == -1) { + // wait failed. this is unexpected so bail. + log((CLOG_CRIT "wait() failed")); + return 16; + } + + // what happened? if the child exited normally with a + // status less than 16 then the child was deliberately + // terminated so we also terminate. otherwise, we + // loop. + if (WIFEXITED(status) && WEXITSTATUS(status) < 16) { + return 0; + } + break; + } + + case -1: + // fork() failed. log the error and proceed as a child + log((CLOG_WARN "fork() failed; cannot automatically restart on error")); + // fall through + + case 0: + // child process + try { + realMain(); + return 0; + } + catch (XBase& e) { + log((CLOG_CRIT "failed: %s", e.what())); + fprintf(stderr, "failed: %s\n", e.what()); + return 16; + } + catch (XThread&) { + // terminated + return 1; + } + } } } +#else + +#error no main() for platform + #endif diff --git a/synergy/CXWindowsScreen.cpp b/synergy/CXWindowsScreen.cpp index 25602a4a..d2817c95 100644 --- a/synergy/CXWindowsScreen.cpp +++ b/synergy/CXWindowsScreen.cpp @@ -357,7 +357,7 @@ int CXWindowsScreen::ioErrorHandler(Display*) s_screen->m_display = NULL; s_screen->onUnexpectedClose(); log((CLOG_CRIT "quiting due to X display disconnection")); - exit(1); + exit(17); } diff --git a/synergy/IPrimaryScreen.h b/synergy/IPrimaryScreen.h index 464d85af..295bf37a 100644 --- a/synergy/IPrimaryScreen.h +++ b/synergy/IPrimaryScreen.h @@ -47,7 +47,8 @@ public: // called when the user navigates off the primary screen. hide // the cursor and grab exclusive access to the input devices. - virtual void leave() = 0; + // return true iff successful. + virtual bool leave() = 0; // called when the configuration has changed. subclasses may need // to adjust things (like the jump zones) after the configuration