diff --git a/lib/platform/CMSWindowsPrimaryScreen.cpp b/lib/platform/CMSWindowsPrimaryScreen.cpp index bcb2d04c..bc5257ff 100644 --- a/lib/platform/CMSWindowsPrimaryScreen.cpp +++ b/lib/platform/CMSWindowsPrimaryScreen.cpp @@ -295,9 +295,9 @@ CMSWindowsPrimaryScreen::CMSWindowsPrimaryScreen( m_receiver(primaryReceiver), m_is95Family(CArchMiscWindows::isWindows95Family()), m_threadID(0), - m_window(NULL), m_mark(0), - m_markReceived(0) + m_markReceived(0), + m_lowLevel(false) { assert(m_receiver != NULL); @@ -333,7 +333,6 @@ CMSWindowsPrimaryScreen::CMSWindowsPrimaryScreen( CMSWindowsPrimaryScreen::~CMSWindowsPrimaryScreen() { assert(m_hookLibrary != NULL); - assert(m_window == NULL); delete m_screen; FreeLibrary(m_hookLibrary); @@ -385,8 +384,8 @@ KeyModifierMask CMSWindowsPrimaryScreen::getToggleMask() const { KeyModifierMask mask = 0; - if (isActive()) { - // get key state + if (!m_lowLevel) { + // get key state from our shadow state if ((m_keys[VK_CAPITAL] & 0x01) != 0) mask |= KeyModifierCapsLock; if ((m_keys[VK_NUMLOCK] & 0x01) != 0) @@ -395,33 +394,13 @@ CMSWindowsPrimaryScreen::getToggleMask() const mask |= KeyModifierScrollLock; } else { - // show the window, but make it very small. we must do this - // because GetKeyState() reports the key state according to - // processed messages and until the window is visible the - // system won't update the state of the toggle keys reported - // by that function. unfortunately, this slows this method - // down significantly and, for some reason i don't understand, - // causes everything on the screen to redraw. - if (m_window != NULL) { - MoveWindow(m_window, 1, 1, 1, 1, FALSE); - const_cast(this)->showWindow(); - } - - // get key state + // get key state from the system when using low level hooks if ((GetKeyState(VK_CAPITAL) & 0x01) != 0) mask |= KeyModifierCapsLock; if ((GetKeyState(VK_NUMLOCK) & 0x01) != 0) mask |= KeyModifierNumLock; if ((GetKeyState(VK_SCROLL) & 0x01) != 0) mask |= KeyModifierScrollLock; - - // make the window hidden again and restore its size - if (m_window != NULL) { - const_cast(this)->hideWindow(); - SInt32 x, y, w, h; - m_screen->getShape(x, y, w, h); - MoveWindow(m_window, x, y, w, h, FALSE); - } } return mask; @@ -444,12 +423,12 @@ CMSWindowsPrimaryScreen::isLockedToScreen() const 0x7ffffe5f }; - // check each key. note that we cannot use GetKeyboardState() here - // since it reports the state of keys according to key messages - // that have been pulled off the queue. in general, we won't get - // these key messages because they're not for our window. if any - // key (or mouse button) is down then we're locked to the screen. - if (isActive()) { + // check each key. if we're capturing events at a low level we + // can query the keyboard state using GetKeyState(). if not we + // resort to using our shadow keyboard state since the system's + // shadow state won't be in sync (because our window is not + // getting keyboard events). + if (!m_lowLevel) { // use shadow keyboard state in m_keys for (UInt32 i = 0; i < 256; ++i) { if ((m_keys[i] & 0x80) != 0) { @@ -462,7 +441,7 @@ CMSWindowsPrimaryScreen::isLockedToScreen() const for (UInt32 i = 0; i < 256 / 32; ++i) { for (UInt32 b = 1, j = 0; j < 32; b <<= 1, ++j) { if ((s_mappedKeys[i] & b) != 0) { - if (GetAsyncKeyState(i * 32 + j) < 0) { + if (GetKeyState(i * 32 + j) < 0) { LOG((CLOG_DEBUG "locked by \"%s\"", g_vkToName[i * 32 + j])); return true; } @@ -573,9 +552,6 @@ CMSWindowsPrimaryScreen::onPreDispatch(const CEvent* event) LOG((CLOG_DEBUG1 "event: key press key=%d mask=0x%04x button=0x%04x", key, mask, button)); m_receiver->onKeyDown(key, mask, button); } - - // update key state - updateKey(msg->wParam, true); } else { // key release. if the key isn't down according to @@ -594,51 +570,76 @@ CMSWindowsPrimaryScreen::onPreDispatch(const CEvent* event) // do key up LOG((CLOG_DEBUG1 "event: key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); m_receiver->onKeyUp(key, mask, button); - - // update key state - updateKey(msg->wParam, false); } } else { LOG((CLOG_DEBUG2 "event: cannot map key wParam=%d lParam=0x%08x", msg->wParam, msg->lParam)); } } + + // keep our shadow key state up to date + updateKey(msg->wParam, ((msg->lParam & 0x80000000) == 0)); + return true; - case SYNERGY_MSG_MOUSE_BUTTON: + case SYNERGY_MSG_MOUSE_BUTTON: { + static const int s_vkButton[] = { + 0, // kButtonNone + VK_LBUTTON, // kButtonLeft, etc. + VK_MBUTTON, + VK_RBUTTON + }; + + // get which button + bool pressed = false; + const ButtonID button = mapButton(msg->wParam); + // ignore message if posted prior to last mark change if (!ignore()) { - static const int s_vkButton[] = { - 0, // kButtonNone - VK_LBUTTON, // kButtonLeft, etc. - VK_MBUTTON, - VK_RBUTTON - }; - - const ButtonID button = mapButton(msg->wParam); switch (msg->wParam) { case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_NCLBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCMBUTTONDBLCLK: + case WM_NCRBUTTONDBLCLK: LOG((CLOG_DEBUG1 "event: button press button=%d", button)); if (button != kButtonNone) { m_receiver->onMouseDown(button); m_keys[s_vkButton[button]] |= 0x80; } + pressed = true; break; case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: + case WM_NCLBUTTONUP: + case WM_NCMBUTTONUP: + case WM_NCRBUTTONUP: LOG((CLOG_DEBUG1 "event: button release button=%d", button)); if (button != kButtonNone) { m_receiver->onMouseUp(button); m_keys[s_vkButton[button]] &= ~0x80; } + pressed = false; break; } } + + // keep our shadow key state up to date + if (button != kButtonNone) { + updateKey(s_vkButton[button], pressed); + } + return true; + } case SYNERGY_MSG_MOUSE_WHEEL: // ignore message if posted prior to last mark change @@ -765,29 +766,25 @@ CMSWindowsPrimaryScreen::getJumpZoneSize() const } void -CMSWindowsPrimaryScreen::postCreateWindow(HWND window) +CMSWindowsPrimaryScreen::postCreateWindow(HWND) { - // save window - m_window = window; - // install hooks - m_install(); + switch (m_install()) { + case kHOOK_FAILED: + // FIXME -- can't install hook so we won't work; report error + m_lowLevel = false; + break; - // resize window - // 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 screen) causes all other windows to redraw. - SInt32 x, y, w, h; - m_screen->getShape(x, y, w, h); - MoveWindow(m_window, x, y, w, h, FALSE); + case kHOOK_OKAY: + m_lowLevel = false; + break; - if (isActive()) { - // hide the cursor - showWindow(); + case kHOOK_OKAY_LL: + m_lowLevel = true; + break; } - else { + + if (!isActive()) { // watch jump zones m_setRelay(false); @@ -799,11 +796,6 @@ CMSWindowsPrimaryScreen::postCreateWindow(HWND window) void CMSWindowsPrimaryScreen::preDestroyWindow(HWND) { - // hide the window if it's visible - if (isActive()) { - hideWindow(); - } - // uninstall hooks m_uninstall(); } @@ -813,14 +805,11 @@ CMSWindowsPrimaryScreen::onPreMainLoop() { // must call mainLoop() from same thread as open() assert(m_threadID == GetCurrentThreadId()); - assert(m_window != NULL); } void CMSWindowsPrimaryScreen::onPreOpen() { - assert(m_window == NULL); - // initialize hook library m_threadID = GetCurrentThreadId(); if (m_init(m_threadID) == 0) { @@ -857,8 +846,6 @@ CMSWindowsPrimaryScreen::onPostClose() void CMSWindowsPrimaryScreen::onPreEnter() { - assert(m_window != NULL); - // enable ctrl+alt+del, alt+tab, etc if (m_is95Family) { DWORD dummy = 0; @@ -879,8 +866,6 @@ CMSWindowsPrimaryScreen::onPostEnter() void CMSWindowsPrimaryScreen::onPreLeave() { - assert(m_window != NULL); - // all messages prior to now are invalid nextMark(); } @@ -904,19 +889,10 @@ void CMSWindowsPrimaryScreen::createWindow() { // open the desktop and the window - m_window = m_screen->openDesktop(); - if (m_window == NULL) { + HWND window = m_screen->openDesktop(); + if (window == NULL) { throw XScreenOpenFailure(); } - - // 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 x, y, w, h; - m_screen->getShape(x, y, w, h); - MoveWindow(m_window, x, y, w, h, FALSE); } void @@ -924,104 +900,19 @@ CMSWindowsPrimaryScreen::destroyWindow() { // close the desktop and the window m_screen->closeDesktop(); - m_window = NULL; } bool CMSWindowsPrimaryScreen::showWindow() { - // 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); - DWORD myThread = GetCurrentThreadId(); - if (m_lastActiveThread != 0) { - if (myThread != m_lastActiveThread) { - if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) { - m_lastActiveWindow = GetActiveWindow(); - AttachThreadInput(myThread, m_lastActiveThread, FALSE); - } - } - } - - // show our window - ShowWindow(m_window, SW_SHOW); - - // force our window to the foreground. this is necessary to - // capture input but is complicated by microsoft's misguided - // attempt to prevent applications from changing the - // foreground window. (the user should be in control of that - // under normal circumstances but there are exceptions; the - // good folks at microsoft, after abusing the previously - // available ability to switch foreground tasks in many of - // their apps, changed the behavior to prevent it. maybe - // it was easier than fixing the applications.) - // - // anyway, simply calling SetForegroundWindow() doesn't work - // unless there is no foreground window or we already are the - // foreground window. so we AttachThreadInput() to the - // foreground process then call SetForegroundWindow(); that - // makes Windows think the foreground process changed the - // foreground window which is allowed since the foreground - // is "voluntarily" yielding control. then we unattach the - // thread input and go about our business. - // - // unfortunately, this still doesn't work for console windows - // on the windows 95 family. if a console is the foreground - // app on the server when the user leaves the server screen - // then the keyboard will not be captured by synergy. - if (m_lastActiveThread != myThread) { - if (m_lastActiveThread != 0) { - AttachThreadInput(myThread, m_lastActiveThread, TRUE); - } - SetForegroundWindow(m_window); - if (m_lastActiveThread != 0) { - AttachThreadInput(myThread, m_lastActiveThread, FALSE); - } - } - - // get keyboard input and capture mouse - SetActiveWindow(m_window); - SetFocus(m_window); - SetCapture(m_window); - + // do nothing. we don't need to show a window to capture input. return true; } void CMSWindowsPrimaryScreen::hideWindow() { - // 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); - } - } - - // hide the window. do not wait for it, though, since ShowWindow() - // waits for the event loop to process the show-window event, but - // that thread may need to lock the mutex that this thread has - // already locked. in particular, that deadlock will occur unless - // we use the asynchronous version of show window when a client - // disconnects: thread A will lock the mutex and enter the primary - // screen which warps the mouse and calls this method while thread B - // will handle the mouse warp event and call methods that try to - // lock the mutex. thread A owns the mutex and is waiting for the - // event loop, thread B owns the event loop and is waiting for the - // mutex causing deadlock. - ShowWindowAsync(m_window, SW_HIDE); + // do nothing. we don't need to show a window to capture input. } void @@ -1520,15 +1411,27 @@ CMSWindowsPrimaryScreen::mapButton(WPARAM button) const { switch (button) { case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: case WM_LBUTTONUP: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCLBUTTONUP: return kButtonLeft; case WM_MBUTTONDOWN: + case WM_MBUTTONDBLCLK: case WM_MBUTTONUP: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONDBLCLK: + case WM_NCMBUTTONUP: return kButtonMiddle; case WM_RBUTTONDOWN: + case WM_RBUTTONDBLCLK: case WM_RBUTTONUP: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONDBLCLK: + case WM_NCRBUTTONUP: return kButtonRight; default: diff --git a/lib/platform/CMSWindowsPrimaryScreen.h b/lib/platform/CMSWindowsPrimaryScreen.h index f179a90c..bb5c0eb0 100644 --- a/lib/platform/CMSWindowsPrimaryScreen.h +++ b/lib/platform/CMSWindowsPrimaryScreen.h @@ -101,9 +101,6 @@ private: // the main loop's thread id DWORD m_threadID; - // our window - HWND m_window; - // used to discard queued messages that are no longer needed UInt32 m_mark; UInt32 m_markReceived; @@ -126,6 +123,7 @@ private: SetSidesFunc m_setSides; SetZoneFunc m_setZone; SetRelayFunc m_setRelay; + bool m_lowLevel; // stuff for restoring active window HWND m_lastForegroundWindow; diff --git a/lib/platform/CSynergyHook.cpp b/lib/platform/CSynergyHook.cpp index 885ec8d0..1742f91c 100644 --- a/lib/platform/CSynergyHook.cpp +++ b/lib/platform/CSynergyHook.cpp @@ -61,7 +61,6 @@ 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_hookThreadLL = NULL; static DWORD g_hookThreadIDLL = 0; @@ -122,117 +121,92 @@ restoreCursor() } static -LRESULT CALLBACK -keyboardHook(int code, WPARAM wParam, LPARAM lParam) +bool +keyboardHookHandler(WPARAM wParam, LPARAM lParam) { - if (code >= 0) { - if (g_relay) { - // forward message to our window - PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, wParam, lParam); + // forward message to our window. do this whether or not we're + // forwarding events to clients because this'll keep our thread's + // key state table up to date. that's important for querying + // the scroll lock toggle state. + PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, wParam, lParam); - // let certain keys pass through - switch (wParam) { - case VK_CAPITAL: - case VK_NUMLOCK: - case VK_SCROLL: - // pass event on. we want to let these through to - // the window proc because otherwise the keyboard - // lights may not stay synchronized. - break; + if (g_relay) { + // let certain keys pass through + switch (wParam) { + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + // pass event on. we want to let these through to + // the window proc because otherwise the keyboard + // lights may not stay synchronized. + break; - default: - // discard event - return 1; - } + default: + // discard event + return true; } } - return CallNextHookEx(g_keyboard, code, wParam, lParam); + return false; } static -LRESULT CALLBACK -mouseHook(int code, WPARAM wParam, LPARAM lParam) +bool +mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 wheel) { - if (code >= 0) { + switch (wParam) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_NCLBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCMBUTTONDBLCLK: + case WM_NCRBUTTONDBLCLK: + case WM_NCLBUTTONUP: + case WM_NCMBUTTONUP: + case WM_NCRBUTTONUP: + // always relay the event. eat it if relaying. + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_BUTTON, wParam, 0); + return g_relay; + + case WM_MOUSEWHEEL: if (g_relay) { - switch (wParam) { - case WM_LBUTTONDOWN: - case WM_MBUTTONDOWN: - case WM_RBUTTONDOWN: - case WM_LBUTTONUP: - case WM_MBUTTONUP: - case WM_RBUTTONUP: - PostThreadMessage(g_threadID, - SYNERGY_MSG_MOUSE_BUTTON, wParam, 0); - return 1; + // relay event + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, wheel, 0); + } + return g_relay; - case WM_MOUSEWHEEL: - { - // win2k and other systems supporting WM_MOUSEWHEEL in - // the mouse hook are gratuitously different (and poorly - // documented). if a low-level mouse hook is in place - // it should capture these events so we'll never see - // them. - 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; - - // 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); - } - - // get position - SInt32 x = (SInt32)info->pt.x; - SInt32 y = (SInt32)info->pt.y; - - // relay the motion - PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); - } - return 1; + case WM_NCMOUSEMOVE: + case WM_MOUSEMOVE: + if (g_relay) { + // 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 = GetCurrentThreadId(); + if (thread != g_cursorThread) { + restoreCursor(); + hideCursor(thread); } + + // relay and eat event + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); + return true; } else { // check for mouse inside jump zone bool inside = false; - const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam; - SInt32 x = (SInt32)info->pt.x; - SInt32 y = (SInt32)info->pt.y; if (!inside && (g_zoneSides & kLeftMask) != 0) { inside = (x < g_xScreen + g_zoneSize); } @@ -246,36 +220,71 @@ mouseHook(int code, WPARAM wParam, LPARAM lParam) inside = (y >= g_yScreen + g_hScreen - g_zoneSize); } - // if inside then eat event and notify our window - if (inside) { - restoreCursor(); - PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); - return 1; + // relay the event + PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); + + // if inside then eat the event + return inside; + } + } + + // pass the event + return false; +} + +static +LRESULT CALLBACK +keyboardHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + // handle the message + if (keyboardHookHandler(wParam, lParam)) { + return 1; + } + } + + return CallNextHookEx(g_keyboard, code, wParam, lParam); +} + +static +LRESULT CALLBACK +mouseHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + // decode message + const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam; + SInt32 x = (SInt32)info->pt.x; + SInt32 y = (SInt32)info->pt.y; + SInt32 w = 0; + if (wParam == WM_MOUSEWHEEL) { + // win2k and other systems supporting WM_MOUSEWHEEL in + // the mouse hook are gratuitously different (and poorly + // documented). if a low-level mouse hook is in place + // it should capture these events so we'll never see + // them. + switch (g_wheelSupport) { + case kWheelModern: + w = static_cast(LOWORD(info->dwExtraInfo)); + break; + + case kWheelWin2000: { + const MOUSEHOOKSTRUCTWin2000* info2k = + (const MOUSEHOOKSTRUCTWin2000*)lParam; + w = static_cast(HIWORD(info2k->mouseData)); + break; } - else { - PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); } } + + // handle the message + if (mouseHookHandler(wParam, x, y, w)) { + return 1; + } } return CallNextHookEx(g_mouse, code, wParam, lParam); } -/* -static -LRESULT CALLBACK -cbtHook(int code, WPARAM wParam, LPARAM lParam) -{ - if (code >= 0) { - if (g_relay) { - // do nothing for now. may add something later. - } - } - - return CallNextHookEx(g_cbt, code, wParam, lParam); -} -*/ - static LRESULT CALLBACK getMessageHook(int code, WPARAM wParam, LPARAM lParam) @@ -312,7 +321,7 @@ getMessageHook(int code, WPARAM wParam, LPARAM lParam) // // low-level keyboard hook -- this allows us to capture and handle // alt+tab, alt+esc, ctrl+esc, and windows key hot keys. on the down -// side, key repeats are not compressed for us. +// side, key repeats are not reported to us. // static @@ -320,41 +329,27 @@ LRESULT CALLBACK keyboardLLHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { - if (g_relay) { - KBDLLHOOKSTRUCT* info = reinterpret_cast(lParam); + // decode the message + KBDLLHOOKSTRUCT* info = reinterpret_cast(lParam); + WPARAM wParam = info->vkCode; + LPARAM lParam = 1; // repeat code + lParam |= (info->scanCode << 16); // scan code + if (info->flags & LLKHF_EXTENDED) { + lParam |= (1lu << 24); // extended key + } + if (info->flags & LLKHF_ALTDOWN) { + lParam |= (1lu << 29); // context code + } + if (info->flags & LLKHF_UP) { + lParam |= (1lu << 31); // transition + } + // FIXME -- bit 30 should be set if key was already down but + // we don't know that info. as a result we'll never generate + // key repeat events. - // let certain keys pass through - switch (info->vkCode) { - case VK_CAPITAL: - case VK_NUMLOCK: - case VK_SCROLL: - // pass event on. we want to let these through to - // the window proc because otherwise the keyboard - // lights may not stay synchronized. - break; - - default: - // construct lParam for WM_KEYDOWN, etc. - DWORD lParam = 1; // repeat code - lParam |= (info->scanCode << 16); // scan code - if (info->flags & LLKHF_EXTENDED) { - lParam |= (1lu << 24); // extended key - } - if (info->flags & LLKHF_ALTDOWN) { - lParam |= (1lu << 29); // context code - } - if (info->flags & LLKHF_UP) { - lParam |= (1lu << 31); // transition - } - // FIXME -- bit 30 should be set if key was already down - - // forward message to our window - PostThreadMessage(g_threadID, - SYNERGY_MSG_KEY, info->vkCode, lParam); - - // discard event - return 1; - } + // handle the message + if (keyboardHookHandler(wParam, lParam)) { + return 1; } } @@ -363,11 +358,7 @@ keyboardLLHook(int code, WPARAM wParam, LPARAM lParam) // // low-level mouse hook -- this allows us to capture and handle mouse -// wheel events on all windows NT platforms from NT SP3 and up. this -// is both simpler than using the mouse hook and also supports windows -// windows NT which does not report mouse wheel events. we need to -// keep the mouse hook handling of mouse wheel events because the -// windows 95 family doesn't support low-level hooks. +// events very early. the earlier the better. // static @@ -375,28 +366,15 @@ LRESULT CALLBACK mouseLLHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { - if (g_relay) { - MSLLHOOKSTRUCT* info = reinterpret_cast(lParam); + // decode the message + MSLLHOOKSTRUCT* info = reinterpret_cast(lParam); + SInt32 x = (SInt32)info->pt.x; + SInt32 y = (SInt32)info->pt.y; + SInt32 w = (SInt32)HIWORD(info->mouseData); - switch (wParam) { - case WM_MOUSEWHEEL: - // mouse wheel events are the same for entire NT family - // (>=SP3, prior versions have no low level hooks) for - // low-level mouse hook messages, unlike (regular) mouse - // hook messages which are gratuitously different on - // win2k and not sent at all for windows NT. - - // forward message to our window - PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, - HIWORD(info->mouseData), 0); - - // discard event - return 1; - - default: - // all other events are passed through - break; - } + // handle the message + if (mouseHookHandler(wParam, x, y, w)) { + return 1; } } @@ -592,7 +570,6 @@ init(DWORD threadID) g_threadID = 0; g_keyboard = NULL; g_mouse = NULL; - g_cbt = NULL; g_getMessage = NULL; g_hookThreadLL = NULL; g_hookThreadIDLL = 0; @@ -632,79 +609,28 @@ cleanup(void) return 1; } -int +EHookResult install() { assert(g_hinstance != NULL); assert(g_keyboard == NULL); assert(g_mouse == NULL); - assert(g_cbt == NULL); assert(g_getMessage == NULL || g_screenSaver); // must be initialized if (g_threadID == 0) { - return 0; + return kHOOK_FAILED; } // check for mouse wheel support g_wheelSupport = getWheelSupport(); - // install keyboard hook -#if !NO_GRAB_KEYBOARD - g_keyboard = SetWindowsHookEx(WH_KEYBOARD, - &keyboardHook, - g_hinstance, - 0); - if (g_keyboard == NULL) { - g_threadID = NULL; - return 0; - } -#else - // keep compiler quiet - &keyboardHook; -#endif - - // install mouse hook - g_mouse = SetWindowsHookEx(WH_MOUSE, - &mouseHook, - g_hinstance, - 0); - if (g_mouse == NULL) { - // uninstall keyboard hook before failing - if (g_keyboard != NULL) { - UnhookWindowsHookEx(g_keyboard); - g_keyboard = NULL; - } - g_threadID = NULL; - return 0; - } - -/* - // install CBT hook - g_cbt = SetWindowsHookEx(WH_CBT, - &cbtHook, - g_hinstance, - 0); - if (g_cbt == NULL) { - // uninstall keyboard and mouse hooks before failing - if (g_keyboard != NULL) { - UnhookWindowsHookEx(g_keyboard); - g_keyboard = NULL; - } - UnhookWindowsHookEx(g_mouse); - g_mouse = NULL; - g_threadID = NULL; - return 0; - } -*/ - // install GetMessage hook (unless already installed) if (g_wheelSupport == kWheelOld && g_getMessage == NULL) { g_getMessage = SetWindowsHookEx(WH_GETMESSAGE, &getMessageHook, g_hinstance, 0); - // ignore failure; we just won't get mouse wheel messages } // install low-level keyboard/mouse hooks, if possible. since these @@ -735,7 +661,47 @@ install() } } - return 1; + // install non-low-level hooks if the low-level hooks are not installed + if (g_hookThreadLL == NULL) { +#if !NO_GRAB_KEYBOARD + g_keyboard = SetWindowsHookEx(WH_KEYBOARD, + &keyboardHook, + g_hinstance, + 0); +#else + // keep compiler quiet + &keyboardHook; +#endif + g_mouse = SetWindowsHookEx(WH_MOUSE, + &mouseHook, + g_hinstance, + 0); + } + + // check for any failures. uninstall all hooks on failure. + if (g_hookThreadLL == NULL && +#if !NO_GRAB_KEYBOARD + (g_keyboard == NULL || g_mouse == NULL)) { +#else + (g_mouse == NULL)) { +#endif + if (g_keyboard != NULL) { + UnhookWindowsHookEx(g_keyboard); + g_keyboard = NULL; + } + if (g_mouse != NULL) { + UnhookWindowsHookEx(g_mouse); + g_mouse = NULL; + } + if (g_getMessage != NULL && !g_screenSaver) { + UnhookWindowsHookEx(g_getMessage); + g_getMessage = NULL; + } + g_threadID = NULL; + return kHOOK_FAILED; + } + + return (g_hookThreadLL == NULL) ? kHOOK_OKAY : kHOOK_OKAY_LL; } int @@ -755,20 +721,16 @@ uninstall(void) } if (g_keyboard != NULL) { UnhookWindowsHookEx(g_keyboard); + g_keyboard = NULL; } if (g_mouse != NULL) { UnhookWindowsHookEx(g_mouse); - } - if (g_cbt != NULL) { - UnhookWindowsHookEx(g_cbt); + g_mouse = NULL; } if (g_getMessage != NULL && !g_screenSaver) { UnhookWindowsHookEx(g_getMessage); g_getMessage = NULL; } - g_keyboard = NULL; - g_mouse = NULL; - g_cbt = NULL; g_wheelSupport = kWheelNone; // show the cursor @@ -837,6 +799,10 @@ setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, SInt32 jumpZoneSize) void setRelay(int enable) { + if ((enable != 0) == g_relay) { + // no change + return; + } g_relay = (enable != 0); if (!g_relay) { restoreCursor(); diff --git a/lib/platform/CSynergyHook.h b/lib/platform/CSynergyHook.h index 7f306665..03e72fa7 100644 --- a/lib/platform/CSynergyHook.h +++ b/lib/platform/CSynergyHook.h @@ -43,9 +43,15 @@ extern "C" { +enum EHookResult { + kHOOK_FAILED, + kHOOK_OKAY, + kHOOK_OKAY_LL +}; + typedef int (*InitFunc)(DWORD targetQueueThreadID); typedef int (*CleanupFunc)(void); -typedef int (*InstallFunc)(void); +typedef EHookResult (*InstallFunc)(void); typedef int (*UninstallFunc)(void); typedef int (*InstallScreenSaverFunc)(void); typedef int (*UninstallScreenSaverFunc)(void); @@ -55,7 +61,7 @@ typedef void (*SetRelayFunc)(int); CSYNERGYHOOK_API int init(DWORD); CSYNERGYHOOK_API int cleanup(void); -CSYNERGYHOOK_API int install(void); +CSYNERGYHOOK_API EHookResult install(void); CSYNERGYHOOK_API int uninstall(void); CSYNERGYHOOK_API int installScreenSaver(void); CSYNERGYHOOK_API int uninstallScreenSaver(void);