diff --git a/all.dsp b/all.dsp index 902d872b..0c7635eb 100644 --- a/all.dsp +++ b/all.dsp @@ -23,8 +23,8 @@ CFG=all - Win32 Debug # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "millpond" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" MTL=midl.exe !IF "$(CFG)" == "all - Win32 Release" diff --git a/base/CLog.cpp b/base/CLog.cpp index 2b519a6e..70c2955e 100644 --- a/base/CLog.cpp +++ b/base/CLog.cpp @@ -201,10 +201,8 @@ void CLog::output(int priority, char* msg) // print it CHoldLock lock(s_lock); - if (s_outputter) { - s_outputter(priority, msg + g_maxPriorityLength - n); - } - else { + if (s_outputter == NULL || + !s_outputter(priority, msg + g_maxPriorityLength - n)) { #if defined(CONFIG_PLATFORM_WIN32) openConsole(); #endif diff --git a/base/CLog.h b/base/CLog.h index 0e53e7c1..22a34357 100644 --- a/base/CLog.h +++ b/base/CLog.h @@ -17,7 +17,11 @@ public: kDEBUG2 }; - typedef void (*Outputter)(int priority, const char*); + // type of outputter function. return false if CLog should use + // the default outputter, true otherwise. + typedef bool (*Outputter)(int priority, const char*); + + // type of lock/unlock function typedef void (*Lock)(bool lock); // diff --git a/base/base.dsp b/base/base.dsp index ef498104..b3495e9e 100644 --- a/base/base.dsp +++ b/base/base.dsp @@ -23,8 +23,8 @@ CFG=base - Win32 Debug # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "millpond" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" CPP=cl.exe RSC=rc.exe diff --git a/base/common.h b/base/common.h index 08f02331..c29d7543 100644 --- a/base/common.h +++ b/base/common.h @@ -27,6 +27,9 @@ #pragma warning(disable: 4786) // identifier truncated in debug info #pragma warning(disable: 4514) // unreferenced inline function removed +// this one's a little too aggressive +#pragma warning(disable: 4127) // conditional expression is constant + #endif // (_MSC_VER >= 1200) #else diff --git a/base/stdpre.h b/base/stdpre.h index 4461b475..c51292a4 100644 --- a/base/stdpre.h +++ b/base/stdpre.h @@ -8,4 +8,5 @@ #pragma warning(disable: 4284) #pragma warning(disable: 4146) // unary minus on unsigned value #pragma warning(disable: 4127) // conditional expression is constant +#pragma warning(disable: 4701) // variable possibly used uninitialized #endif diff --git a/client/CClient.cpp b/client/CClient.cpp index 09e78f6c..61ed3f54 100644 --- a/client/CClient.cpp +++ b/client/CClient.cpp @@ -120,6 +120,11 @@ void CClient::run(const CNetworkAddress& serverAddress) } } +void CClient::quit() +{ + m_screen->stop(); +} + void CClient::onClipboardChanged(ClipboardID id) { log((CLOG_DEBUG "sending clipboard %d changed", id)); diff --git a/client/CClient.h b/client/CClient.h index 9920a45c..30bcee50 100644 --- a/client/CClient.h +++ b/client/CClient.h @@ -19,8 +19,12 @@ public: // manipulators + // start the client. does not return until quit() is called. void run(const CNetworkAddress& serverAddress); + // tell client to exit gracefully + void quit(); + // handle events on client's screen void onClipboardChanged(ClipboardID); void onResolutionChanged(); diff --git a/client/CMSWindowsSecondaryScreen.cpp b/client/CMSWindowsSecondaryScreen.cpp index 26a97fa5..4cc19a4f 100644 --- a/client/CMSWindowsSecondaryScreen.cpp +++ b/client/CMSWindowsSecondaryScreen.cpp @@ -1,9 +1,12 @@ #include "CMSWindowsSecondaryScreen.h" #include "CMSWindowsClipboard.h" #include "CClient.h" +#include "CPlatform.h" #include "CClipboard.h" -#include "CThread.h" +#include "CLock.h" #include "CLog.h" +#include "CThread.h" +#include "XScreen.h" #include #include @@ -13,10 +16,18 @@ CMSWindowsSecondaryScreen::CMSWindowsSecondaryScreen() : m_client(NULL), + m_threadID(0), + m_desk(NULL), + m_deskName(), m_window(NULL), + m_active(false), m_nextClipboardWindow(NULL) { - // do nothing + m_is95Family = CPlatform::isWindows95Family(); + + // make sure this thread has a message queue + MSG dummy; + PeekMessage(&dummy, NULL, WM_USER, WM_USER, PM_NOREMOVE); } CMSWindowsSecondaryScreen::~CMSWindowsSecondaryScreen() @@ -26,21 +37,32 @@ CMSWindowsSecondaryScreen::~CMSWindowsSecondaryScreen() void CMSWindowsSecondaryScreen::run() { + // must call run() from same thread as open() + assert(m_threadID == GetCurrentThreadId()); + // change our priority CThread::getCurrentThread().setPriority(-7); - // save thread id - m_threadID = GetCurrentThreadId(); + // poll input desktop to see if it changes (onPreTranslate() + // handles WM_TIMER) + UINT timer = 0; + if (!m_is95Family) { + SetTimer(NULL, 0, 200, NULL); + } // run event loop log((CLOG_INFO "entering event loop")); doRun(); log((CLOG_INFO "exiting event loop")); + + // remove timer + if (!m_is95Family) { + KillTimer(NULL, timer); + } } void CMSWindowsSecondaryScreen::stop() { - log((CLOG_INFO "requesting event loop stop")); doStop(); } @@ -49,8 +71,6 @@ void CMSWindowsSecondaryScreen::open(CClient* client) assert(m_client == NULL); assert(client != NULL); - log((CLOG_INFO "opening screen")); - // set the client m_client = client; @@ -64,14 +84,16 @@ void CMSWindowsSecondaryScreen::open(CClient* client) // assume primary has all clipboards for (ClipboardID id = 0; id < kClipboardEnd; ++id) grabClipboard(id); + + // hide the cursor + m_active = true; + leave(); } void CMSWindowsSecondaryScreen::close() { assert(m_client != NULL); - log((CLOG_INFO "closing screen")); - // close the display closeDisplay(); @@ -82,12 +104,16 @@ void CMSWindowsSecondaryScreen::close() void CMSWindowsSecondaryScreen::enter( SInt32 x, SInt32 y, KeyModifierMask mask) { + CLock lock(&m_mutex); assert(m_window != NULL); + assert(m_active == false); log((CLOG_INFO "entering screen at %d,%d mask=%04x", x, y, mask)); - // attach thread input queues - AttachThreadInput(GetCurrentThreadId(), m_threadID, TRUE); + syncDesktop(); + + // now active + m_active = true; // update our keyboard state to reflect the local state updateKeys(); @@ -104,34 +130,25 @@ void CMSWindowsSecondaryScreen::enter( toggleKey(VK_SCROLL, KeyModifierScrollLock); } - // warp to requested location - SInt32 w, h; - getScreenSize(&w, &h); - mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, - (DWORD)((65535.99 * x) / (w - 1)), - (DWORD)((65535.99 * y) / (h - 1)), - 0, 0); - - // show cursor - log((CLOG_INFO "show cursor")); - ShowWindow(m_window, SW_HIDE); + // hide mouse + onEnter(x, y); } void CMSWindowsSecondaryScreen::leave() { + CLock lock(&m_mutex); assert(m_window != NULL); + assert(m_active == true); log((CLOG_INFO "leaving screen")); - // move hider window under the mouse (rather than moving the mouse - // somewhere else on the screen) - POINT point; - GetCursorPos(&point); - MoveWindow(m_window, point.x, point.y, 1, 1, FALSE); + syncDesktop(); - // raise and show the hider window. take activation. - log((CLOG_INFO "hide cursor")); - ShowWindow(m_window, SW_SHOWNORMAL); + // hide mouse + onLeave(); + + // not active anymore + m_active = false; // if we think we own the clipboard but we don't then somebody // grabbed the clipboard on this screen without us knowing. @@ -160,6 +177,10 @@ void CMSWindowsSecondaryScreen::keyDown( Keystrokes keys; UINT virtualKey; + CLock lock(&m_mutex); + assert(m_window != NULL); + syncDesktop(); + // get the sequence of keys to simulate key press and the final // modifier state. m_mask = mapKey(keys, virtualKey, key, mask, kPress); @@ -195,6 +216,10 @@ void CMSWindowsSecondaryScreen::keyRepeat( Keystrokes keys; UINT virtualKey; + CLock lock(&m_mutex); + assert(m_window != NULL); + syncDesktop(); + // get the sequence of keys to simulate key repeat and the final // modifier state. m_mask = mapKey(keys, virtualKey, key, mask, kRepeat); @@ -211,6 +236,10 @@ void CMSWindowsSecondaryScreen::keyUp( Keystrokes keys; UINT virtualKey; + CLock lock(&m_mutex); + assert(m_window != NULL); + syncDesktop(); + // get the sequence of keys to simulate key release and the final // modifier state. m_mask = mapKey(keys, virtualKey, key, mask, kRelease); @@ -263,6 +292,10 @@ void CMSWindowsSecondaryScreen::keyUp( void CMSWindowsSecondaryScreen::mouseDown(ButtonID button) { + CLock lock(&m_mutex); + assert(m_window != NULL); + syncDesktop(); + // map button id to button flag DWORD flags = mapButton(button, true); @@ -273,6 +306,10 @@ void CMSWindowsSecondaryScreen::mouseDown(ButtonID button) void CMSWindowsSecondaryScreen::mouseUp(ButtonID button) { + CLock lock(&m_mutex); + assert(m_window != NULL); + syncDesktop(); + // map button id to button flag DWORD flags = mapButton(button, false); @@ -281,8 +318,13 @@ void CMSWindowsSecondaryScreen::mouseUp(ButtonID button) mouse_event(flags, 0, 0, 0, 0); } -void CMSWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y) +void CMSWindowsSecondaryScreen::mouseMove( + SInt32 x, SInt32 y) { + CLock lock(&m_mutex); + assert(m_window != NULL); + syncDesktop(); + SInt32 w, h; getScreenSize(&w, &h); mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, @@ -293,20 +335,27 @@ void CMSWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y) void CMSWindowsSecondaryScreen::mouseWheel(SInt32 delta) { + CLock lock(&m_mutex); + assert(m_window != NULL); + syncDesktop(); + mouse_event(MOUSEEVENTF_WHEEL, 0, 0, delta, 0); } void CMSWindowsSecondaryScreen::setClipboard( - ClipboardID id, const IClipboard* src) + ClipboardID /*id*/, const IClipboard* src) { + CLock lock(&m_mutex); assert(m_window != NULL); CMSWindowsClipboard dst(m_window); CClipboard::copy(&dst, src); } -void CMSWindowsSecondaryScreen::grabClipboard(ClipboardID id) +void CMSWindowsSecondaryScreen::grabClipboard( + ClipboardID /*id*/) { + CLock lock(&m_mutex); assert(m_window != NULL); CMSWindowsClipboard clipboard(m_window); @@ -321,6 +370,10 @@ void CMSWindowsSecondaryScreen::getMousePos( assert(x != NULL); assert(y != NULL); + CLock lock(&m_mutex); + assert(m_window != NULL); + syncDesktop(); + POINT pos; if (GetCursorPos(&pos)) { *x = pos.x; @@ -344,8 +397,9 @@ SInt32 CMSWindowsSecondaryScreen::getJumpZoneSize() const } void CMSWindowsSecondaryScreen::getClipboard( - ClipboardID id, IClipboard* dst) const + ClipboardID /*id*/, IClipboard* dst) const { + CLock lock(&m_mutex); assert(m_window != NULL); CMSWindowsClipboard src(m_window); @@ -356,44 +410,59 @@ void CMSWindowsSecondaryScreen::onOpenDisplay() { assert(m_window == NULL); - // initialize clipboard owner to current owner. we don't want - // to take ownership of the clipboard just by starting up. - m_clipboardOwner = GetClipboardOwner(); + // save thread id. we'll need to pass this to the hook library. + m_threadID = GetCurrentThreadId(); - // create the cursor hiding window. this window is used to hide the - // cursor when it's not on the screen. the window is hidden as soon - // as the cursor enters the screen or the display's real cursor is - // moved. - m_window = CreateWindowEx(WS_EX_TOPMOST | - WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, - (LPCTSTR)getClass(), "Synergy", - WS_POPUP, - 0, 0, 1, 1, NULL, NULL, - getInstance(), - NULL); - - // hide the cursor - leave(); - - // install our clipboard snooper - m_nextClipboardWindow = SetClipboardViewer(m_window); + // get the input desktop and switch to it + if (m_is95Family) { + if (!openDesktop()) { + throw XScreenOpenFailure(); + } + } + else { + if (!switchDesktop(openInputDesktop())) { + throw XScreenOpenFailure(); + } + } } void CMSWindowsSecondaryScreen::onCloseDisplay() { - assert(m_window != NULL); + // disconnect from desktop + if (m_is95Family) { + closeDesktop(); + } + else { + switchDesktop(NULL); + } - // remove clipboard snooper - ChangeClipboardChain(m_window, m_nextClipboardWindow); - m_nextClipboardWindow = NULL; + // clear thread id + m_threadID = 0; - // destroy window - DestroyWindow(m_window); - m_window = NULL; + assert(m_window == NULL); + assert(m_desk == NULL); } bool CMSWindowsSecondaryScreen::onPreTranslate(MSG* msg) { + // handle event + switch (msg->message) { + case WM_TIMER: + // if current desktop is not the input desktop then switch to it + if (!m_is95Family) { + HDESK desk = openInputDesktop(); + if (desk != NULL) { + if (isCurrentDesktop(desk)) { + CloseDesktop(desk); + } + else { + switchDesktop(desk); + } + } + } + return true; + } + return false; } @@ -402,7 +471,21 @@ LRESULT CMSWindowsSecondaryScreen::onEvent( WPARAM wParam, LPARAM lParam) { switch (msg) { - // FIXME -- handle display changes + case WM_QUERYENDSESSION: + if (m_is95Family) { + return TRUE; + } + break; + + case WM_ENDSESSION: + if (m_is95Family) { + if (wParam == TRUE && lParam == 0) { + stop(); + } + return 0; + } + break; + case WM_PAINT: ValidateRect(hwnd, NULL); return 0; @@ -410,7 +493,6 @@ LRESULT CMSWindowsSecondaryScreen::onEvent( case WM_ACTIVATEAPP: if (wParam == FALSE) { // some other app activated. hide the hider window. - log((CLOG_INFO "show cursor")); ShowWindow(m_window, SW_HIDE); } break; @@ -423,9 +505,11 @@ LRESULT CMSWindowsSecondaryScreen::onEvent( // now notify client that somebody changed the clipboard (unless // we're now the owner, in which case it's because we took - // ownership). + // ownership, or now it's owned by nobody, which will happen if + // we owned it and switched desktops because we destroy our + // window to do that). m_clipboardOwner = GetClipboardOwner(); - if (m_clipboardOwner != m_window) { + if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) { m_client->onClipboardChanged(kClipboardClipboard); m_client->onClipboardChanged(kClipboardSelection); } @@ -448,6 +532,176 @@ LRESULT CMSWindowsSecondaryScreen::onEvent( return DefWindowProc(hwnd, msg, wParam, lParam); } +void CMSWindowsSecondaryScreen::onEnter(SInt32 x, SInt32 y) +{ + // warp to requested location + SInt32 w, h; + getScreenSize(&w, &h); + mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, + (DWORD)((65535.99 * x) / (w - 1)), + (DWORD)((65535.99 * y) / (h - 1)), + 0, 0); + + // show cursor + ShowWindow(m_window, SW_HIDE); +} + +void CMSWindowsSecondaryScreen::onLeave() +{ + // move hider window under the mouse (rather than moving the mouse + // somewhere else on the screen) + POINT point; + GetCursorPos(&point); + MoveWindow(m_window, point.x, point.y, 1, 1, FALSE); + + // raise and show the hider window. take activation. + ShowWindow(m_window, SW_SHOWNORMAL); +} + +bool CMSWindowsSecondaryScreen::openDesktop() +{ + CLock lock(&m_mutex); + + // initialize clipboard owner to current owner. we don't want + // to take ownership of the clipboard just by starting up. + m_clipboardOwner = GetClipboardOwner(); + + // create the cursor hiding window. this window is used to hide the + // cursor when it's not on the screen. the window is hidden as soon + // as the cursor enters the screen or the display's real cursor is + // moved. + m_window = CreateWindowEx(WS_EX_TOPMOST | + WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, + (LPCTSTR)getClass(), "Synergy", + WS_POPUP, + 0, 0, 1, 1, NULL, NULL, + getInstance(), + NULL); + + // install our clipboard snooper + m_nextClipboardWindow = SetClipboardViewer(m_window); + + return true; +} + +void CMSWindowsSecondaryScreen::closeDesktop() +{ + CLock lock(&m_mutex); + + if (m_window != NULL) { + // remove clipboard snooper + ChangeClipboardChain(m_window, m_nextClipboardWindow); + m_nextClipboardWindow = NULL; + + // destroy window + DestroyWindow(m_window); + m_window = NULL; + } +} + +bool CMSWindowsSecondaryScreen::switchDesktop(HDESK desk) +{ + CLock lock(&m_mutex); + + bool ownClipboard = false; + if (m_window != NULL) { + // note if we own the clipboard + ownClipboard = (m_clipboardOwner == m_window); + + // remove clipboard snooper + ChangeClipboardChain(m_window, m_nextClipboardWindow); + m_nextClipboardWindow = NULL; + + // destroy window + DestroyWindow(m_window); + m_window = NULL; + } + + // done with desktop + if (m_desk != NULL) { + CloseDesktop(m_desk); + m_desk = NULL; + m_deskName = ""; + } + + // if no new desktop then we're done + if (desk == NULL) { + log((CLOG_INFO "disconnecting desktop")); + return true; + } + + // set the desktop. can only do this when there are no windows + // and hooks on the current desktop owned by this thread. + if (SetThreadDesktop(desk) == 0) { + log((CLOG_ERR "failed to set desktop: %d", GetLastError())); + CloseDesktop(desk); + return false; + } + + // initialize clipboard owner to current owner. we don't want + // to take ownership of the clipboard just by starting up. + m_clipboardOwner = GetClipboardOwner(); + + // create the cursor hiding window. this window is used to hide the + // cursor when it's not on the screen. the window is hidden as soon + // as the cursor enters the screen or the display's real cursor is + // moved. + m_window = CreateWindowEx(WS_EX_TOPMOST | + WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, + (LPCTSTR)getClass(), "Synergy", + WS_POPUP, + 0, 0, 1, 1, NULL, NULL, + getInstance(), + NULL); + if (m_window == NULL) { + log((CLOG_ERR "failed to create window: %d", GetLastError())); + CloseDesktop(desk); + return false; + } + + // install our clipboard snooper + m_nextClipboardWindow = SetClipboardViewer(m_window); + + // if we owned the desktop then set the clipboard owner + if (ownClipboard) { + m_clipboardOwner = GetClipboardOwner(); + } + + // save new desktop + m_desk = desk; + m_deskName = getDesktopName(m_desk); + log((CLOG_INFO "switched to desktop %s", m_deskName.c_str())); + + // get desktop up to date + if (!m_active) { + onLeave(); + } + + return true; +} + +void CMSWindowsSecondaryScreen::syncDesktop() const +{ + // note -- mutex must be locked on entry + + DWORD threadID = GetCurrentThreadId(); + if (!m_is95Family) { + if (GetThreadDesktop(threadID) != m_desk) { + // FIXME -- this doesn't work. if we set a desktop then + // sending events doesn't work. + if (SetThreadDesktop(m_desk) == 0) { + log((CLOG_ERR "failed to set desktop: %d", GetLastError())); + } + } + } + AttachThreadInput(threadID, m_threadID, TRUE); +} + +CString CMSWindowsSecondaryScreen::getCurrentDesktopName() const +{ + return m_deskName; +} + // these tables map KeyID (a X windows KeySym) to virtual key codes. // if the key is an extended key then the entry is the virtual key // code | 0x100. keys that map to normal characters have a 0 entry diff --git a/client/CMSWindowsSecondaryScreen.h b/client/CMSWindowsSecondaryScreen.h index 258853b5..d8cce84b 100644 --- a/client/CMSWindowsSecondaryScreen.h +++ b/client/CMSWindowsSecondaryScreen.h @@ -3,7 +3,8 @@ #include "CMSWindowsScreen.h" #include "ISecondaryScreen.h" -#include "stdmap.h" +#include "CMutex.h" +#include "CString.h" #include "stdvector.h" class CMSWindowsSecondaryScreen : public CMSWindowsScreen, public ISecondaryScreen { @@ -39,6 +40,7 @@ protected: virtual LRESULT onEvent(HWND, UINT, WPARAM, LPARAM); virtual void onOpenDisplay(); virtual void onCloseDisplay(); + virtual CString getCurrentDesktopName() const; private: enum EKeyAction { kPress, kRelease, kRepeat }; @@ -49,7 +51,21 @@ private: bool m_repeat; }; typedef std::vector Keystrokes; - + + void onEnter(SInt32 x, SInt32 y); + void onLeave(); + + // open/close desktop (for windows 95/98/me) + bool openDesktop(); + void closeDesktop(); + + // make desk the thread desktop (for windows NT/2000/XP) + bool switchDesktop(HDESK desk); + + // get calling thread to use the input desktop + void syncDesktop() const; + + // key and button queries and operations DWORD mapButton(ButtonID button, bool press) const; KeyModifierMask mapKey(Keystrokes&, UINT& virtualKey, KeyID, KeyModifierMask, EKeyAction) const; @@ -63,14 +79,29 @@ private: void sendKeyEvent(UINT virtualKey, bool press); private: + CMutex m_mutex; CClient* m_client; + + // true if windows 95/98/me + bool m_is95Family; + + // the main loop's thread id + DWORD m_threadID; + + // the current desk and it's name + HDESK m_desk; + CString m_deskName; + + // our window (for getting clipboard changes) HWND m_window; + + // m_active is true if this screen has been entered + bool m_active; + + // clipboard stuff HWND m_nextClipboardWindow; HWND m_clipboardOwner; - // thread id of the event loop thread - DWORD m_threadID; - // virtual key states BYTE m_keys[256]; diff --git a/client/client.cpp b/client/client.cpp index da943c18..6297ad38 100644 --- a/client/client.cpp +++ b/client/client.cpp @@ -1,6 +1,8 @@ #include "CClient.h" #include "CString.h" #include "CLog.h" +#include "CCondVar.h" +#include "CLock.h" #include "CMutex.h" #include "CNetwork.h" #include "CNetworkAddress.h" @@ -11,6 +13,15 @@ #include "Version.h" #include +// platform dependent name of a daemon +#if defined(CONFIG_PLATFORM_WIN32) +#define DAEMON "service" +#define DAEMON_NAME "Synergy Client" +#elif defined(CONFIG_PLATFORM_UNIX) +#define DAEMON "daemon" +#define DAEMON_NAME "synergy" +#endif + // // program arguments // @@ -18,6 +29,8 @@ static const char* pname = NULL; static bool s_restartable = true; static bool s_daemon = true; +static bool s_install = false; +static bool s_uninstall = false; static const char* s_logFilter = NULL; static const char* s_serverName = NULL; @@ -42,44 +55,91 @@ static void logLock(bool lock) // -// main +// platform independent main // -void realMain(const CString& name, - const CString& hostname, - UInt16 port) +static CClient* s_client = NULL; + +static int realMain(CMutex* mutex) { - // initialize threading library - CThread::init(); + static const UInt16 port = 50001; // FIXME - // make logging thread safe - CMutex logMutex; - s_logMutex = &logMutex; - CLog::setLock(&logLock); - - CClient* client = NULL; try { - // initialize network library - CNetwork::init(); + // initialize threading library + CThread::init(); - // run client - CNetworkAddress addr(hostname, port); - client = new CClient(name); - client->run(addr); + // make logging thread safe + CMutex logMutex; + s_logMutex = &logMutex; + CLog::setLock(&logLock); - // clean up - delete client; - CNetwork::cleanup(); - CLog::setLock(NULL); - s_logMutex = NULL; + bool locked = true; + try { + // initialize network library + CNetwork::init(); + + // create client + CNetworkAddress addr(s_serverName, port); + s_client = new CClient("secondary"); // FIXME + + // run client + if (mutex != NULL) { + mutex->unlock(); + } + locked = false; + s_client->run(addr); + locked = true; + if (mutex != NULL) { + mutex->lock(); + } + + // clean up + delete s_client; + s_client = NULL; + CNetwork::cleanup(); + CLog::setLock(NULL); + s_logMutex = NULL; + } + catch (...) { + // clean up + if (!locked && mutex != NULL) { + mutex->lock(); + } + delete s_client; + s_client = NULL; + CNetwork::cleanup(); + CLog::setLock(NULL); + s_logMutex = NULL; + throw; + } } - catch (...) { - // clean up - delete client; - CNetwork::cleanup(); - CLog::setLock(NULL); - s_logMutex = NULL; - throw; + catch (XBase& e) { + log((CLOG_CRIT "failed: %s", e.what())); + return 16; + } + catch (XThread&) { + // terminated + return 1; + } + + return 0; +} + +static int restartMain() +{ + return realMain(NULL); +} + +// invoke realMain and wait for it. if s_restartable then keep +// restarting realMain until it returns a terminate code. +static int restartableMain() +{ + if (s_restartable) { + CPlatform platform; + return platform.restart(restartMain, 16); + } + else { + return realMain(NULL); } } @@ -88,11 +148,9 @@ void realMain(const CString& name, // command line parsing // -static void bye() -{ - log((CLOG_PRINT "Try `%s --help' for more information.", pname)); - exit(1); -} +#define BYE "\nTry `%s --help' for more information." + +static void (*bye)(int) = &exit; static void version() { @@ -113,32 +171,37 @@ static void help() log((CLOG_PRINT "Usage: %s" " [--debug ]" -" [--daemon|--no-daemon]" +" [--"DAEMON"|--no-"DAEMON"]" " [--restart|--no-restart]" +" [--install]" " \n" +"or\n" +" --uninstall\n" "Start the synergy mouse/keyboard sharing server.\n" "\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 client in the foreground.\n" -" --daemon run the client as a daemon.\n" +" -f, --no-"DAEMON" run the client in the foreground.\n" +"* --"DAEMON" run the client as a "DAEMON".\n" " -1, --no-restart do not try to restart the client if it fails for\n" " some reason.\n" -" --restart restart the client automatically if it fails.\n" +"* --restart restart the client automatically if it fails.\n" +" --install install server as a "DAEMON".\n" +" --uninstall uninstall server "DAEMON".\n" " -h, --help display this help and exit.\n" " --version display version information and exit.\n" "\n" -"By default, the client is a restartable daemon.\n" +"* marks defaults.\n" "\n" "Where log messages go depends on the platform and whether or not the\n" -"client is running as a daemon.", +"client is running as a "DAEMON".", pname)); } static bool isArg(int argi, - int argc, char** argv, + int argc, const char** argv, const char* name1, const char* name2, int minRequiredParameters = 0) @@ -147,9 +210,9 @@ static bool isArg(int argi, (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(); + log((CLOG_PRINT "%s: missing arguments for `%s'" BYE, + pname, argv[argi], pname)); + bye(2); } return true; } @@ -158,7 +221,7 @@ static bool isArg(int argi, return false; } -static void parse(int argc, char** argv) +static void parse(int argc, const char** argv) { assert(pname != NULL); assert(argv != NULL); @@ -172,12 +235,12 @@ static void parse(int argc, char** argv) s_logFilter = argv[++i]; } - else if (isArg(i, argc, argv, "-f", "--no-daemon")) { + else if (isArg(i, argc, argv, "-f", "--no-"DAEMON)) { // not a daemon s_daemon = false; } - else if (isArg(i, argc, argv, NULL, "--daemon")) { + else if (isArg(i, argc, argv, NULL, "--"DAEMON)) { // daemonize s_daemon = true; } @@ -194,12 +257,42 @@ static void parse(int argc, char** argv) else if (isArg(i, argc, argv, "-h", "--help")) { help(); - exit(1); + bye(0); } else if (isArg(i, argc, argv, NULL, "--version")) { version(); - exit(1); + bye(0); + } + + else if (isArg(i, argc, argv, NULL, "--install")) { +#if !defined(CONFIG_PLATFORM_WIN32) + log((CLOG_PRINT "%s: `%s' not permitted on this platform" BYE, + pname, argv[i], pname)); + bye(2); +#endif + s_install = true; + if (s_uninstall) { + log((CLOG_PRINT "%s: `--install' and `--uninstall'" + " are mutually exclusive" BYE, + pname, argv[i], pname)); + bye(2); + } + } + + else if (isArg(i, argc, argv, NULL, "--uninstall")) { +#if !defined(CONFIG_PLATFORM_WIN32) + log((CLOG_PRINT "%s: `%s' not permitted on this platform" BYE, + pname, argv[i], pname)); + bye(2); +#endif + s_uninstall = true; + if (s_install) { + log((CLOG_PRINT "%s: `--install' and `--uninstall'" + " are mutually exclusive" BYE, + pname, argv[i], pname)); + bye(2); + } } else if (isArg(i, argc, argv, "--", NULL)) { @@ -209,8 +302,9 @@ static void parse(int argc, char** argv) } else if (argv[i][0] == '-') { - log((CLOG_PRINT "%s: unrecognized option `%s'", pname, argv[i])); - bye(); + log((CLOG_PRINT "%s: unrecognized option `%s'" BYE, + pname, argv[i], pname)); + bye(2); } else { @@ -219,21 +313,51 @@ static void parse(int argc, char** argv) } } - // exactly one non-option argument: server-address - if (i == argc) { - log((CLOG_PRINT "%s: a server address or name is required", pname)); - bye(); + // exactly one non-option argument (server-address) unless using + // --uninstall. + if (s_uninstall) { + if (i != argc) { + log((CLOG_PRINT "%s: unrecognized option `%s' to `%s'" BYE, + pname, argv[i], pname, + s_install ? "--install" : "--uninstall")); + bye(2); + } } - if (i + 1 != argc) { - log((CLOG_PRINT "%s: unrecognized option `%s'", pname, argv[i])); - bye(); + else { + if (i == argc) { + log((CLOG_PRINT "%s: a server address or name is required" BYE, + pname, pname)); + bye(1); + } + if (i + 1 != argc) { + log((CLOG_PRINT "%s: unrecognized option `%s'" BYE, + pname, argv[i], pname)); + bye(2); + } + s_serverName = argv[i]; + } + + // increase default filter level for daemon. the user must + // explicitly request another level for a daemon. + if (s_daemon && s_logFilter == NULL) { +#if defined(CONFIG_PLATFORM_WIN32) + if (CPlatform::isWindows95Family()) { + // windows 95 has no place for logging so avoid showing + // the log console window. + s_logFilter = "FATAL"; + } + else +#endif + { + s_logFilter = "NOTE"; + } } - s_serverName = argv[i]; // set log filter if (!CLog::setFilter(s_logFilter)) { - log((CLOG_PRINT "%s: unrecognized log level `%s'", pname, s_logFilter)); - bye(); + log((CLOG_PRINT "%s: unrecognized log level `%s'" BYE, + pname, s_logFilter, pname)); + bye(2); } } @@ -245,7 +369,71 @@ static void parse(int argc, char** argv) #if defined(CONFIG_PLATFORM_WIN32) #include "CMSWindowsScreen.h" -#include + +static bool logMessageBox(int priority, const char* msg) +{ + if (priority <= CLog::kFATAL) { + MessageBox(NULL, msg, pname, MB_OK | MB_ICONWARNING); + return true; + } + else { + return false; + } +} + +static void byeThrow(int x) +{ + throw CWin32Platform::CDaemonFailed(x); +} + +static void daemonStop(void) +{ + s_client->quit(); +} + +static int daemonStartup(IPlatform* iplatform, + int argc, const char** argv) +{ + // get platform pointer + CWin32Platform* platform = static_cast(iplatform); + + // catch errors that would normally exit + bye = &byeThrow; + + // parse command line + s_install = false; + s_uninstall = false; + parse(argc, argv); + if (s_install || s_uninstall) { + // not allowed to install/uninstall from service + throw CWin32Platform::CDaemonFailed(1); + } + + // run as a service + return platform->runDaemon(realMain, daemonStop); +} + +static int daemonStartup95(IPlatform*, int, const char**) +{ + return realMain(NULL); +} + +static bool logDiscard(int, const char*) +{ + return true; +} + +static bool s_die = false; + +static void checkParse(int e) +{ + // anything over 1 means invalid args. 1 means missing args. + // 0 means graceful exit. we plan to exit for anything but + // 1 (missing args); the service control manager may supply + // the missing arguments so we don't exit in that case. + s_die = (e != 1); + throw s_die; +} int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) { @@ -255,40 +443,115 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) CMSWindowsScreen::init(instance); // get program name - pname = platform.getBasename(argv[0]); + pname = platform.getBasename(__argv[0]); -// 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. - -// FIXME + // parse command line without reporting errors but recording if + // the app would've exited. this is too avoid showing a dialog + // box if we're being started as a service because we shouldn't + // take too long to startup in that case. this mostly works but + // will choke if the service control manager passes --install + // or --uninstall (but that's unlikely). + CLog::setOutputter(&logDiscard); + bye = &checkParse; try { - realMain("secondary", s_serverName, 50001); + parse(__argc, const_cast(__argv)); + } + catch (...) { + // ignore + } + + // if we're not starting as an NT service then reparse the command + // line normally. + if (s_die || !s_daemon || s_install || s_uninstall || + CWin32Platform::isWindows95Family()) { + // send PRINT and FATAL output to a message box + CLog::setOutputter(&logMessageBox); + + // exit on bye + bye = &exit; + + // reparse + parse(__argc, const_cast(__argv)); + } + + // if starting as a daemon then we ignore the startup command line + // here. we'll parse the command line passed in when the service + // control manager calls us back. + else { + // do nothing + } + + // install/uninstall + if (s_install) { + // get the full path to this program + TCHAR path[MAX_PATH]; + if (GetModuleFileName(NULL, path, + sizeof(path) / sizeof(path[0])) == 0) { + log((CLOG_CRIT "cannot determine absolute path to program")); + return 16; + } + + // construct the command line to start the service with + CString commandLine = "--"DAEMON; + if (s_restartable) { + commandLine += " --restart"; + } + else { + commandLine += " --no-restart"; + } + if (s_logFilter != NULL) { + commandLine += " --debug "; + commandLine += s_logFilter; + } + commandLine += " "; + commandLine += s_serverName; + + // install + if (!platform.installDaemon(DAEMON_NAME, + "Shares this system's mouse and keyboard with others.", + path, commandLine.c_str())) { + log((CLOG_CRIT "failed to install service")); + return 16; + } + log((CLOG_PRINT "installed successfully")); return 0; } - catch (XBase& e) { - log((CLOG_CRIT "failed: %s", e.what())); - CString msg = "failed: "; - msg += e.what(); - MessageBox(NULL, msg.c_str(), "error", MB_OK | MB_ICONERROR); - return 1; + else if (s_uninstall) { + if (!platform.uninstallDaemon(DAEMON_NAME)) { + log((CLOG_CRIT "failed to uninstall service")); + return 16; + } + log((CLOG_PRINT "uninstalled successfully")); + return 0; } - catch (XThread&) { - // terminated - return 1; + + // daemonize if requested + int result; + if (s_daemon) { + if (CWin32Platform::isWindows95Family()) { + result = platform.daemonize(DAEMON_NAME, &daemonStartup95); + } + else { + result = platform.daemonize(DAEMON_NAME, &daemonStartup); + } + if (result == -1) { + log((CLOG_CRIT "failed to start as a service")); + return 16; + } } + else { + result = restartableMain(); + } + + return result; } #elif defined(CONFIG_PLATFORM_UNIX) -#include -#include -#include +static int daemonStartup(IPlatform*, int, const char**) +{ + return restartableMain(); +} int main(int argc, char** argv) { @@ -298,63 +561,22 @@ int main(int argc, char** argv) pname = platform.getBasename(argv[0]); // parse command line - parse(argc, argv); + parse(argc, const_cast(argv)); // daemonize if requested + int result; if (s_daemon) { - if (!platform.daemonize("synergy")) { + result = platform.daemonize(DAEMON_NAME, &daemonStartup); + if (result == -1) { log((CLOG_CRIT "failed to daemonize")); return 16; } } - - // 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("secondary", s_serverName, 50001); - return 0; - } - catch (XBase& e) { - log((CLOG_CRIT "failed: %s", e.what())); - return 16; - } - catch (XThread&) { - // terminated - return 1; - } - } + else { + result = restartableMain(); } + + return result; } #else diff --git a/client/client.dsp b/client/client.dsp index 682006b2..e5755909 100644 --- a/client/client.dsp +++ b/client/client.dsp @@ -23,8 +23,8 @@ CFG=client - Win32 Debug # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "millpond" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" CPP=cl.exe MTL=midl.exe RSC=rc.exe @@ -40,6 +40,7 @@ RSC=rc.exe # PROP Use_Debug_Libraries 0 # PROP Output_Dir "../Release" # PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c # ADD CPP /nologo /MT /W4 /GX /O2 /I "..\base" /I "..\io" /I "..\mt" /I "..\net" /I "..\synergy" /I "..\platform" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c @@ -53,7 +54,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 -# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"../Release/synergy.exe" !ELSEIF "$(CFG)" == "client - Win32 Debug" @@ -66,6 +67,7 @@ LINK32=link.exe # PROP Use_Debug_Libraries 1 # PROP Output_Dir "../Debug" # PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c # ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "..\base" /I "..\io" /I "..\mt" /I "..\net" /I "..\synergy" /I "..\platform" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FD /GZ /c @@ -79,7 +81,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept -# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /out:"../Debug/synergy.exe" /pdbtype:sept !ENDIF diff --git a/http/http.dsp b/http/http.dsp index 75173bef..2330ca65 100755 --- a/http/http.dsp +++ b/http/http.dsp @@ -23,8 +23,8 @@ CFG=http - Win32 Debug # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "millpond" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" CPP=cl.exe RSC=rc.exe diff --git a/io/io.dsp b/io/io.dsp index 58df7ddc..b53b3236 100644 --- a/io/io.dsp +++ b/io/io.dsp @@ -23,8 +23,8 @@ CFG=io - Win32 Debug # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "millpond" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" CPP=cl.exe RSC=rc.exe diff --git a/mt/CThreadRep.cpp b/mt/CThreadRep.cpp index 4a0f99f4..6e54080f 100644 --- a/mt/CThreadRep.cpp +++ b/mt/CThreadRep.cpp @@ -614,17 +614,9 @@ unsigned int __stdcall CThreadRep::threadFunc(void* arg) { CThreadRep* rep = (CThreadRep*)arg; - // initialize OLE - const HRESULT hr = OleInitialize(NULL); - // run thread rep->doThreadFunc(); - // close OLE - if (!FAILED(hr)) { - OleUninitialize(); - } - // signal termination SetEvent(rep->m_exit); diff --git a/mt/mt.dsp b/mt/mt.dsp index 0c5dab8d..baa08de9 100644 --- a/mt/mt.dsp +++ b/mt/mt.dsp @@ -23,8 +23,8 @@ CFG=mt - Win32 Debug # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "millpond" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" CPP=cl.exe RSC=rc.exe diff --git a/net/net.dsp b/net/net.dsp index 1b5ff922..2e11719b 100644 --- a/net/net.dsp +++ b/net/net.dsp @@ -23,8 +23,8 @@ CFG=net - Win32 Debug # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "millpond" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" CPP=cl.exe RSC=rc.exe diff --git a/platform/CMSWindowsScreen.cpp b/platform/CMSWindowsScreen.cpp index 7cd34a53..83e11704 100644 --- a/platform/CMSWindowsScreen.cpp +++ b/platform/CMSWindowsScreen.cpp @@ -152,6 +152,38 @@ void CMSWindowsScreen::getScreenSize( *h = m_h; } +HDESK CMSWindowsScreen::openInputDesktop() const +{ + return OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, TRUE, + DESKTOP_CREATEWINDOW | + DESKTOP_HOOKCONTROL | + GENERIC_WRITE); +} + +CString CMSWindowsScreen::getDesktopName( + HDESK desk) const +{ + if (desk == NULL) { + return CString(); + } + else { + DWORD size; + GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size); + TCHAR* name = new TCHAR[size / sizeof(TCHAR) + 1]; + GetUserObjectInformation(desk, UOI_NAME, name, size, &size); + CString result(name); + delete[] name; + return result; + } +} + +bool CMSWindowsScreen::isCurrentDesktop( + HDESK desk) const +{ + return CStringUtil::CaselessCmp::equal(getDesktopName(desk), + getCurrentDesktopName()); +} + void CMSWindowsScreen::getEvent(MSG* msg) const { // wait for an event in a cancellable way diff --git a/platform/CMSWindowsScreen.h b/platform/CMSWindowsScreen.h index 510ed3c6..90c00b95 100644 --- a/platform/CMSWindowsScreen.h +++ b/platform/CMSWindowsScreen.h @@ -18,6 +18,11 @@ public: static void init(HINSTANCE); + // accessors + + // get the application instance handle + static HINSTANCE getInstance(); + protected: // runs an event loop and returns when WM_QUIT is received void doRun(); @@ -35,9 +40,7 @@ protected: // is closed. void closeDisplay(); - // get the application instance handle and the registered window - // class atom - static HINSTANCE getInstance(); + // get the registered window class atom ATOM getClass() const; // update screen size cache @@ -46,6 +49,17 @@ protected: // get the size of the screen void getScreenSize(SInt32* w, SInt32* h) const; + // get the input desktop. caller must CloseDesktop() the result. + // do not call under windows 95/98/me. + HDESK openInputDesktop() const; + + // get the desktop's name. do not call under windows 95/98/me. + CString getDesktopName(HDESK) const; + + // returns true iff desk is the current desk. do not call under + // windows 95/98/me. + bool isCurrentDesktop(HDESK desk) const; + // wait for and get the next message. cancellable. void getEvent(MSG*) const; @@ -62,6 +76,9 @@ protected: // called by closeDisplay() to virtual void onCloseDisplay() = 0; + // called by isCurrentDesktop() to get the current desktop name + virtual CString getCurrentDesktopName() const = 0; + private: static LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM); diff --git a/platform/CUnixPlatform.cpp b/platform/CUnixPlatform.cpp index ff519a92..63fb77d2 100644 --- a/platform/CUnixPlatform.cpp +++ b/platform/CUnixPlatform.cpp @@ -5,9 +5,11 @@ #include #include #include +#include #include #include + // // CUnixPlatform // @@ -22,26 +24,31 @@ CUnixPlatform::~CUnixPlatform() // do nothing } -bool CUnixPlatform::installDaemon(/* FIXME */) +bool CUnixPlatform::installDaemon( + const char*, + const char*, + const char*, + const char*) { // daemons don't require special installation return true; } -bool CUnixPlatform::uninstallDaemon(/* FIXME */) +bool CUnixPlatform::uninstallDaemon(const char*) { // daemons don't require special installation return true; } -bool CUnixPlatform::daemonize(const char* name) +int CUnixPlatform::daemonize( + const char* name, DaemonFunc func) { // fork so shell thinks we're done and so we're not a process // group leader switch (fork()) { case -1: // failed - return false; + return -1; case 0: // child @@ -75,7 +82,44 @@ bool CUnixPlatform::daemonize(const char* name) // hook up logger setDaemonLogger(name); - return true; + // invoke function + return func(this, 1, &name); +} + +int CUnixPlatform::restart( + RestartFunc func, int minErrorCode) +{ + for (;;) { + switch (fork()) { + 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 minErrorCode; + } + + // 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) < minErrorCode) { + return WEXITSTATUS(status); + } + 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 + return func(); + } + } } const char* CUnixPlatform::getBasename(const char* pathname) const @@ -117,7 +161,9 @@ CString CUnixPlatform::addPathComponent( CString path; path.reserve(prefix.size() + 1 + suffix.size()); path += prefix; - path += '/'; + if (path.size() == 0 || path[path.size() - 1] != '/') { + path += '/'; + } path += suffix; return path; } @@ -128,7 +174,7 @@ void CUnixPlatform::setDaemonLogger(const char* name) CLog::setOutputter(&CUnixPlatform::deamonLogger); } -void CUnixPlatform::deamonLogger( +bool CUnixPlatform::deamonLogger( int priority, const char* msg) { // convert priority @@ -157,4 +203,5 @@ void CUnixPlatform::deamonLogger( // log it syslog(priority, "%s", msg); + return true; } diff --git a/platform/CUnixPlatform.h b/platform/CUnixPlatform.h index c449e3ed..94bc4a05 100644 --- a/platform/CUnixPlatform.h +++ b/platform/CUnixPlatform.h @@ -9,9 +9,13 @@ public: virtual ~CUnixPlatform(); // IPlatform overrides - virtual bool installDaemon(/* FIXME */); - virtual bool uninstallDaemon(/* FIXME */); - virtual bool daemonize(const char* name); + virtual bool installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine); + virtual bool uninstallDaemon(const char* name); + virtual int daemonize(const char* name, DaemonFunc); + virtual int restart(RestartFunc, int minErrorCode); virtual const char* getBasename(const char* pathname) const; virtual CString getUserDirectory() const; virtual CString getSystemDirectory() const; @@ -23,7 +27,7 @@ protected: virtual void setDaemonLogger(const char* name); private: - static void deamonLogger(int, const char*); + static bool deamonLogger(int, const char*); }; #endif diff --git a/platform/CWin32Platform.cpp b/platform/CWin32Platform.cpp index b0ee15d5..9e71a141 100644 --- a/platform/CWin32Platform.cpp +++ b/platform/CWin32Platform.cpp @@ -1,12 +1,20 @@ #include "CWin32Platform.h" +#include "CLock.h" +#include "CThread.h" #include "CLog.h" +#include "stdvector.h" #include -#include +#include +#include +#include // // CWin32Platform // +HANDLE CWin32Platform::s_eventLog = NULL; +CWin32Platform* CWin32Platform::s_daemonPlatform = NULL; + CWin32Platform::CWin32Platform() { // do nothing @@ -17,22 +25,282 @@ CWin32Platform::~CWin32Platform() // do nothing } -bool CWin32Platform::installDaemon(/* FIXME */) +bool CWin32Platform::isWindows95Family() { - // FIXME - return false; + OSVERSIONINFO version; + version.dwOSVersionInfoSize = sizeof(version); + if (GetVersionEx(&version) == 0) { + log((CLOG_WARN "cannot determine OS: %d", GetLastError())); + return true; + } + return (version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); } -bool CWin32Platform::uninstallDaemon(/* FIXME */) +void CWin32Platform::setStatus( + SERVICE_STATUS_HANDLE handle, + DWORD state) { - // FIXME - return false; + setStatus(handle, state, 0, 0); } -bool CWin32Platform::daemonize(const char* name) +void CWin32Platform::setStatus( + SERVICE_STATUS_HANDLE handle, + DWORD state, DWORD step, DWORD waitHint) { - // FIXME - return false; + SERVICE_STATUS status; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS; + status.dwCurrentState = state; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_PAUSE_CONTINUE | + SERVICE_ACCEPT_SHUTDOWN; + status.dwWin32ExitCode = NO_ERROR; + status.dwServiceSpecificExitCode = 0; + status.dwCheckPoint = step; + status.dwWaitHint = waitHint; + SetServiceStatus(handle, &status); +} + +void CWin32Platform::setStatusError( + SERVICE_STATUS_HANDLE handle, + DWORD error) +{ + SERVICE_STATUS status; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS; + status.dwCurrentState = SERVICE_STOPPED; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_PAUSE_CONTINUE | + SERVICE_ACCEPT_SHUTDOWN; + status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + status.dwServiceSpecificExitCode = error; + status.dwCheckPoint = 0; + status.dwWaitHint = 0; + SetServiceStatus(handle, &status); +} + +bool CWin32Platform::installDaemon( + const char* name, + const char* description, + const char* pathname, + const char* commandLine) +{ + // windows 95 family services + if (isWindows95Family()) { + // open registry + HKEY key = open95ServicesKey(); + if (key == NULL) { + log((CLOG_ERR "cannot open RunServices registry key", GetLastError())); + return false; + } + + // construct entry + CString value; + value += "\""; + value += pathname; + value += "\" "; + value += commandLine; + + // install entry + setValue(key, name, value); + + // clean up + closeKey(key); + + return true; + } + + // windows NT family services + else { + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + log((CLOG_ERR "OpenSCManager failed with %d", GetLastError())); + return false; + } + + // create the servie + SC_HANDLE service = CreateService(mgr, + name, + name, + 0, + SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + pathname, + NULL, + NULL, + NULL, + NULL, + NULL); + + // done with service and manager + if (service != NULL) { + CloseServiceHandle(service); + CloseServiceHandle(mgr); + } + else { + log((CLOG_ERR "CreateService failed with %d", GetLastError())); + CloseServiceHandle(mgr); + return false; + } + + // open the registry key for this service + HKEY key = openNTServicesKey(); + key = openKey(key, name); + if (key == NULL) { + // can't open key + uninstallDaemon(name); + return false; + } + + // set the description + setValue(key, "Description", description); + + // set command line + key = openKey(key, "Parameters"); + if (key == NULL) { + // can't open key + uninstallDaemon(name); + return false; + } + setValue(key, "CommandLine", commandLine); + + // done with registry + closeKey(key); + + return true; + } +} + +bool CWin32Platform::uninstallDaemon(const char* name) +{ + // windows 95 family services + if (isWindows95Family()) { + // open registry + HKEY key = open95ServicesKey(); + if (key == NULL) { + log((CLOG_ERR "cannot open RunServices registry key", GetLastError())); + return false; + } + + // remove entry + deleteValue(key, name); + + // clean up + closeKey(key); + + return true; + } + + // windows NT family services + else { + // remove parameters for this service + HKEY key = openNTServicesKey(); + key = openKey(key, name); + if (key != NULL) { + deleteKey(key, "Parameters"); + closeKey(key); + } + + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + log((CLOG_ERR "OpenSCManager failed with %d", GetLastError())); + return false; + } + + // open the service. oddly, you must open a service to delete it. + bool success; + SC_HANDLE service = OpenService(mgr, name, DELETE); + if (service == NULL) { + log((CLOG_ERR "OpenService failed with %d", GetLastError())); + success = false; + } + + else { + success = (DeleteService(service) != 0); + CloseServiceHandle(service); + } + + // close the manager + CloseServiceHandle(mgr); + + return success; + } +} + +int CWin32Platform::daemonize( + const char* name, + DaemonFunc func) +{ + assert(name != NULL); + assert(func != NULL); + + // windows 95 family services + if (isWindows95Family()) { + typedef DWORD (WINAPI *RegisterServiceProcessT)(DWORD, DWORD); + + // mark this process as a service so it's not killed when the + // user logs off. + HINSTANCE kernel = LoadLibrary("kernel32.dll"); + if (kernel == NULL) { + log((CLOG_ERR "LoadLibrary failed with %d", GetLastError())); + return -1; + } + RegisterServiceProcessT RegisterServiceProcess = + reinterpret_cast( + GetProcAddress(kernel, + _T("RegisterServiceProcess"))); + if (RegisterServiceProcess == NULL) { + log((CLOG_ERR "can't lookup RegisterServiceProcess: %d", GetLastError())); + FreeLibrary(kernel); + return -1; + } + if (RegisterServiceProcess(NULL, 1) == 0) { + log((CLOG_ERR "RegisterServiceProcess failed with %d", GetLastError())); + FreeLibrary(kernel); + return -1; + } + FreeLibrary(kernel); + + // now simply call the daemon function + return func(this, 1, &name); + } + + // windows NT family services + else { + // save daemon function + m_daemonFunc = func; + + // construct the service entry + SERVICE_TABLE_ENTRY entry[2]; + entry[0].lpServiceName = const_cast(name); + entry[0].lpServiceProc = &CWin32Platform::serviceMainEntry; + entry[1].lpServiceName = NULL; + entry[1].lpServiceProc = NULL; + + // hook us up to the service control manager. this won't return + // (if successful) until the processes have terminated. + s_daemonPlatform = this; + if (StartServiceCtrlDispatcher(entry)) { + s_daemonPlatform = NULL; + return m_daemonResult; + } + log((CLOG_ERR "StartServiceCtrlDispatcher failed with %d", GetLastError())); + s_daemonPlatform = NULL; + return -1; + } +} + +int CWin32Platform::restart( + RestartFunc func, int /*minErrorCode*/) +{ + // FIXME -- start in separate process or thread. note that this + // isn't too critical as win32 doesn't force us to terminate for + // any reason so we should never have to restart. + return func(); } const char* CWin32Platform::getBasename(const char* pathname) const @@ -61,14 +329,58 @@ const char* CWin32Platform::getBasename(const char* pathname) const CString CWin32Platform::getUserDirectory() const { - // FIXME - return CString(); + // try %HOMEPATH% + TCHAR dir[MAX_PATH]; + DWORD size = sizeof(dir) / sizeof(TCHAR); + DWORD result = GetEnvironmentVariable(_T("HOMEPATH"), dir, size); + if (result != 0 && result <= size) { + // sanity check -- if dir doesn't appear to start with a + // drive letter and isn't a UNC name then don't use it + // FIXME -- allow UNC names + if (dir[0] != '\0' && (dir[1] == ':' || + ((dir[0] == '\\' || dir[0] == '/') && + (dir[1] == '\\' || dir[1] == '/')))) { + return dir; + } + } + + // get the location of the personal files. that's as close to + // a home directory as we're likely to find. + ITEMIDLIST* idl; + if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_PERSONAL, &idl))) { + TCHAR* path = NULL; + if (SHGetPathFromIDList(idl, dir)) { + DWORD attr = GetFileAttributes(dir); + if (attr != 0xffffffff && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + path = dir; + } + + IMalloc* shalloc; + if (SUCCEEDED(SHGetMalloc(&shalloc))) { + shalloc->Free(idl); + shalloc->Release(); + } + + if (path != NULL) { + return path; + } + } + + // use root of C drive as a default + return "C:"; } CString CWin32Platform::getSystemDirectory() const { - // FIXME - return ""; + // get windows directory + char dir[MAX_PATH]; + if (GetWindowsDirectory(dir, sizeof(dir)) != 0) { + return dir; + } + else { + // can't get it. use C:\ as a default. + return "C:"; + } } CString CWin32Platform::addPathComponent( @@ -78,13 +390,453 @@ CString CWin32Platform::addPathComponent( CString path; path.reserve(prefix.size() + 1 + suffix.size()); path += prefix; - path += '\\'; + if (path.size() == 0 || + (path[path.size() - 1] != '\\' && + path[path.size() - 1] != '/')) { + path += '\\'; + } path += suffix; return path; } -void CWin32Platform::serviceLogger( +HKEY CWin32Platform::openKey( + HKEY key, const char* keyName) +{ + // open next key + HKEY newKey; + LONG result = RegOpenKeyEx(key, keyName, 0, + KEY_WRITE | KEY_QUERY_VALUE, &newKey); + if (result != ERROR_SUCCESS) { + DWORD disp; + result = RegCreateKeyEx(key, keyName, 0, _T(""), + 0, KEY_WRITE | KEY_QUERY_VALUE, + NULL, &newKey, &disp); + } + if (result != ERROR_SUCCESS) { + RegCloseKey(key); + return NULL; + } + + // switch to new key + RegCloseKey(key); + return newKey; +} + +HKEY CWin32Platform::openKey( + HKEY key, const char** keyNames) +{ + for (UInt32 i = 0; key != NULL && keyNames[i] != NULL; ++i) { + // open next key + key = openKey(key, keyNames[i]); + } + return key; +} + +void CWin32Platform::closeKey(HKEY key) +{ + assert(key != NULL); + RegCloseKey(key); +} + +void CWin32Platform::deleteKey(HKEY key, const char* name) +{ + assert(key != NULL); + assert(name != NULL); + RegDeleteKey(key, name); +} + +void CWin32Platform::deleteValue(HKEY key, const char* name) +{ + assert(key != NULL); + assert(name != NULL); + RegDeleteValue(key, name); +} + +void CWin32Platform::setValue(HKEY key, + const char* name, const CString& value) +{ + assert(key != NULL); + assert(name != NULL); + RegSetValueEx(key, name, 0, REG_SZ, + reinterpret_cast(value.c_str()), + value.size() + 1); +} + +CString CWin32Platform::readValueString(HKEY key, + const char* name) +{ + // get the size of the string + DWORD type; + DWORD size = 0; + LONG result = RegQueryValueEx(key, name, 0, &type, NULL, &size); + if (result != ERROR_SUCCESS || type != REG_SZ) { + return CString(); + } + + // allocate space + char* buffer = new char[size]; + + // read it + result = RegQueryValueEx(key, name, 0, &type, + reinterpret_cast(buffer), &size); + if (result != ERROR_SUCCESS || type != REG_SZ) { + delete[] buffer; + return CString(); + } + + // clean up and return value + CString value(buffer); + delete[] buffer; + return value; +} + +HKEY CWin32Platform::openNTServicesKey() +{ + static const char* s_keyNames[] = { + _T("SYSTEM"), + _T("CurrentControlSet"), + _T("Services"), + NULL + }; + + return openKey(HKEY_LOCAL_MACHINE, s_keyNames); +} + +HKEY CWin32Platform::open95ServicesKey() +{ + static const char* s_keyNames[] = { + _T("Software"), + _T("Microsoft"), + _T("Windows"), + _T("CurrentVersion"), + _T("RunServices"), + NULL + }; + + return openKey(HKEY_LOCAL_MACHINE, s_keyNames); +} + +int CWin32Platform::runDaemon(RunFunc run, StopFunc stop) +{ + // should only be called from DaemonFunc + assert(m_serviceMutex != NULL); + + CLock lock(m_serviceMutex); + try { + int result; + m_stop = stop; + m_serviceHandlerWaiting = false; + m_serviceRunning = false; + for (;;) { + // mark server as running + setStatus(m_statusHandle, SERVICE_RUNNING); + + // run callback + m_serviceRunning = true; + result = run(m_serviceMutex); + m_serviceRunning = false; + + // notify handler that the server stopped. if handler + // isn't waiting then we stopped unexpectedly and we + // quit. + if (m_serviceHandlerWaiting) { + m_serviceHandlerWaiting = false; + m_serviceState->broadcast(); + } + else { + break; + } + + // wait until we're told what to do next + while (*m_serviceState != SERVICE_RUNNING && + *m_serviceState != SERVICE_STOPPED) { + m_serviceState->wait(); + } + + // exit loop if we've been told to stop + if (*m_serviceState == SERVICE_STOPPED) { + break; + } + } + + // prevent daemonHandler from changing state + *m_serviceState = SERVICE_STOPPED; + + // tell service control that the service is stopped. + // FIXME -- hopefully this will ensure that our handler won't + // be called again but i can't find documentation that + // verifies that. if it does it'll crash on the mutex that + // we're about to destroy. + setStatus(m_statusHandle, *m_serviceState); + + // clean up + m_stop = NULL; + + return result; + } + catch (...) { + // FIXME -- report error + + // prevent serviceHandler from changing state + *m_serviceState = SERVICE_STOPPED; + + // set status + setStatusError(m_statusHandle, 0); + + // wake up serviceHandler if it's waiting then wait for it + if (m_serviceHandlerWaiting) { + m_serviceHandlerWaiting = false; + m_serviceState->broadcast(); + m_serviceState->wait(); + // serviceHandler has exited by now + } + + throw; + } +} + +void CWin32Platform::serviceMain( + DWORD argc, LPTSTR* argvIn) +{ + typedef std::vector ArgList; + typedef std::vector Arguments; + const char** argv = const_cast(argvIn); + + // open event log and direct log messages to it + if (s_eventLog == NULL) { + s_eventLog = RegisterEventSource(NULL, argv[0]); + if (s_eventLog != NULL) { + CLog::setOutputter(&CWin32Platform::serviceLogger); + } + } + + // create synchronization objects + CThread::init(); + m_serviceMutex = new CMutex; + m_serviceState = new CCondVar(m_serviceMutex, SERVICE_RUNNING); + + // register our service handler functiom + m_statusHandle = RegisterServiceCtrlHandler(argv[0], + &CWin32Platform::serviceHandlerEntry); + if (m_statusHandle == NULL) { + // cannot start as service + m_daemonResult = -1; + delete m_serviceState; + delete m_serviceMutex; + return; + } + + // tell service control manager that we're starting + setStatus(m_statusHandle, SERVICE_START_PENDING, 0, 1000); + + // if no arguments supplied then try getting them from the registry. + // the first argument doesn't count because it's the service name. + Arguments args; + ArgList myArgv; + if (argc <= 1) { + // read command line + CString commandLine; + HKEY key = openNTServicesKey(); + key = openKey(key, argv[0]); + key = openKey(key, "Parameters"); + if (key != NULL) { + commandLine = readValueString(key, "CommandLine"); + } + + // if the command line isn't empty then parse and use it + if (!commandLine.empty()) { + // parse, honoring double quoted substrings + CString::size_type i = commandLine.find_first_not_of(" \t"); + while (i != CString::npos && i != commandLine.size()) { + // find end of string + CString::size_type e; + if (commandLine[i] == '\"') { + // quoted. find closing quote. + ++i; + e = commandLine.find("\"", i); + + // whitespace must follow closing quote + if (e == CString::npos || + (e + 1 != commandLine.size() && + commandLine[e + 1] != ' ' && + commandLine[e + 1] != '\t')) { + args.clear(); + break; + } + + // extract + args.push_back(commandLine.substr(i, e - i)); + i = e + 1; + } + else { + // unquoted. find next whitespace. + e = commandLine.find_first_of(" \t", i); + if (e == CString::npos) { + e = commandLine.size(); + } + + // extract + args.push_back(commandLine.substr(i, e - i)); + i = e + 1; + } + + // next argument + i = commandLine.find_first_not_of(" \t", i); + } + + // service name goes first + myArgv.push_back(argv[0]); + + // get pointers + for (UInt32 i = 0; i < args.size(); ++i) { + myArgv.push_back(args[i].c_str()); + } + + // adjust argc/argv + argc = myArgv.size(); + argv = &myArgv[0]; + } + } + + try { + // invoke daemon function + m_daemonResult = m_daemonFunc(this, static_cast(argc), argv); + } + catch (CDaemonFailed& e) { + setStatusError(m_statusHandle, e.m_result); + m_daemonResult = -1; + } + catch (...) { + setStatusError(m_statusHandle, 1); + m_daemonResult = -1; + } + + // clean up + delete m_serviceState; + delete m_serviceMutex; + + // FIXME -- close event log? +} + +void WINAPI CWin32Platform::serviceMainEntry( + DWORD argc, LPTSTR* argv) +{ + s_daemonPlatform->serviceMain(argc, argv); +} + +void CWin32Platform::serviceHandler(DWORD ctrl) +{ + assert(m_serviceMutex != NULL); + assert(m_serviceState != NULL); + + CLock lock(m_serviceMutex); + + // ignore request if service is already stopped + if (*m_serviceState == SERVICE_STOPPED) { + setStatus(m_statusHandle, *m_serviceState); + return; + } + + switch (ctrl) { + case SERVICE_CONTROL_PAUSE: + // update state + *m_serviceState = SERVICE_PAUSE_PENDING; + setStatus(m_statusHandle, *m_serviceState, 0, 1000); + + // stop run callback if running and wait for it to finish + if (m_serviceRunning) { + m_serviceHandlerWaiting = true; + m_stop(); + m_serviceState->wait(); + } + + // update state if service hasn't stopped while we were waiting + if (*m_serviceState != SERVICE_STOPPED) { + *m_serviceState = SERVICE_PAUSED; + } + m_serviceState->broadcast(); + break; + + case SERVICE_CONTROL_CONTINUE: + // required status update + setStatus(m_statusHandle, *m_serviceState); + + // update state but let main loop send RUNNING notification + *m_serviceState = SERVICE_RUNNING; + m_serviceState->broadcast(); + return; + + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + // update state + *m_serviceState = SERVICE_STOP_PENDING; + setStatus(m_statusHandle, *m_serviceState, 0, 1000); + + // stop run callback if running and wait for it to finish + if (m_serviceRunning) { + m_serviceHandlerWaiting = true; + m_stop(); + m_serviceState->wait(); + } + + // update state + *m_serviceState = SERVICE_STOPPED; + m_serviceState->broadcast(); + break; + + default: + log((CLOG_WARN "unknown service command: %d", ctrl)); + // fall through + + case SERVICE_CONTROL_INTERROGATE: + break; + } + + // send update + setStatus(m_statusHandle, *m_serviceState); +} + +void WINAPI CWin32Platform::serviceHandlerEntry(DWORD ctrl) +{ + s_daemonPlatform->serviceHandler(ctrl); +} + +bool CWin32Platform::serviceLogger( int priority, const char* msg) { - // FIXME + if (s_eventLog == NULL) { + return false; + } + + // convert priority + WORD type; + switch (priority) { + case CLog::kFATAL: + case CLog::kERROR: + type = EVENTLOG_ERROR_TYPE; + break; + + case CLog::kWARNING: + type = EVENTLOG_WARNING_TYPE; + break; + + default: + type = EVENTLOG_INFORMATION_TYPE; + break; + } + + // log it + // FIXME -- win32 wants to use a message table to look up event + // strings. log messages aren't organized that way so we'll + // just dump our string into the raw data section of the event + // so users can at least see the message. note that we use our + // priority as the event category. + ReportEvent(s_eventLog, type, static_cast(priority), + 0, // event ID + NULL, + 0, + strlen(msg + 1), // raw data size + NULL, + const_cast(msg));// raw data + return true; } diff --git a/platform/CWin32Platform.h b/platform/CWin32Platform.h index 3a680be0..33f657b9 100644 --- a/platform/CWin32Platform.h +++ b/platform/CWin32Platform.h @@ -2,16 +2,52 @@ #define CWIN32PLATFORM_H #include "IPlatform.h" +#include "CCondVar.h" +#include "CMutex.h" +#include class CWin32Platform : public IPlatform { public: + typedef int (*RunFunc)(CMutex*); + typedef void (*StopFunc)(void); + CWin32Platform(); virtual ~CWin32Platform(); + // returns true iff the platform is win95/98/me + static bool isWindows95Family(); + + // utility for calling SetServiceStatus() + static void setStatus(SERVICE_STATUS_HANDLE, DWORD state); + static void setStatus(SERVICE_STATUS_HANDLE, + DWORD state, DWORD step, DWORD waitHint); + static void setStatusError(SERVICE_STATUS_HANDLE, DWORD error); + + // run a service. the RunFunc should unlock the passed in mutex + // (which will be locked on entry) when not initializing or + // shutting down (i.e. when running its loop). StopFunc should + // cause the RunFunc() to return. returns what RunFunc returns. + // RunFunc should throw CDaemonFailed if the service fails. + int runDaemon(RunFunc, StopFunc); + + // thrown by RunFunc on service failure. result is the error + // code reported by the service. + class CDaemonFailed { + public: + CDaemonFailed(int result) : m_result(result) { } + + public: + int m_result; + }; + // IPlatform overrides - virtual bool installDaemon(/* FIXME */); - virtual bool uninstallDaemon(/* FIXME */); - virtual bool daemonize(const char* name); + virtual bool installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine); + virtual bool uninstallDaemon(const char* name); + virtual int daemonize(const char* name, DaemonFunc); + virtual int restart(RestartFunc, int minErrorCode); virtual const char* getBasename(const char* pathname) const; virtual CString getUserDirectory() const; virtual CString getSystemDirectory() const; @@ -20,7 +56,40 @@ public: const CString& suffix) const; private: - static void serviceLogger(int, const char*); + static HKEY openKey(HKEY parent, const char*); + static HKEY openKey(HKEY parent, const char**); + static void closeKey(HKEY); + static void deleteKey(HKEY, const char* name); + static void deleteValue(HKEY, const char* name); + static void setValue(HKEY, const char* name, + const CString& value); + static CString readValueString(HKEY, const char* name); + static HKEY openNTServicesKey(); + static HKEY open95ServicesKey(); + + void serviceMain(DWORD, LPTSTR*); + static void WINAPI serviceMainEntry(DWORD, LPTSTR*); + + void serviceHandler(DWORD ctrl); + static void WINAPI serviceHandlerEntry(DWORD ctrl); + + static bool serviceLogger(int, const char*); + +private: + DaemonFunc m_daemonFunc; + int m_daemonResult; + + SERVICE_STATUS_HANDLE m_statusHandle; + + CMutex* m_serviceMutex; + CCondVar* m_serviceState; + bool m_serviceHandlerWaiting; + bool m_serviceRunning; + StopFunc m_stop; + + static HANDLE s_eventLog; + + static CWin32Platform* s_daemonPlatform; }; #endif diff --git a/platform/IPlatform.h b/platform/IPlatform.h index 73769abd..7012304e 100644 --- a/platform/IPlatform.h +++ b/platform/IPlatform.h @@ -7,19 +7,48 @@ class IPlatform : public IInterface { public: + typedef int (*DaemonFunc)(IPlatform*, int argc, const char** argv); + typedef int (*RestartFunc)(); + // manipulators - // install/uninstall a daemon. + // install/uninstall a daemon. commandLine should *not* + // include the name of program as the first argument. // FIXME -- throw on error? will get better error messages that way. - virtual bool installDaemon(/* FIXME */) = 0; - virtual bool uninstallDaemon(/* FIXME */) = 0; + virtual bool installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine) = 0; + virtual bool uninstallDaemon(const char* name) = 0; // daemonize. this should have the side effect of sending log // messages to a system message logger since messages can no // longer go to the console. returns true iff successful. // the name is the name of the daemon. -// FIXME -- win32 services will require a more complex interface - virtual bool daemonize(const char* name) = 0; + + // daemonize. this should have the side effect of sending log + // messages to a system message logger since messages can no + // longer go to the console. name is the name of the daemon. + // once daemonized, func is invoked and daemonize returns when + // and what func does. daemonize() returns -1 on error. + // + // exactly what happens when daemonizing depends on the platform. + // unix: + // detaches from terminal. func gets one argument, the name + // passed to daemonize(). + // win32: + // becomes a service. argument 0 is the name of the service + // and the rest are the arguments passed to StartService(). + // func is only called when the service is actually started. + // func must behave like a proper ServiceMain() function; in + // particular, it must call RegisterServiceCtrlHandler() and + // SetServiceStatus(). + virtual int daemonize(const char* name, DaemonFunc func) = 0; + + // continually restart the given function in a separate process + // or thread until it exits normally with a code less than the + // given code then return the code. + virtual int restart(RestartFunc, int minErrorCode) = 0; // accessors diff --git a/platform/platform.dsp b/platform/platform.dsp new file mode 100644 index 00000000..1a7efbca --- /dev/null +++ b/platform/platform.dsp @@ -0,0 +1,139 @@ +# Microsoft Developer Studio Project File - Name="platform" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=platform - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "platform.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "platform.mak" CFG="platform - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "platform - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "platform - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "platform - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MT /W4 /GX /O2 /I "..\base" /I "..\mt" /I "..\synergy" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "platform - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "..\base" /I "..\mt" /I "..\synergy" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "platform - Win32 Release" +# Name "platform - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\CMSWindowsClipboard.cpp +# End Source File +# Begin Source File + +SOURCE=.\CMSWindowsScreen.cpp +# End Source File +# Begin Source File + +SOURCE=.\CPlatform.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\CMSWindowsClipboard.h +# End Source File +# Begin Source File + +SOURCE=.\CMSWindowsScreen.h +# End Source File +# Begin Source File + +SOURCE=.\CPlatform.h +# End Source File +# Begin Source File + +SOURCE=.\CWin32Platform.h +# End Source File +# Begin Source File + +SOURCE=.\IPlatform.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# Begin Group "Included Files" + +# PROP Default_Filter "inc" +# Begin Source File + +SOURCE=.\CWin32Platform.cpp +# PROP Exclude_From_Build 1 +# End Source File +# End Group +# End Target +# End Project diff --git a/server/CMSWindowsPrimaryScreen.cpp b/server/CMSWindowsPrimaryScreen.cpp index ae4ea098..cc83079a 100644 --- a/server/CMSWindowsPrimaryScreen.cpp +++ b/server/CMSWindowsPrimaryScreen.cpp @@ -1,10 +1,11 @@ #include "CMSWindowsPrimaryScreen.h" #include "CMSWindowsClipboard.h" #include "CServer.h" -#include "CSynergyHook.h" +#include "CPlatform.h" +#include "XScreen.h" #include "XSynergy.h" -#include "CThread.h" #include "CLog.h" +#include "CThread.h" #include #include @@ -14,38 +15,76 @@ CMSWindowsPrimaryScreen::CMSWindowsPrimaryScreen() : m_server(NULL), - m_active(false), + m_threadID(0), + m_desk(NULL), + m_deskName(), m_window(NULL), - m_nextClipboardWindow(NULL), - m_clipboardOwner(NULL), - m_hookLibrary(NULL), + m_active(false), m_mark(0), - m_markReceived(0) + m_markReceived(0), + m_nextClipboardWindow(NULL), + m_clipboardOwner(NULL) { - // detect operating system - OSVERSIONINFO version; - version.dwOSVersionInfoSize = sizeof(version); - if (GetVersionEx(&version) == 0) { - log((CLOG_WARN "cannot determine OS: %d", GetLastError())); + // load the hook library + m_hookLibrary = LoadLibrary("synrgyhk"); + if (m_hookLibrary == NULL) { + log((CLOG_ERR "failed to load hook library")); + throw XScreenOpenFailure(); } - m_is95Family = (version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); + m_setZone = (SetZoneFunc)GetProcAddress(m_hookLibrary, "setZone"); + m_setRelay = (SetRelayFunc)GetProcAddress(m_hookLibrary, "setRelay"); + m_install = (InstallFunc)GetProcAddress(m_hookLibrary, "install"); + m_uninstall = (UninstallFunc)GetProcAddress(m_hookLibrary, "uninstall"); + if (m_setZone == NULL || + m_setRelay == NULL || + m_install == NULL || + m_uninstall == NULL) { + log((CLOG_ERR "invalid hook library")); + FreeLibrary(m_hookLibrary); + throw XScreenOpenFailure(); + } + + // detect operating system + m_is95Family = CPlatform::isWindows95Family(); + + // make sure this thread has a message queue + MSG dummy; + PeekMessage(&dummy, NULL, WM_USER, WM_USER, PM_NOREMOVE); } CMSWindowsPrimaryScreen::~CMSWindowsPrimaryScreen() { + assert(m_hookLibrary != NULL); assert(m_window == NULL); - assert(m_hookLibrary == NULL); + + // done with hook library + FreeLibrary(m_hookLibrary); } void CMSWindowsPrimaryScreen::run() { + // must call run() from same thread as open() + assert(m_threadID == GetCurrentThreadId()); + // change our priority CThread::getCurrentThread().setPriority(-3); + // poll input desktop to see if it changes (preTranslateMessage() + // handles WM_TIMER) + UINT timer = 0; + if (!m_is95Family) { + SetTimer(NULL, 0, 200, NULL); + } + // run event loop log((CLOG_INFO "entering event loop")); doRun(); log((CLOG_INFO "exiting event loop")); + + // remove timer + if (!m_is95Family) { + KillTimer(NULL, timer); + } } void CMSWindowsPrimaryScreen::stop() @@ -64,16 +103,25 @@ void CMSWindowsPrimaryScreen::open(CServer* server) // open the display openDisplay(); - // get keyboard state - updateKeys(); + // initialize marks + m_mark = 0; + m_markReceived = 0; + nextMark(); // send screen info SInt32 w, h; getScreenSize(&w, &h); m_server->setInfo(w, h, getJumpZoneSize(), 0, 0); + // compute center pixel of screen + m_xCenter = w >> 1; + m_yCenter = h >> 1; + + // get keyboard state + updateKeys(); + // enter the screen - doEnter(); + enterNoWarp(); } void CMSWindowsPrimaryScreen::close() @@ -92,115 +140,48 @@ void CMSWindowsPrimaryScreen::enter(SInt32 x, SInt32 y) log((CLOG_INFO "entering primary at %d,%d", x, y)); assert(m_active == true); - doEnter(); + // enter the screen + enterNoWarp(); // warp to requested location warpCursor(x, y); } -void CMSWindowsPrimaryScreen::doEnter() -{ - // not active anymore - m_active = false; - - // release keyboard/mouse and set the zones that should cause a jump -/* FIXME -if (UnregisterHotKey(m_window, 0x0001) != 0) { -log((CLOG_INFO "released hot key")); -} -else { -log((CLOG_INFO "failed to release hot key: %d", GetLastError())); -} -*/ - if (m_is95Family) { - DWORD dummy = 0; - SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, FALSE, &dummy, 0); - } - SInt32 w, h; - getScreenSize(&w, &h); - SetZoneFunc setZone = (SetZoneFunc)GetProcAddress( - m_hookLibrary, "setZone"); - setZone(m_server->getActivePrimarySides(), w, h, getJumpZoneSize()); - - // restore the active window and hide our window. we can only set - // the active window for another thread if we first attach our input - // to that thread. - ReleaseCapture(); - if (m_lastActiveWindow != NULL) { - DWORD myThread = GetCurrentThreadId(); - if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) { - // FIXME -- shouldn't raise window if X-Mouse is enabled - // but i have no idea how to do that or check if enabled. - SetActiveWindow(m_lastActiveWindow); - AttachThreadInput(myThread, m_lastActiveThread, FALSE); - } - } - ShowWindow(m_window, SW_HIDE); - - // all messages prior to now are invalid - nextMark(); -} - bool CMSWindowsPrimaryScreen::leave() { log((CLOG_INFO "leaving primary")); assert(m_active == false); - // do non-warp enter stuff - // get state of keys as we leave - updateKeys(); - // all messages prior to now are invalid nextMark(); - // remember the active window before we leave. GetActiveWindow() - // will only return the active window for the thread's queue (i.e. - // our app) but we need the globally active window. get that by - // attaching input to the foreground window's thread then calling - // GetActiveWindow() and then detaching our input. - m_lastActiveWindow = NULL; - m_lastForegroundWindow = GetForegroundWindow(); - m_lastActiveThread = GetWindowThreadProcessId( - m_lastForegroundWindow, NULL); - if (m_lastActiveThread != 0) { - DWORD myThread = GetCurrentThreadId(); - if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) { - m_lastActiveWindow = GetActiveWindow(); - AttachThreadInput(myThread, m_lastActiveThread, FALSE); - } + // save active window, show ours, and grab mouse/keyboard + if (!onLeave()) { + return false; } - // show our window - ShowWindow(m_window, SW_SHOW); - // relay all mouse and keyboard events - SetRelayFunc setRelay = (SetRelayFunc)GetProcAddress( - m_hookLibrary, "setRelay"); - setRelay(); + m_setRelay(); + + // get state of keys as we leave + updateKeys(); + + // warp mouse to center of screen + warpCursor(m_xCenter, m_yCenter); + + // ignore this many mouse motion events (not including the already + // queued events). on (at least) the win2k login desktop, one + // motion event is reported using a position from before the above + // warpCursor(). i don't know why it does that and other desktops + // don't have the same problem. anyway, simply ignoring that event + // works around it. + m_mouseMoveIgnore = 1; + + // disable ctrl+alt+del, alt+tab, etc if (m_is95Family) { - // disable ctrl+alt+del, alt+tab, ctrl+esc DWORD dummy = 0; SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, TRUE, &dummy, 0); } -/* FIXME -if (RegisterHotKey(m_window, 0x0001, MOD_ALT, VK_TAB) != 0) { -log((CLOG_INFO "got hot key")); -} -else { -log((CLOG_INFO "failed to get hot key: %d", GetLastError())); -} -*/ - - // get keyboard input and capture mouse - SetActiveWindow(m_window); - SetFocus(m_window); - SetCapture(m_window); - - // warp the mouse to the center of the screen - getScreenSize(&m_xCenter, &m_yCenter); - m_xCenter >>= 1; - m_yCenter >>= 1; - warpCursor(m_xCenter, m_yCenter); // discard messages until after the warp nextMark(); @@ -238,12 +219,11 @@ log((CLOG_INFO "failed to get hot key: %d", GetLastError())); void CMSWindowsPrimaryScreen::onConfigure() { - if (!m_active) { + if ((m_is95Family || m_desk != NULL) && !m_active) { SInt32 w, h; getScreenSize(&w, &h); - SetZoneFunc setZone = (SetZoneFunc)GetProcAddress( - m_hookLibrary, "setZone"); - setZone(m_server->getActivePrimarySides(), w, h, getJumpZoneSize()); + m_setZone(m_server->getActivePrimarySides(), + w, h, getJumpZoneSize()); } } @@ -254,7 +234,7 @@ void CMSWindowsPrimaryScreen::warpCursor(SInt32 x, SInt32 y) } void CMSWindowsPrimaryScreen::setClipboard( - ClipboardID id, const IClipboard* src) + ClipboardID /*id*/, const IClipboard* src) { assert(m_window != NULL); @@ -262,7 +242,8 @@ void CMSWindowsPrimaryScreen::setClipboard( CClipboard::copy(&dst, src); } -void CMSWindowsPrimaryScreen::grabClipboard(ClipboardID id) +void CMSWindowsPrimaryScreen::grabClipboard( + ClipboardID /*id*/) { assert(m_window != NULL); @@ -284,7 +265,7 @@ SInt32 CMSWindowsPrimaryScreen::getJumpZoneSize() const } void CMSWindowsPrimaryScreen::getClipboard( - ClipboardID id, IClipboard* dst) const + ClipboardID /*id*/, IClipboard* dst) const { assert(m_window != NULL); @@ -332,80 +313,37 @@ void CMSWindowsPrimaryScreen::onOpenDisplay() assert(m_window == NULL); assert(m_server != NULL); - // initialize clipboard owner to current owner. we don't want - // to take ownership of the clipboard just by starting up. - m_clipboardOwner = GetClipboardOwner(); + // save thread id. we'll need to pass this to the hook library. + m_threadID = GetCurrentThreadId(); - // get screen size - // note -- we use a fullscreen window to grab input. it should - // be possible to use a 1x1 window but i've run into problems - // with losing keyboard input (focus?) in that case. - // unfortunately, hiding the full screen window causes all other - // windows to redraw. - SInt32 w, h; - getScreenSize(&w, &h); - - // create the window - m_window = CreateWindowEx(WS_EX_TOPMOST | - WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, - (LPCTSTR)getClass(), "Synergy", - WS_POPUP, - 0, 0, w, h, NULL, NULL, - getInstance(), - NULL); - assert(m_window != NULL); - - // install our clipboard snooper - m_nextClipboardWindow = SetClipboardViewer(m_window); - - // load the hook library - bool hooked = false; - m_hookLibrary = LoadLibrary("synrgyhk"); - if (m_hookLibrary != NULL) { - // install input hooks - InstallFunc install = (InstallFunc)GetProcAddress( - m_hookLibrary, "install"); - if (install != NULL) { - hooked = (install(m_window) != 0); + // get the input desktop and switch to it + if (m_is95Family) { + if (!openDesktop()) { + throw XScreenOpenFailure(); } } - if (!hooked) { - log((CLOG_ERR "failed to install hooks")); - ChangeClipboardChain(m_window, m_nextClipboardWindow); - m_nextClipboardWindow = NULL; - DestroyWindow(m_window); - m_window = NULL; - // FIXME -- throw + else { + if (!switchDesktop(openInputDesktop())) { + throw XScreenOpenFailure(); + } } - - // initialize marks - m_mark = 0; - m_markReceived = 0; - nextMark(); } void CMSWindowsPrimaryScreen::onCloseDisplay() { - assert(m_window != NULL); - - // uninstall input hooks - UninstallFunc uninstall = (UninstallFunc)GetProcAddress( - m_hookLibrary, "uninstall"); - if (uninstall != NULL) { - uninstall(); + // disconnect from desktop + if (m_is95Family) { + closeDesktop(); + } + else { + switchDesktop(NULL); } - // done with hook library - FreeLibrary(m_hookLibrary); - m_hookLibrary = NULL; + // clear thread id + m_threadID = 0; - // remove clipboard snooper - ChangeClipboardChain(m_window, m_nextClipboardWindow); - m_nextClipboardWindow = NULL; - - // destroy window - DestroyWindow(m_window); - m_window = NULL; + assert(m_window == NULL); + assert(m_desk == NULL); } bool CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg) @@ -417,8 +355,8 @@ bool CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg) return true; case SYNERGY_MSG_KEY: - // ignore if not at current mark - if (m_mark == m_markReceived) { + // ignore message if posted prior to last mark change + if (m_markReceived == m_mark) { KeyModifierMask mask; const KeyID key = mapKey(msg->wParam, msg->lParam, &mask); if (key != kKeyNone) { @@ -453,8 +391,8 @@ bool CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg) return true; case SYNERGY_MSG_MOUSE_BUTTON: - // ignore if not at current mark - if (m_mark == m_markReceived) { + // ignore message if posted prior to last mark change + if (m_markReceived == m_mark) { const ButtonID button = mapButton(msg->wParam); switch (msg->wParam) { case WM_LBUTTONDOWN: @@ -479,20 +417,19 @@ bool CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg) return true; case SYNERGY_MSG_MOUSE_WHEEL: - // ignore if not at current mark - if (m_mark == m_markReceived) { + // ignore message if posted prior to last mark change + if (m_markReceived == m_mark) { log((CLOG_ERR "event: button wheel delta=%d %d", msg->wParam, msg->lParam)); m_server->onMouseWheel(msg->wParam); } return true; case SYNERGY_MSG_MOUSE_MOVE: - // ignore if not at current mark - if (m_mark == m_markReceived) { - SInt32 x = (SInt32)msg->wParam; - SInt32 y = (SInt32)msg->lParam; + // ignore message if posted prior to last mark change + if (m_markReceived == m_mark) { + SInt32 x = static_cast(msg->wParam); + SInt32 y = static_cast(msg->lParam); if (!m_active) { - log((CLOG_DEBUG2 "event: inactive move %d,%d", x, y)); m_server->onMouseMovePrimary(x, y); } else { @@ -500,15 +437,35 @@ bool CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg) x -= m_xCenter; y -= m_yCenter; - // ignore if the mouse didn't move - if (x != 0 && y != 0) { - log((CLOG_DEBUG2 "event: active move %d,%d", x, y)); + // ignore if the mouse didn't move or we're ignoring + // motion. + if (m_mouseMoveIgnore == 0) { + if (x != 0 && y != 0) { + // warp mouse back to center + warpCursor(m_xCenter, m_yCenter); - // warp mouse back to center - warpCursor(m_xCenter, m_yCenter); + // send motion + m_server->onMouseMoveSecondary(x, y); + } + } + else { + // ignored one more motion event + --m_mouseMoveIgnore; + } + } + } + return true; - // send motion - m_server->onMouseMoveSecondary(x, y); + case WM_TIMER: + // if current desktop is not the input desktop then switch to it + if (!m_is95Family) { + HDESK desk = openInputDesktop(); + if (desk != NULL) { + if (isCurrentDesktop(desk)) { + CloseDesktop(desk); + } + else { + switchDesktop(desk); } } } @@ -523,11 +480,20 @@ LRESULT CMSWindowsPrimaryScreen::onEvent( WPARAM wParam, LPARAM lParam) { switch (msg) { -/* -case WM_HOTKEY: -log((CLOG_INFO "hot key: %d, %d, %s %s %s", wParam, HIWORD(lParam), (LOWORD(lParam) & MOD_ALT) ? "ALT" : "", (LOWORD(lParam) & MOD_CONTROL) ? "CTRL" : "", (LOWORD(lParam) & MOD_SHIFT) ? "SHIFT" : "", (LOWORD(lParam) & MOD_WIN) ? "WIN" : "")); -return 0; -*/ + case WM_QUERYENDSESSION: + if (m_is95Family) { + return TRUE; + } + break; + + case WM_ENDSESSION: + if (m_is95Family) { + if (wParam == TRUE && lParam == 0) { + stop(); + } + return 0; + } + break; case WM_PAINT: ValidateRect(hwnd, NULL); @@ -537,7 +503,9 @@ return 0; log((CLOG_DEBUG "clipboard was taken")); // first pass it on - SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + if (m_nextClipboardWindow != NULL) { + SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + } // now notify server that somebody changed the clipboard. // skip that if we're the new owner. @@ -555,51 +523,302 @@ return 0; return 0; case WM_CHANGECBCHAIN: - if (m_nextClipboardWindow == (HWND)wParam) + if (m_nextClipboardWindow == (HWND)wParam) { m_nextClipboardWindow = (HWND)lParam; - else + } + else if (m_nextClipboardWindow != NULL) { SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + } return 0; - case WM_DISPLAYCHANGE: { - // screen resolution has changed - SInt32 w, h; - updateScreenSize(); - getScreenSize(&w, &h); + case WM_DISPLAYCHANGE: + { + // screen resolution may have changed + SInt32 wOld, hOld; + getScreenSize(&wOld, &hOld); + SInt32 w, h; + updateScreenSize(); + getScreenSize(&w, &h); - // recompute center pixel of screen - m_xCenter = w >> 1; - m_yCenter = h >> 1; + // do nothing if resolution hasn't changed + if (w != wOld || h != hOld) { + // recompute center pixel of screen + m_xCenter = w >> 1; + m_yCenter = h >> 1; - // warp mouse to center if active - if (m_active) { - warpCursor(m_xCenter, m_yCenter); + // warp mouse to center if active + if (m_active) { + warpCursor(m_xCenter, m_yCenter); + } + + // tell hook about resize if not active + else { + onConfigure(); + } + + // send new screen info + POINT pos; + GetCursorPos(&pos); + m_server->setInfo(w, h, getJumpZoneSize(), pos.x, pos.y); + } + + return 0; } - - // tell hook about resize if not active - else { - SetZoneFunc setZone = (SetZoneFunc)GetProcAddress( - m_hookLibrary, "setZone"); - setZone(m_server->getActivePrimarySides(), w, h, getJumpZoneSize()); - } - - // send new screen info - POINT pos; - GetCursorPos(&pos); - m_server->setInfo(w, h, getJumpZoneSize(), pos.x, pos.y); - - return 0; - } } return DefWindowProc(hwnd, msg, wParam, lParam); } +void CMSWindowsPrimaryScreen::enterNoWarp() +{ + // not active anymore + m_active = false; + + // reset motion ignore count + m_mouseMoveIgnore = 0; + + // enable ctrl+alt+del, alt+tab, etc + if (m_is95Family) { + DWORD dummy = 0; + SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, FALSE, &dummy, 0); + } + + // install jump zones + onConfigure(); + + // restore active window and hide our window + onEnter(); + + // all messages prior to now are invalid + nextMark(); +} + +void CMSWindowsPrimaryScreen::onEnter() +{ + // restore the active window and hide our window. we can only set + // the active window for another thread if we first attach our input + // to that thread. + ReleaseCapture(); + if (m_lastActiveWindow != NULL) { + DWORD myThread = GetCurrentThreadId(); + if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) { + // FIXME -- shouldn't raise window if X-Mouse is enabled + // but i have no idea how to do that or check if enabled. + SetActiveWindow(m_lastActiveWindow); + AttachThreadInput(myThread, m_lastActiveThread, FALSE); + } + } + ShowWindow(m_window, SW_HIDE); +} + +bool CMSWindowsPrimaryScreen::onLeave() +{ + // remember the active window before we leave. GetActiveWindow() + // will only return the active window for the thread's queue (i.e. + // our app) but we need the globally active window. get that by + // attaching input to the foreground window's thread then calling + // GetActiveWindow() and then detaching our input. + m_lastActiveWindow = NULL; + m_lastForegroundWindow = GetForegroundWindow(); + m_lastActiveThread = GetWindowThreadProcessId( + m_lastForegroundWindow, NULL); + if (m_lastActiveThread != 0) { + DWORD myThread = GetCurrentThreadId(); + if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) { + m_lastActiveWindow = GetActiveWindow(); + AttachThreadInput(myThread, m_lastActiveThread, FALSE); + } + } + + // show our window + ShowWindow(m_window, SW_SHOW); + + // get keyboard input and capture mouse + SetActiveWindow(m_window); + SetFocus(m_window); + SetCapture(m_window); + + return true; +} + void CMSWindowsPrimaryScreen::nextMark() { - assert(m_window != NULL); + // next mark + ++m_mark; - PostMessage(m_window, SYNERGY_MSG_MARK, ++m_mark, 0); + // mark point in message queue where the mark was changed + PostThreadMessage(m_threadID, SYNERGY_MSG_MARK, m_mark, 0); +} + +bool CMSWindowsPrimaryScreen::openDesktop() +{ + // install hooks + m_install(m_threadID); + + // note -- we use a fullscreen window to grab input. it should + // be possible to use a 1x1 window but i've run into problems + // with losing keyboard input (focus?) in that case. + // unfortunately, hiding the full screen window (when entering + // the scren causes all other windows to redraw). + SInt32 w, h; + getScreenSize(&w, &h); + + // create the window + m_window = CreateWindowEx(WS_EX_TOPMOST | + WS_EX_TRANSPARENT | + WS_EX_TOOLWINDOW, + (LPCTSTR)getClass(), + "Synergy", + WS_POPUP, + 0, 0, w, h, NULL, NULL, + getInstance(), + NULL); + if (m_window == NULL) { + log((CLOG_ERR "failed to create window: %d", GetLastError())); + m_uninstall(); + return false; + } + + // install our clipboard snooper + m_nextClipboardWindow = SetClipboardViewer(m_window); + + return true; +} + +void CMSWindowsPrimaryScreen::closeDesktop() +{ + // destroy old window + if (m_window != NULL) { + // restore active window and hide ours + if (m_active) { + onEnter(); + } + + // first remove clipboard snooper + ChangeClipboardChain(m_window, m_nextClipboardWindow); + m_nextClipboardWindow = NULL; + + // we no longer own the clipboard + if (m_clipboardOwner == m_window) { + m_clipboardOwner = NULL; + } + + // now destroy window + DestroyWindow(m_window); + m_window = NULL; + } + + // unhook + m_uninstall(); +} + +bool CMSWindowsPrimaryScreen::switchDesktop(HDESK desk) +{ + // did we own the clipboard? + bool ownClipboard = (m_clipboardOwner == m_window); + + // destroy old window + if (m_window != NULL) { + // restore active window and hide ours + if (m_active) { + onEnter(); + } + + // first remove clipboard snooper + ChangeClipboardChain(m_window, m_nextClipboardWindow); + m_nextClipboardWindow = NULL; + + // we no longer own the clipboard + if (ownClipboard) { + m_clipboardOwner = NULL; + } + + // now destroy window + DestroyWindow(m_window); + m_window = NULL; + } + + // unhook + if (m_desk != NULL) { + m_uninstall(); + CloseDesktop(m_desk); + m_desk = NULL; + m_deskName = ""; + } + + // if no new desktop then we're done + if (desk == NULL) { + log((CLOG_INFO "disconnecting desktop")); + return true; + } + + // set the desktop. can only do this when there are no windows + // and hooks on the current desktop owned by this thread. + if (SetThreadDesktop(desk) == 0) { + log((CLOG_ERR "failed to set desktop: %d", GetLastError())); + CloseDesktop(desk); + return false; + } + + // install hooks + m_install(m_threadID); + + // note -- we use a fullscreen window to grab input. it should + // be possible to use a 1x1 window but i've run into problems + // with losing keyboard input (focus?) in that case. + // unfortunately, hiding the full screen window (when entering + // the scren causes all other windows to redraw). + SInt32 w, h; + getScreenSize(&w, &h); + + // create the window + m_window = CreateWindowEx(WS_EX_TOPMOST | + WS_EX_TRANSPARENT | + WS_EX_TOOLWINDOW, + (LPCTSTR)getClass(), + "Synergy", + WS_POPUP, + 0, 0, w, h, NULL, NULL, + getInstance(), + NULL); + if (m_window == NULL) { + log((CLOG_ERR "failed to create window: %d", GetLastError())); + m_uninstall(); + CloseDesktop(desk); + return false; + } + + // install our clipboard snooper + m_nextClipboardWindow = SetClipboardViewer(m_window); + + // reassert clipboard ownership + if (ownClipboard) { + // FIXME + } + + // save new desktop + m_desk = desk; + m_deskName = getDesktopName(m_desk); + log((CLOG_INFO "switched to desktop %s", m_deskName.c_str())); + + // get active window and show ours + if (m_active) { + onLeave(); + } + else { + // set jump zones + onConfigure(); + + // all messages prior to now are invalid + nextMark(); + } + + return true; +} + +CString CMSWindowsPrimaryScreen::getCurrentDesktopName() const +{ + return m_deskName; } static const KeyID g_virtualKey[] = diff --git a/server/CMSWindowsPrimaryScreen.h b/server/CMSWindowsPrimaryScreen.h index fa54388c..a568171a 100644 --- a/server/CMSWindowsPrimaryScreen.h +++ b/server/CMSWindowsPrimaryScreen.h @@ -1,10 +1,11 @@ #ifndef CMSWINDOWSPRIMARYSCREEN_H #define CMSWINDOWSPRIMARYSCREEN_H -#include "KeyTypes.h" -#include "MouseTypes.h" #include "CMSWindowsScreen.h" #include "IPrimaryScreen.h" +#include "MouseTypes.h" +#include "CString.h" +#include "CSynergyHook.h" class CMSWindowsPrimaryScreen : public CMSWindowsScreen, public IPrimaryScreen { public: @@ -36,12 +37,24 @@ protected: virtual LRESULT onEvent(HWND, UINT, WPARAM, LPARAM); virtual void onOpenDisplay(); virtual void onCloseDisplay(); + virtual CString getCurrentDesktopName() const; private: - void doEnter(); + void enterNoWarp(); + void onEnter(); + bool onLeave(); + // discard posted messages void nextMark(); + // open/close desktop (for windows 95/98/me) + bool openDesktop(); + void closeDesktop(); + + // make desk the thread desktop (for windows NT/2000/XP) + bool switchDesktop(HDESK desk); + + // key and button queries KeyID mapKey(WPARAM keycode, LPARAM info, KeyModifierMask* maskOut); ButtonID mapButton(WPARAM button) const; @@ -50,19 +63,51 @@ private: private: CServer* m_server; + + // true if windows 95/98/me bool m_is95Family; - bool m_active; + + // the main loop's thread id + DWORD m_threadID; + + // the current desk and it's name + HDESK m_desk; + CString m_deskName; + + // our window (for getting clipboard changes) HWND m_window; + + // m_active is true the hooks are relaying events + bool m_active; + + // used to discard queued messages that are no longer needed + UInt32 m_mark; + UInt32 m_markReceived; + + // clipboard stuff HWND m_nextClipboardWindow; HWND m_clipboardOwner; + + // map of key state + BYTE m_keys[256]; + + // position of center pixel of screen + SInt32 m_xCenter, m_yCenter; + + // used to ignore mouse motion + SInt32 m_mouseMoveIgnore; + + // hook library stuff + HINSTANCE m_hookLibrary; + InstallFunc m_install; + UninstallFunc m_uninstall; + SetZoneFunc m_setZone; + SetRelayFunc m_setRelay; + + // stuff for restoring active window HWND m_lastForegroundWindow; HWND m_lastActiveWindow; DWORD m_lastActiveThread; - HINSTANCE m_hookLibrary; - UInt32 m_mark; - UInt32 m_markReceived; - BYTE m_keys[256]; - SInt32 m_xCenter, m_yCenter; }; #endif diff --git a/server/CServer.cpp b/server/CServer.cpp index 8e0fa1d8..578d4dfa 100644 --- a/server/CServer.cpp +++ b/server/CServer.cpp @@ -298,7 +298,7 @@ void CServer::setInfoNoLock(const CString& screen, info->m_width = w; info->m_height = h; info->m_zoneSize = zoneSize; - log((CLOG_NOTE "screen \"%s\" size=%dx%d zone=%d pos=%d,%d", screen.c_str(), w, h, zoneSize, x, y)); + log((CLOG_INFO "screen \"%s\" size=%dx%d zone=%d pos=%d,%d", screen.c_str(), w, h, zoneSize, x, y)); // send acknowledgement (if screen isn't the primary) if (info->m_protocol != NULL) { @@ -353,7 +353,7 @@ void CServer::grabClipboardNoLock( } // mark screen as owning clipboard - log((CLOG_NOTE "screen \"%s\" grabbed clipboard %d from \"%s\"", screen.c_str(), id, clipboard.m_clipboardOwner.c_str())); + log((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", screen.c_str(), id, clipboard.m_clipboardOwner.c_str())); clipboard.m_clipboardOwner = screen; clipboard.m_clipboardSeqNum = seqNum; @@ -404,7 +404,7 @@ void CServer::setClipboard(ClipboardID id, } // unmarshall into our clipboard buffer - log((CLOG_NOTE "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id)); + log((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id)); clipboard.m_clipboardReady = true; clipboard.m_clipboardData = data; clipboard.m_clipboard.unmarshall(clipboard.m_clipboardData, 0); @@ -603,17 +603,25 @@ void CServer::onMouseMoveSecondaryNoLock( if (!isLockedToScreenNoLock()) { // find direction of neighbor CConfig::EDirection dir; - if (m_x < 0) + if (m_x < 0) { dir = CConfig::kLeft; - else if (m_x > m_active->m_width - 1) + } + else if (m_x > m_active->m_width - 1) { dir = CConfig::kRight; - else if (m_y < 0) + } + else if (m_y < 0) { dir = CConfig::kTop; - else if (m_y > m_active->m_height - 1) + } + else if (m_y > m_active->m_height - 1) { dir = CConfig::kBottom; - else + } + else { newScreen = m_active; + // keep compiler quiet about unset variable + dir = CConfig::kLeft; + } + // get neighbor if we should switch if (newScreen == NULL) { log((CLOG_DEBUG1 "leave \"%s\" on %s", m_active->m_name.c_str(), CConfig::dirName(dir))); @@ -709,7 +717,7 @@ void CServer::switchScreen(CScreenInfo* dst, assert(x >= 0 && y >= 0 && x < dst->m_width && y < dst->m_height); assert(m_active != NULL); - log((CLOG_NOTE "switch from \"%s\" to \"%s\" at %d,%d", m_active->m_name.c_str(), dst->m_name.c_str(), x, y)); + log((CLOG_INFO "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 @@ -1485,7 +1493,7 @@ void CServer::removeConnection(const CString& name) m_y = m_primaryInfo->m_height >> 1; // don't notify active screen since it probably already disconnected - log((CLOG_NOTE "jump from \"%s\" to \"%s\" at %d,%d", m_active->m_name.c_str(), m_primaryInfo->m_name.c_str(), m_x, m_y)); + log((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", m_active->m_name.c_str(), m_primaryInfo->m_name.c_str(), m_x, m_y)); // cut over m_active = m_primaryInfo; diff --git a/server/CSynergyHook.cpp b/server/CSynergyHook.cpp index 21bf518d..de47ab5f 100644 --- a/server/CSynergyHook.cpp +++ b/server/CSynergyHook.cpp @@ -33,28 +33,26 @@ typedef struct tagMOUSEHOOKSTRUCTWin2000 { #pragma data_seg("shared") // all data in this shared section *must* be initialized -static HINSTANCE g_hinstance = NULL; -static DWORD g_process = NULL; -static EWheelSupport g_wheelSupport = kWheelNone; -static UINT g_wmMouseWheel = 0; -static HWND g_hwnd = NULL; -static HHOOK g_keyboard = NULL; -static HHOOK g_mouse = NULL; -static HHOOK g_cbt = NULL; -static HHOOK g_getMessage = NULL; -static HANDLE g_keyHookThread = NULL; +static HINSTANCE g_hinstance = NULL; +static DWORD g_process = NULL; +static EWheelSupport g_wheelSupport = kWheelNone; +static UINT g_wmMouseWheel = 0; +static DWORD g_threadID = 0; +static HHOOK g_keyboard = NULL; +static HHOOK g_mouse = NULL; +static HHOOK g_cbt = NULL; +static HHOOK g_getMessage = NULL; +static HANDLE g_keyHookThread = NULL; static DWORD g_keyHookThreadID = 0; -static HANDLE g_keyHookEvent = NULL; -static HHOOK g_keyboardLL = NULL; -static bool g_relay = false; -static SInt32 g_zoneSize = 0; -static UInt32 g_zoneSides = 0; -static SInt32 g_wScreen = 0; -static SInt32 g_hScreen = 0; -static HCURSOR g_cursor = NULL; -static DWORD g_cursorThread = 0; - -static int foo = 0; +static HANDLE g_keyHookEvent = NULL; +static HHOOK g_keyboardLL = NULL; +static bool g_relay = false; +static SInt32 g_zoneSize = 0; +static UInt32 g_zoneSides = 0; +static SInt32 g_wScreen = 0; +static SInt32 g_hScreen = 0; +static HCURSOR g_cursor = NULL; +static DWORD g_cursorThread = 0; #pragma data_seg() @@ -91,16 +89,7 @@ static LRESULT CALLBACK keyboardHook(int code, WPARAM wParam, LPARAM lParam) if (code >= 0) { if (g_relay) { // forward message to our window - PostMessage(g_hwnd, SYNERGY_MSG_KEY, wParam, lParam); - -/* XXX -- this doesn't seem to work/help with lost key events - // if the active window isn't our window then make it - // active. - const bool wrongFocus = (GetActiveWindow() != g_hwnd); - if (wrongFocus) { - SetForegroundWindow(g_hwnd); - } -*/ + PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, wParam, lParam); // let certain keys pass through switch (wParam) { @@ -133,59 +122,65 @@ static LRESULT CALLBACK mouseHook(int code, WPARAM wParam, LPARAM lParam) case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: - PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_BUTTON, wParam, 0); + PostThreadMessage(g_threadID, + SYNERGY_MSG_MOUSE_BUTTON, wParam, 0); return 1; - case WM_MOUSEWHEEL: { - // win2k and other systems supporting WM_MOUSEWHEEL in - // the mouse hook are gratuitously different (and poorly - // documented). - switch (g_wheelSupport) { - case kWheelModern: { + case WM_MOUSEWHEEL: + { + // win2k and other systems supporting WM_MOUSEWHEEL in + // the mouse hook are gratuitously different (and poorly + // documented). + switch (g_wheelSupport) { + case kWheelModern: { + const MOUSEHOOKSTRUCT* info = + (const MOUSEHOOKSTRUCT*)lParam; + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, + static_cast( + LOWORD(info->dwExtraInfo)), 0); + break; + } + + case kWheelWin2000: { + const MOUSEHOOKSTRUCTWin2000* info = + (const MOUSEHOOKSTRUCTWin2000*)lParam; + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, + static_cast( + HIWORD(info->mouseData)), 0); + break; + } + + default: + break; + } + } + return 1; + + case WM_MOUSEMOVE: + { const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam; - PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_WHEEL, - static_cast(LOWORD(info->dwExtraInfo)), 0); - break; - } + SInt32 x = (SInt32)info->pt.x; + SInt32 y = (SInt32)info->pt.y; - case kWheelWin2000: { - const MOUSEHOOKSTRUCTWin2000* info = - (const MOUSEHOOKSTRUCTWin2000*)lParam; - PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_WHEEL, - static_cast(HIWORD(info->mouseData)), 0); - break; - } + // we want the cursor to be hidden at all times so we + // hide the cursor on whatever window has it. but then + // we have to show the cursor whenever we leave that + // window (or at some later time before we stop relaying). + // so check the window with the cursor. if it's not the + // same window that had it before then show the cursor + // in the last window and hide it in this window. + DWORD thread = GetWindowThreadProcessId(info->hwnd, NULL); + if (thread != g_cursorThread) { + restoreCursor(); + hideCursor(thread); + } - default: - break; + // relay the motion + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); } return 1; } - - case WM_MOUSEMOVE: { - const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam; - SInt32 x = (SInt32)info->pt.x; - SInt32 y = (SInt32)info->pt.y; - - // we want the cursor to be hidden at all times so we - // hide the cursor on whatever window has it. but then - // we have to show the cursor whenever we leave that - // window (or at some later time before we stop relaying). - // so check the window with the cursor. if it's not the - // same window that had it before then show the cursor - // in the last window and hide it in this window. - DWORD thread = GetWindowThreadProcessId(info->hwnd, NULL); - if (thread != g_cursorThread) { - restoreCursor(); - hideCursor(thread); - } - - // relay the motion - PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_MOVE, x, y); - return 1; - } - } } else { // check for mouse inside jump zone @@ -209,7 +204,7 @@ static LRESULT CALLBACK mouseHook(int code, WPARAM wParam, LPARAM lParam) // if inside then eat event and notify our window if (inside) { restoreCursor(); - PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_MOVE, x, y); + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); return 1; } } @@ -222,15 +217,7 @@ static LRESULT CALLBACK cbtHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { if (g_relay) { - switch (code) { - case HCBT_ACTIVATE: - case HCBT_SETFOCUS: - // discard unless activating our window - if (reinterpret_cast(wParam) != g_hwnd) { - return 1; - } - break; - } + // do nothing for now. may add something later. } } @@ -244,7 +231,8 @@ static LRESULT CALLBACK getMessageHook(int code, WPARAM wParam, LPARAM lParam) MSG* msg = reinterpret_cast(lParam); if (msg->message == g_wmMouseWheel) { // post message to our window - PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_WHEEL, msg->wParam, 0); + PostThreadMessage(g_threadID, + SYNERGY_MSG_MOUSE_WHEEL, msg->wParam, 0); // zero out the delta in the message so it's (hopefully) // ignored @@ -296,7 +284,8 @@ static LRESULT CALLBACK keyboardLLHook(int code, WPARAM wParam, LPARAM lParam) // FIXME -- bit 30 should be set if key was already down // forward message to our window - PostMessage(g_hwnd, SYNERGY_MSG_KEY, info->vkCode, lParam); + PostThreadMessage(g_threadID, + SYNERGY_MSG_KEY, info->vkCode, lParam); // discard event return 1; @@ -436,16 +425,18 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID) extern "C" { -int install(HWND hwnd) +int install(DWORD threadID) { + assert(g_threadID == 0); assert(g_hinstance != NULL); assert(g_keyboard == NULL); assert(g_mouse == NULL); assert(g_cbt == NULL); assert(g_wheelSupport != kWheelOld || g_getMessage == NULL); - // save window - g_hwnd = hwnd; + // save thread id. we'll post messages to this thread's + // message queue. + g_threadID = threadID; // set defaults g_relay = false; @@ -465,7 +456,7 @@ int install(HWND hwnd) g_hinstance, 0); if (g_keyboard == NULL) { - g_hwnd = NULL; + g_threadID = NULL; return 0; } @@ -478,7 +469,7 @@ int install(HWND hwnd) // uninstall keyboard hook before failing UnhookWindowsHookEx(g_keyboard); g_keyboard = NULL; - g_hwnd = NULL; + g_threadID = NULL; return 0; } @@ -493,7 +484,7 @@ int install(HWND hwnd) UnhookWindowsHookEx(g_mouse); g_keyboard = NULL; g_mouse = NULL; - g_hwnd = NULL; + g_threadID = NULL; return 0; } @@ -563,7 +554,7 @@ int uninstall(void) g_mouse = NULL; g_cbt = NULL; g_getMessage = NULL; - g_hwnd = NULL; + g_threadID = 0; // show the cursor restoreCursor(); diff --git a/server/CSynergyHook.h b/server/CSynergyHook.h index ec99afa2..7d6fa53b 100644 --- a/server/CSynergyHook.h +++ b/server/CSynergyHook.h @@ -20,14 +20,14 @@ #define SYNERGY_MSG_MOUSE_MOVE WM_APP + 0x0014 // x; y #define SYNERGY_MSG_MOUSE_WHEEL WM_APP + 0x0015 // delta; -typedef int (*InstallFunc)(HWND); +extern "C" { + +typedef int (*InstallFunc)(DWORD targetQueueThreadID); typedef int (*UninstallFunc)(void); typedef void (*SetZoneFunc)(UInt32, SInt32, SInt32, SInt32); typedef void (*SetRelayFunc)(void); -extern "C" { - -CSYNERGYHOOK_API int install(HWND); +CSYNERGYHOOK_API int install(DWORD); CSYNERGYHOOK_API int uninstall(void); CSYNERGYHOOK_API void setZone(UInt32 sides, SInt32 w, SInt32 h, SInt32 jumpZoneSize); diff --git a/server/makehook.dsp b/server/makehook.dsp index 7a9aac98..507157ef 100644 --- a/server/makehook.dsp +++ b/server/makehook.dsp @@ -23,8 +23,8 @@ CFG=makehook - Win32 Debug # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "millpond" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" MTL=midl.exe !IF "$(CFG)" == "makehook - Win32 Release" diff --git a/server/server.cpp b/server/server.cpp index feaa4f5b..e33c3383 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -1,6 +1,7 @@ #include "CServer.h" #include "CConfig.h" #include "CLog.h" +#include "CLock.h" #include "CMutex.h" #include "CNetwork.h" #include "CPlatform.h" @@ -11,15 +12,20 @@ #include "stdfstream.h" #include +// platform dependent name of a daemon +#if defined(CONFIG_PLATFORM_WIN32) +#define DAEMON "service" +#define DAEMON_NAME "Synergy Server" +#elif defined(CONFIG_PLATFORM_UNIX) +#define DAEMON "daemon" +#define DAEMON_NAME "synergyd" +#endif + // 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 // @@ -29,6 +35,8 @@ static const char* pname = NULL; static bool s_restartable = true; static bool s_daemon = true; +static bool s_install = false; +static bool s_uninstall = false; static const char* s_configFile = NULL; static const char* s_logFilter = NULL; static CConfig s_config; @@ -54,47 +62,97 @@ static void logLock(bool lock) // -// main +// platform independent main // -void realMain() +static CServer* s_server = NULL; + +static int realMain(CMutex* mutex) { - // initialize threading library - CThread::init(); + // s_serverLock should have mutex locked on entry - // make logging thread safe - CMutex logMutex; - s_logMutex = &logMutex; - CLog::setLock(&logLock); - - CServer* server = NULL; try { - // initialize network library - CNetwork::init(); + // initialize threading library + CThread::init(); - // if configuration has no screens then add this system - // as the default - if (s_config.begin() == s_config.end()) { - s_config.addScreen("primary"); + // make logging thread safe + CMutex logMutex; + s_logMutex = &logMutex; + CLog::setLock(&logLock); + + bool locked = true; + try { + // initialize network library + CNetwork::init(); + + // if configuration has no screens then add this system + // as the default + if (s_config.begin() == s_config.end()) { + s_config.addScreen("primary"); + } + + // create server + s_server = new CServer(); + + // run server (unlocked) + if (mutex != NULL) { + mutex->unlock(); + } + locked = false; + s_server->setConfig(s_config); + s_server->run(); + locked = true; + if (mutex != NULL) { + mutex->lock(); + } + + // clean up + delete s_server; + s_server = NULL; + CNetwork::cleanup(); + CLog::setLock(NULL); + s_logMutex = NULL; + } + catch (...) { + // clean up + if (!locked && mutex != NULL) { + mutex->lock(); + } + delete s_server; + s_server = NULL; + CNetwork::cleanup(); + CLog::setLock(NULL); + s_logMutex = NULL; + throw; } - - // run server - server = new CServer(); - server->setConfig(s_config); - server->run(); - - // clean up - delete server; - CNetwork::cleanup(); - CLog::setLock(NULL); - s_logMutex = NULL; } - catch (...) { - delete server; - CNetwork::cleanup(); - CLog::setLock(NULL); - s_logMutex = NULL; - throw; + catch (XBase& e) { + log((CLOG_CRIT "failed: %s", e.what())); + return 16; + } + catch (XThread&) { + // terminated + return 1; + } + + return 0; +} + +static int restartMain() +{ + return realMain(NULL); +} + +// invoke realMain and wait for it. if s_restartable then keep +// restarting realMain until it returns a terminate code. +static int restartableMain() +{ + if (s_restartable) { + CPlatform platform; + return platform.restart(restartMain, 16); + } + else { + return realMain(NULL); } } @@ -103,11 +161,9 @@ void realMain() // command line parsing // -static void bye() -{ - log((CLOG_PRINT "Try `%s --help' for more information.", pname)); - exit(1); -} +#define BYE "\nTry `%s --help' for more information." + +static void (*bye)(int) = &exit; static void version() { @@ -125,12 +181,18 @@ static void version() static void help() { + CPlatform platform; + log((CLOG_PRINT "Usage: %s" " [--config ]" " [--debug ]" -" [--daemon|--no-daemon]" +" [--"DAEMON"|--no-"DAEMON"]" " [--restart|--no-restart]\n" +"or\n" +" --install\n" +" --uninstall\n" +"\n" "Start the synergy mouse/keyboard sharing server.\n" "\n" " -c, --config use the named configuration file instead\n" @@ -138,58 +200,38 @@ static void help() " -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" +" -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" +"* --restart restart the server automatically if it fails.\n" +" --install install server as a "DAEMON".\n" +" --uninstall uninstall server "DAEMON".\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" +"* marks defaults.\n" +"\n" +"If no configuration file pathname is provided then the first of the\n" +"following to load sets the configuration:\n" +" %s\n" +" %s\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; +"server is running as a "DAEMON".", + pname, + platform.addPathComponent( + platform.getUserDirectory(), + CONFIG_NAME).c_str(), + platform.addPathComponent( + platform.getSystemDirectory(), + CONFIG_NAME).c_str())); } static bool isArg(int argi, - int argc, char** argv, + int argc, const char** argv, const char* name1, const char* name2, int minRequiredParameters = 0) @@ -198,9 +240,9 @@ static bool isArg(int argi, (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(); + log((CLOG_PRINT "%s: missing arguments for `%s'" BYE, + pname, argv[argi], pname)); + bye(2); } return true; } @@ -209,7 +251,7 @@ static bool isArg(int argi, return false; } -static void parse(int argc, char** argv) +static void parse(int argc, const char** argv) { assert(pname != NULL); assert(argv != NULL); @@ -228,12 +270,12 @@ static void parse(int argc, char** argv) s_configFile = argv[++i]; } - else if (isArg(i, argc, argv, "-f", "--no-daemon")) { + else if (isArg(i, argc, argv, "-f", "--no-"DAEMON)) { // not a daemon s_daemon = false; } - else if (isArg(i, argc, argv, NULL, "--daemon")) { + else if (isArg(i, argc, argv, NULL, "--"DAEMON)) { // daemonize s_daemon = true; } @@ -250,12 +292,42 @@ static void parse(int argc, char** argv) else if (isArg(i, argc, argv, "-h", "--help")) { help(); - exit(1); + bye(0); } else if (isArg(i, argc, argv, NULL, "--version")) { version(); - exit(1); + bye(0); + } + + else if (isArg(i, argc, argv, NULL, "--install")) { +#if !defined(CONFIG_PLATFORM_WIN32) + log((CLOG_PRINT "%s: `%s' not permitted on this platform" BYE, + pname, argv[i], pname)); + bye(2); +#endif + s_install = true; + if (s_uninstall) { + log((CLOG_PRINT "%s: `--install' and `--uninstall'" + " are mutually exclusive" BYE, + pname, argv[i], pname)); + bye(2); + } + } + + else if (isArg(i, argc, argv, NULL, "--uninstall")) { +#if !defined(CONFIG_PLATFORM_WIN32) + log((CLOG_PRINT "%s: `%s' not permitted on this platform" BYE, + pname, argv[i], pname)); + bye(2); +#endif + s_uninstall = true; + if (s_install) { + log((CLOG_PRINT "%s: `--install' and `--uninstall'" + " are mutually exclusive" BYE, + pname, argv[i], pname)); + bye(2); + } } else if (isArg(i, argc, argv, "--", NULL)) { @@ -265,8 +337,9 @@ static void parse(int argc, char** argv) } else if (argv[i][0] == '-') { - log((CLOG_PRINT "%s: unrecognized option `%s'", pname, argv[i])); - bye(); + log((CLOG_PRINT "%s: unrecognized option `%s'" BYE, + pname, argv[i], pname)); + bye(2); } else { @@ -277,101 +350,76 @@ static void parse(int argc, char** argv) // no non-option arguments are allowed if (i != argc) { - log((CLOG_PRINT "%s: unrecognized option `%s'", pname, argv[i])); - bye(); + log((CLOG_PRINT "%s: unrecognized option `%s'" BYE, + pname, argv[i], pname)); + bye(2); + } + + // increase default filter level for daemon. the user must + // explicitly request another level for a daemon. + if (s_daemon && s_logFilter == NULL) { +#if defined(CONFIG_PLATFORM_WIN32) + if (CPlatform::isWindows95Family()) { + // windows 95 has no place for logging so avoid showing + // the log console window. + s_logFilter = "FATAL"; + } + else +#endif + { + s_logFilter = "NOTE"; + } } // set log filter if (!CLog::setFilter(s_logFilter)) { - log((CLOG_PRINT "%s: unrecognized log level `%s'", pname, s_logFilter)); - bye(); + log((CLOG_PRINT "%s: unrecognized log level `%s'" BYE, + pname, s_logFilter, pname)); + bye(2); } +} - // load the config file, if any +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&) { + if (require) { + log((CLOG_PRINT "%s: cannot read configuration '%s'", + pname, pathname)); + bye(3); + } + else { + log((CLOG_DEBUG "cannot read configuration \"%s\"", pathname)); + } + } + return false; +} + +static void loadConfig() +{ + // load the config file, if specified if (s_configFile != NULL) { // require the user specified file to load correctly loadConfig(s_configFile, true); } -} - -// -// platform dependent entry points -// - -#if defined(CONFIG_PLATFORM_WIN32) - -#include "CMSWindowsScreen.h" -#include - -int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) -{ - CPlatform platform; - - // save instance - CMSWindowsScreen::init(instance); - - // get program name - pname = platform.getBasename(argv[0]); - -// 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 - } - -// FIXME - if (__argc != 1) { - CString msg = "no arguments allowed. exiting."; - MessageBox(NULL, msg.c_str(), "error", MB_OK | MB_ICONERROR); - return 1; - } - - try { - realMain(); - return 0; - } - catch (XBase& e) { - log((CLOG_CRIT "failed: %s", e.what())); - CString msg = "failed: "; - msg += e.what(); - MessageBox(NULL, msg.c_str(), "error", MB_OK | MB_ICONERROR); - return 1; - } - catch (XThread&) { - // terminated - return 1; - } -} - -#elif defined(CONFIG_PLATFORM_UNIX) - -#include // fork() -#include // wait() -#include // wait() - -int main(int argc, char** argv) -{ - CPlatform platform; - - // get program name - pname = platform.getBasename(argv[0]); - - // parse command line - parse(argc, argv); - - // load the configuration file if we haven't already - if (s_configFile == NULL) { + // load the default configuration if no explicit file given + else { // 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. + CPlatform platform; bool loaded = false; CString path = platform.getUserDirectory(); if (!path.empty()) { @@ -390,62 +438,236 @@ int main(int argc, char** argv) } } } +} + +// +// platform dependent entry points +// + +#if defined(CONFIG_PLATFORM_WIN32) + +#include "CMSWindowsScreen.h" + +static bool logMessageBox(int priority, const char* msg) +{ + if (priority <= CLog::kFATAL) { + MessageBox(NULL, msg, pname, MB_OK | MB_ICONWARNING); + return true; + } + else { + return false; + } +} + +static void byeThrow(int x) +{ + throw CWin32Platform::CDaemonFailed(x); +} + +static void daemonStop(void) +{ + s_server->quit(); +} + +static int daemonStartup(IPlatform* iplatform, + int argc, const char** argv) +{ + // get platform pointer + CWin32Platform* platform = static_cast(iplatform); + + // catch errors that would normally exit + bye = &byeThrow; + + // parse command line + s_install = false; + s_uninstall = false; + parse(argc, argv); + if (s_install || s_uninstall) { + // not allowed to install/uninstall from service + throw CWin32Platform::CDaemonFailed(1); + } + + // load configuration + loadConfig(); + + // run as a service + return platform->runDaemon(realMain, daemonStop); +} + +static int daemonStartup95(IPlatform*, int, const char**) +{ + return realMain(NULL); +} + +static bool logDiscard(int, const char*) +{ + return true; +} + +static bool s_die = false; + +static void checkParse(int e) +{ + // anything over 1 means invalid args. 1 means missing args. + // 0 means graceful exit. we plan to exit for anything but + // 1 (missing args); the service control manager may supply + // the missing arguments so we don't exit in that case. + s_die = (e != 1); + throw s_die; +} + +int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int) +{ + CPlatform platform; + + // save instance + CMSWindowsScreen::init(instance); + + // get program name + pname = platform.getBasename(__argv[0]); + + // parse command line without reporting errors but recording if + // the app would've exited. this is too avoid showing a dialog + // box if we're being started as a service because we shouldn't + // take to long to startup as a service. this mostly works but + // will choke if the service control manager passes --install + // or --uninstall (but that's unlikely). + CLog::setOutputter(&logDiscard); + bye = &checkParse; + try { + parse(__argc, const_cast(__argv)); + } + catch (...) { + // ignore + } + + // if we're not starting as an NT service then reparse the command + // line normally. + if (s_die || !s_daemon || s_install || s_uninstall || + CWin32Platform::isWindows95Family()) { + // send PRINT and FATAL output to a message box + CLog::setOutputter(&logMessageBox); + + // exit on bye + bye = &exit; + + // reparse + parse(__argc, const_cast(__argv)); + } + + // if starting as a daemon then we ignore the startup command line + // here. we'll parse the command line passed in when the service + // control manager calls us back. + else { + // do nothing + } + + // install/uninstall + if (s_install) { + // get the full path to this program + TCHAR path[MAX_PATH]; + if (GetModuleFileName(NULL, path, + sizeof(path) / sizeof(path[0])) == 0) { + log((CLOG_CRIT "cannot determine absolute path to program")); + return 16; + } + + // construct the command line to start the service with + CString commandLine; + commandLine += "--"DAEMON; + if (s_restartable) { + commandLine += " --restart"; + } + else { + commandLine += " --no-restart"; + } + if (s_logFilter != NULL) { + commandLine += " --debug "; + commandLine += s_logFilter; + } + if (s_configFile != NULL) { + commandLine += " --config \""; + commandLine += s_configFile; + commandLine += "\""; + } + + // install + if (!platform.installDaemon(DAEMON_NAME, + "Shares this system's mouse and keyboard with others.", + path, commandLine.c_str())) { + log((CLOG_CRIT "failed to install service")); + return 16; + } + log((CLOG_PRINT "installed successfully")); + return 0; + } + else if (s_uninstall) { + if (!platform.uninstallDaemon(DAEMON_NAME)) { + log((CLOG_CRIT "failed to uninstall service")); + return 16; + } + log((CLOG_PRINT "uninstalled successfully")); + return 0; + } + + // load configuration + loadConfig(); // daemonize if requested + int result; if (s_daemon) { - if (!platform.daemonize("synergyd")) { + if (CWin32Platform::isWindows95Family()) { + result = platform.daemonize(DAEMON_NAME, &daemonStartup95); + } + else { + result = platform.daemonize(DAEMON_NAME, &daemonStartup); + } + if (result == -1) { + log((CLOG_CRIT "failed to start as a service")); + return 16; + } + } + else { + result = restartableMain(); + } + + return result; +} + +#elif defined(CONFIG_PLATFORM_UNIX) + +static int daemonStartup(IPlatform*, int, const char**) +{ + return restartableMain(); +} + +int main(int argc, char** argv) +{ + CPlatform platform; + + // get program name + pname = platform.getBasename(argv[0]); + + // parse command line + parse(argc, const_cast(argv)); + + // load configuration + loadConfig(); + + // daemonize if requested + int result; + if (s_daemon) { + result = platform.daemonize(DAEMON_NAME, &daemonStartup); + if (result == -1) { log((CLOG_CRIT "failed to daemonize")); return 16; } } - - // 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())); - return 16; - } - catch (XThread&) { - // terminated - return 1; - } - } + else { + result = restartableMain(); } + + return result; } #else diff --git a/server/server.dsp b/server/server.dsp index 46ac171a..b50dd9f7 100644 --- a/server/server.dsp +++ b/server/server.dsp @@ -23,8 +23,8 @@ CFG=server - Win32 Debug # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "millpond" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" CPP=cl.exe MTL=midl.exe RSC=rc.exe @@ -40,6 +40,7 @@ RSC=rc.exe # PROP Use_Debug_Libraries 0 # PROP Output_Dir "../Release" # PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c # ADD CPP /nologo /MT /W4 /GX /O2 /I "..\base" /I "..\mt" /I "..\io" /I "..\http" /I "..\net" /I "..\synergy" /I "..\platform" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c @@ -53,7 +54,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 -# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"../Release/synergyd.exe" !ELSEIF "$(CFG)" == "server - Win32 Debug" @@ -66,6 +67,7 @@ LINK32=link.exe # PROP Use_Debug_Libraries 1 # PROP Output_Dir "../Debug" # PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c # ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "..\base" /I "..\mt" /I "..\io" /I "..\http" /I "..\net" /I "..\synergy" /I "..\platform" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FD /GZ /c @@ -79,7 +81,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept -# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /out:"../Debug/synergyd.exe" /pdbtype:sept !ENDIF diff --git a/server/synrgyhk.dsp b/server/synrgyhk.dsp index 784e00a8..fe82c7cd 100644 --- a/server/synrgyhk.dsp +++ b/server/synrgyhk.dsp @@ -23,8 +23,8 @@ CFG=synrgyhk - Win32 Debug # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "millpond" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" CPP=cl.exe MTL=midl.exe RSC=rc.exe diff --git a/synergy/IPrimaryScreen.h b/synergy/IPrimaryScreen.h index 295bf37a..fbe00b46 100644 --- a/synergy/IPrimaryScreen.h +++ b/synergy/IPrimaryScreen.h @@ -3,6 +3,7 @@ #include "IInterface.h" #include "BasicTypes.h" +#include "KeyTypes.h" #include "ClipboardTypes.h" class CServer; diff --git a/synergy/synergy.dsp b/synergy/synergy.dsp index a6417067..e4dae53d 100644 --- a/synergy/synergy.dsp +++ b/synergy/synergy.dsp @@ -23,8 +23,8 @@ CFG=synergy - Win32 Debug # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName "millpond" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" CPP=cl.exe RSC=rc.exe