diff --git a/lib/platform/CMSWindowsKeyMapper.cpp b/lib/platform/CMSWindowsKeyMapper.cpp index de2435a2..8cc2641c 100644 --- a/lib/platform/CMSWindowsKeyMapper.cpp +++ b/lib/platform/CMSWindowsKeyMapper.cpp @@ -706,7 +706,7 @@ const KeyButton CMSWindowsKeyMapper::s_mapEF00[] = CMSWindowsKeyMapper::CMSWindowsKeyMapper() : m_deadKey(0) { - // do nothing + m_keyLayout = GetKeyboardLayout(0); } CMSWindowsKeyMapper::~CMSWindowsKeyMapper() @@ -872,6 +872,12 @@ CMSWindowsKeyMapper::updateKey(KeyButton key, bool pressed) } } +void +CMSWindowsKeyMapper::setKeyLayout(HKL keyLayout) +{ + m_keyLayout = keyLayout; +} + KeyButton CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys, const IKeyState& keyState, KeyID id, @@ -1057,8 +1063,8 @@ CMSWindowsKeyMapper::mapKey(IKeyState::Keystrokes& keys, } KeyID -CMSWindowsKeyMapper::mapKeyFromEvent(WPARAM vkCode, LPARAM info, - KeyModifierMask* maskOut, bool* altgr) const +CMSWindowsKeyMapper::mapKeyFromEvent(WPARAM charAndVirtKey, + LPARAM info, KeyModifierMask* maskOut, bool* altgr) const { // note: known microsoft bugs // Q72583 -- MapVirtualKey() maps keypad keys incorrectly @@ -1066,59 +1072,18 @@ CMSWindowsKeyMapper::mapKeyFromEvent(WPARAM vkCode, LPARAM info, // 95,98,NT4: num pad scan code -> bad vk code except // SEPARATOR, MULTIPLY, SUBTRACT, ADD - HKL hkl = GetKeyboardLayout(0); + char c = (char)((charAndVirtKey & 0xff00u) >> 8); + UINT vkCode = (charAndVirtKey & 0xffu); // get the scan code and the extended keyboard flag UINT scanCode = static_cast((info & 0x00ff0000u) >> 16); int extended = ((info & 0x01000000) == 0) ? 0 : 1; - bool press = ((info & 0x80000000) == 0); LOG((CLOG_DEBUG1 "key vk=%d info=0x%08x ext=%d scan=%d", vkCode, info, extended, scanCode)); // handle some keys via table lookup - char c = 0; KeyID id = s_virtualKey[vkCode][extended]; if (id == kKeyNone) { - // not in table - - // save the control state then clear it. ToAscii() maps ctrl+letter - // to the corresponding control code and ctrl+backspace to delete. - // we don't want that translation so we clear the control modifier - // state. however, if we want to simulate AltGr (which is ctrl+alt) - // then we must not clear it. - BYTE keys[256]; - memcpy(keys, m_keys, sizeof(keys)); - BYTE control = keys[VK_CONTROL]; - BYTE menu = keys[VK_MENU]; - if ((control & 0x80) == 0 || (menu & 0x80) == 0) { - keys[VK_LCONTROL] = 0; - keys[VK_RCONTROL] = 0; - keys[VK_CONTROL] = 0; - } - else { - keys[VK_LCONTROL] = 0x80; - keys[VK_CONTROL] = 0x80; - keys[VK_LMENU] = 0x80; - keys[VK_MENU] = 0x80; - } - - // map to a character - bool isMenu = ((menu & 0x80) != 0); - c = mapToCharacter(vkCode, scanCode, keys, press, isMenu, hkl); - - // if mapping failed and ctrl and alt are pressed then try again - // with both not pressed. this handles the case where ctrl and - // alt are being used as individual modifiers rather than AltGr. - if (c == 0 && (control & 0x80) != 0 && (menu & 0x80) != 0) { - keys[VK_LCONTROL] = 0; - keys[VK_RCONTROL] = 0; - keys[VK_CONTROL] = 0; - keys[VK_LMENU] = 0; - keys[VK_RMENU] = 0; - keys[VK_MENU] = 0; - c = mapToCharacter(vkCode, scanCode, keys, press, isMenu, hkl); - } - - // map character to key id + // not in table; map character to key id if (c != 0) { if ((c & 0x80u) != 0) { // character is not really ASCII. instead it's some @@ -1155,7 +1120,7 @@ CMSWindowsKeyMapper::mapKeyFromEvent(WPARAM vkCode, LPARAM info, // required (only because it solves the problems we've seen // so far). in the second, we'll use whatever the keyboard // state says. - WORD virtualKeyAndModifierState = VkKeyScanEx(c, hkl); + WORD virtualKeyAndModifierState = VkKeyScanEx(c, m_keyLayout); if (virtualKeyAndModifierState == 0xffff) { // there is no mapping. assume AltGr. LOG((CLOG_DEBUG1 "no VkKeyScan() mapping")); @@ -1233,8 +1198,7 @@ UINT CMSWindowsKeyMapper::keyToScanCode(KeyButton* virtualKey) const { // try mapping given virtual key - HKL hkl = GetKeyboardLayout(0); - UINT code = MapVirtualKeyEx((*virtualKey) & 0xffu, 0, hkl); + UINT code = MapVirtualKeyEx((*virtualKey) & 0xffu, 0, m_keyLayout); if (code != 0) { return code; } @@ -1265,17 +1229,17 @@ CMSWindowsKeyMapper::keyToScanCode(KeyButton* virtualKey) const case VK_LSHIFT: case VK_RSHIFT: *virtualKey = VK_SHIFT; - return MapVirtualKeyEx(VK_SHIFT, 0, hkl); + return MapVirtualKeyEx(VK_SHIFT, 0, m_keyLayout); case VK_LCONTROL: case VK_RCONTROL: *virtualKey = VK_CONTROL; - return MapVirtualKeyEx(VK_CONTROL, 0, hkl); + return MapVirtualKeyEx(VK_CONTROL, 0, m_keyLayout); case VK_LMENU: case VK_RMENU: *virtualKey = VK_MENU; - return MapVirtualKeyEx(VK_MENU, 0, hkl); + return MapVirtualKeyEx(VK_MENU, 0, m_keyLayout); default: return 0; @@ -1528,93 +1492,9 @@ CMSWindowsKeyMapper::isDeadChar(TCHAR c, HKL hkl, bool menu) const toAscii(' ', hkl, 0, &dummy); // put old dead key back if there was one - if (old == 2) { + if (old == 1 && ascii != ' ') { toAscii(static_cast(ascii & 0xffu), hkl, menu, &dummy); } return isDead; } - -bool -CMSWindowsKeyMapper::putBackDeadChar(TCHAR c, HKL hkl, bool menu) const -{ - return (toAscii(c, hkl, menu, NULL) < 0); -} - -TCHAR -CMSWindowsKeyMapper::getSavedDeadChar(HKL hkl) const -{ - WORD old; - int nOld = toAscii(' ', hkl, false, &old); - if (nOld == 1 || nOld == 2) { - TCHAR c = static_cast(old & 0xffu); - if (nOld == 2 || isDeadChar(c, hkl, false)) { - return c; - } - } - return 0; -} - -char -CMSWindowsKeyMapper::mapToCharacter(UINT vkCode, UINT scanCode, - BYTE* keys, bool press, bool isMenu, HKL hkl) const -{ - // get contents of keyboard layout buffer and clear out that - // buffer. we don't want anything placed there by some other - // app interfering and we need to put anything there back in - // place when we're done. - TCHAR oldDeadKey = getSavedDeadChar(hkl); - - // put our previous dead key, if any, in the layout buffer - putBackDeadChar(m_deadKey, hkl, false); - m_deadKey = 0; - - // process key - WORD ascii; - int result = ToAsciiEx(vkCode, scanCode, keys, &ascii, - isMenu ? 1 : 0, hkl); - - // if result is less than zero then it was a dead key - char c = 0; - if (result < 0) { - // save dead key if a key press. we catch the dead key - // release in the result == 2 case below. - if (press) { - m_deadKey = static_cast(ascii & 0xffu); - } - } - - // if result is 1 then the key was succesfully converted - else if (result == 1) { - c = static_cast(ascii & 0xff); - } - - // if result is 2 and the two characters are the same and this - // is a key release then a dead key was released. save the - // dead key. if the two characters are the same and this is - // not a release then a dead key was pressed twice. send the - // dead key. - else if (result == 2) { - if (((ascii & 0xff00u) >> 8) == (ascii & 0x00ffu)) { - if (!press) { - m_deadKey = static_cast(ascii & 0xffu); - } - else { - putBackDeadChar(oldDeadKey, hkl, false); - result = toAscii(' ', hkl, false, &ascii); - c = static_cast((ascii >> 8) & 0xffu); - } - } - } - - // clear keyboard layout buffer. this removes any dead key we - // may have just put there. - toAscii(' ', hkl, false, NULL); - - // restore keyboard layout buffer so a dead key inserted by - // another app doesn't disappear mysteriously (from its point - // of view). - putBackDeadChar(oldDeadKey, hkl, false); - - return c; -} diff --git a/lib/platform/CMSWindowsKeyMapper.h b/lib/platform/CMSWindowsKeyMapper.h index 5bcfcedf..7292e675 100644 --- a/lib/platform/CMSWindowsKeyMapper.h +++ b/lib/platform/CMSWindowsKeyMapper.h @@ -44,6 +44,13 @@ public: */ void updateKey(KeyButton key, bool pressed); + //! Set the active keyboard layout + /*! + Uses \p keyLayout when finding scan codes via \c keyToScanCode() + and mapping keys in \c mapKeyFromEvent(). + */ + void setKeyLayout(HKL keyLayout); + //@} //! @name accessors //@{ @@ -67,7 +74,7 @@ public: to a modifier mask. If \c altgr is non-NULL it's set to true if the key requires AltGr and false otherwise. */ - KeyID mapKeyFromEvent(WPARAM vkCode, LPARAM info, + KeyID mapKeyFromEvent(WPARAM charAndVirtKey, LPARAM info, KeyModifierMask* maskOut, bool* altgr) const; //! Test shadow key state @@ -127,19 +134,6 @@ private: // return true iff \c c is a dead character bool isDeadChar(TCHAR c, HKL hkl, bool menu) const; - // put back dead key into ToAscii() internal buffer. returns true - // iff the character was a dead key. - bool putBackDeadChar(TCHAR c, HKL hkl, bool menu) const; - - // get the dead key saved in the given keyboard layout, or 0 if none - TCHAR getSavedDeadChar(HKL hkl) const; - - // map the given virtual key, scan code, and keyboard state to a - // character, if possible. this has the side effect of updating - // m_deadKey. - char mapToCharacter(UINT vkCode, UINT scanCode, - BYTE* keys, bool press, bool isMenu, HKL hkl) const; - private: class CModifierKeys { public: @@ -150,6 +144,7 @@ private: BYTE m_keys[256]; mutable TCHAR m_deadKey; + HKL m_keyLayout; static const CModifierKeys s_modifiers[]; static const char* s_vkToName[]; diff --git a/lib/platform/CMSWindowsScreen.cpp b/lib/platform/CMSWindowsScreen.cpp index 1329e4c3..ae15ffb8 100644 --- a/lib/platform/CMSWindowsScreen.cpp +++ b/lib/platform/CMSWindowsScreen.cpp @@ -128,6 +128,7 @@ CMSWindowsScreen::CMSWindowsScreen(bool isPrimary) : m_sequenceNumber(0), m_mark(0), m_markReceived(0), + m_keyLayout(NULL), m_timer(NULL), m_screensaver(NULL), m_screensaverNotify(false), @@ -290,7 +291,17 @@ CMSWindowsScreen::enter() bool CMSWindowsScreen::leave() { - sendDeskMessage(SYNERGY_MSG_LEAVE, 0, 0); + // get keyboard layout of foreground window. we'll use this + // keyboard layout for translating keys sent to clients. + HWND window = GetForegroundWindow(); + DWORD thread = GetWindowThreadProcessId(window, NULL); + m_keyLayout = GetKeyboardLayout(thread); + + // tell the key mapper about the keyboard layout + m_keyMapper.setKeyLayout(m_keyLayout); + + // tell desk that we're leaving and tell it the keyboard layout + sendDeskMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0); if (m_isPrimary) { /* XXX @@ -543,7 +554,7 @@ CMSWindowsScreen::fakeKeyEvent(KeyButton virtualKey, bool press) const if (!press) { flags |= KEYEVENTF_KEYUP; } - const UINT code = m_keyMapper.keyToScanCode(&virtualKey); + UINT code = m_keyMapper.keyToScanCode(&virtualKey); sendDeskMessage(SYNERGY_MSG_FAKE_KEY, flags, MAKEWORD(static_cast(code), static_cast(virtualKey & 0xffu))); @@ -977,6 +988,9 @@ CMSWindowsScreen::onMark(UInt32 mark) bool CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam) { + WPARAM charAndVirtKey = wParam; + wParam &= 0xffu; + // ignore message if posted prior to last mark change if (!ignore()) { // check for ctrl+alt+del emulation @@ -992,8 +1006,8 @@ CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam) // process key normally bool altgr; KeyModifierMask mask; - const KeyID key = m_keyMapper.mapKeyFromEvent(wParam, - lParam, &mask, &altgr); + const KeyID key = m_keyMapper.mapKeyFromEvent( + charAndVirtKey, lParam, &mask, &altgr); KeyButton button = static_cast( (lParam & 0x00ff0000u) >> 16); if (key != kKeyNone && key != kKeyMultiKey) { @@ -1654,8 +1668,8 @@ CMSWindowsScreen::deskMouseMove(SInt32 x, SInt32 y) const SInt32 w = GetSystemMetrics(SM_CXSCREEN); SInt32 h = GetSystemMetrics(SM_CYSCREEN); mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, - (DWORD)((65536.0 * x) / w), - (DWORD)((65536.0 * y) / h), + (DWORD)((65535.0f * x) / (w - 1) + 0.5f), + (DWORD)((65535.0f * y) / (h - 1) + 0.5f), 0, 0); } @@ -1712,43 +1726,45 @@ CMSWindowsScreen::deskMouseMove(SInt32 x, SInt32 y) const } void -CMSWindowsScreen::deskEnter(CDesk* desk, DWORD& cursorThreadID) +CMSWindowsScreen::deskEnter(CDesk* desk) { if (m_isPrimary) { - if (desk->m_lowLevel) { - if (cursorThreadID != 0) { - AttachThreadInput(desk->m_threadID, cursorThreadID, TRUE); - ShowCursor(TRUE); - AttachThreadInput(desk->m_threadID, cursorThreadID, FALSE); - cursorThreadID = 0; - } - } + ShowCursor(TRUE); } ShowWindow(desk->m_window, SW_HIDE); } void -CMSWindowsScreen::deskLeave(CDesk* desk, DWORD& cursorThreadID) +CMSWindowsScreen::deskLeave(CDesk* desk, HKL keyLayout) { if (m_isPrimary) { - // we don't need a window to capture input but we need a window - // to hide the cursor when using low-level hooks. also take the - // activation so we use our keyboard layout, not the layout of - // whatever window was active. + // map a window to hide the cursor and to use whatever keyboard + // layout we choose rather than the keyboard layout of the last + // active window. + int x, y, w, h; if (desk->m_lowLevel) { - SetWindowPos(desk->m_window, HWND_TOPMOST, - m_xCenter, m_yCenter, 1, 1, SWP_NOACTIVATE); - ShowWindow(desk->m_window, SW_SHOW); - if (cursorThreadID == 0) { - HWND hwnd = GetForegroundWindow(); - cursorThreadID = GetWindowThreadProcessId(hwnd, NULL); - if (cursorThreadID != 0) { - AttachThreadInput(desk->m_threadID, cursorThreadID, TRUE); - ShowCursor(FALSE); - AttachThreadInput(desk->m_threadID, cursorThreadID, FALSE); - } - } + // with a low level hook the cursor will never budge so + // just a 1x1 window is sufficient. + x = m_xCenter; + y = m_yCenter; + w = 1; + h = 1; } + else { + // with regular hooks the cursor will jitter as it's moved + // by the user then back to the center by us. to be sure + // we never lose it, cover all the monitors with the window. + x = m_x; + y = m_y; + w = m_w; + h = m_h; + } + SetWindowPos(desk->m_window, HWND_TOPMOST, x, y, w, h, SWP_NOACTIVATE); + ShowWindow(desk->m_window, SW_SHOW); + ShowCursor(FALSE); + + // switch to requested keyboard layout + ActivateKeyboardLayout(keyLayout, 0); } else { // move hider window under the cursor center @@ -1767,9 +1783,6 @@ CMSWindowsScreen::deskThread(void* vdesk) { MSG msg; - // id of thread that had cursor when we were last told to hide it - DWORD cursorThreadID = 0; - // use given desktop for this thread CDesk* desk = reinterpret_cast(vdesk); desk->m_threadID = GetCurrentThreadId(); @@ -1833,11 +1846,11 @@ CMSWindowsScreen::deskThread(void* vdesk) break; case SYNERGY_MSG_ENTER: - deskEnter(desk, cursorThreadID); + deskEnter(desk); break; case SYNERGY_MSG_LEAVE: - deskLeave(desk, cursorThreadID); + deskLeave(desk, (HKL)msg.wParam); break; case SYNERGY_MSG_FAKE_KEY: @@ -1969,7 +1982,7 @@ CMSWindowsScreen::checkDesk() // hide cursor on new desk if (!m_isOnScreen) { - sendDeskMessage(SYNERGY_MSG_LEAVE, 0, 0); + sendDeskMessage(SYNERGY_MSG_LEAVE, (WPARAM)m_keyLayout, 0); } } } diff --git a/lib/platform/CMSWindowsScreen.h b/lib/platform/CMSWindowsScreen.h index ddae1f02..15c8dfc9 100644 --- a/lib/platform/CMSWindowsScreen.h +++ b/lib/platform/CMSWindowsScreen.h @@ -186,8 +186,8 @@ private: static LRESULT CALLBACK secondaryDeskProc(HWND, UINT, WPARAM, LPARAM); void deskMouseMove(SInt32 x, SInt32 y) const; - void deskEnter(CDesk* desk, DWORD& cursorThreadID); - void deskLeave(CDesk* desk, DWORD& cursorThreadID); + void deskEnter(CDesk* desk); + void deskLeave(CDesk* desk, HKL keyLayout); void deskThread(void* vdesk); CDesk* addDesk(const CString& name, HDESK hdesk); void removeDesks(); @@ -234,6 +234,9 @@ private: // the main loop's thread id DWORD m_threadID; + // the keyboard layout to use when off primary screen + HKL m_keyLayout; + // the timer used to check for desktop switching CEventQueueTimer* m_timer; diff --git a/lib/platform/CSynergyHook.cpp b/lib/platform/CSynergyHook.cpp index 4b58ac8d..f7003c4a 100644 --- a/lib/platform/CSynergyHook.cpp +++ b/lib/platform/CSynergyHook.cpp @@ -24,6 +24,12 @@ // #define NO_GRAB_KEYBOARD 0 +// +// debugging compile flag. when not zero the server will not +// install low level hooks. +// +#define NO_LOWLEVEL_HOOKS 0 + // // extra mouse wheel stuff // @@ -86,8 +92,11 @@ static SInt32 g_xScreen = 0; static SInt32 g_yScreen = 0; static SInt32 g_wScreen = 0; static SInt32 g_hScreen = 0; -static HCURSOR g_cursor = NULL; -static DWORD g_cursorThread = 0; +static WPARAM g_deadVirtKey = 0; +static LPARAM g_deadLParam = 0; +static BYTE g_deadKeyState[256] = { 0 }; +static DWORD g_hookThread = 0; +static DWORD g_attachedThread = 0; #pragma data_seg() @@ -105,49 +114,217 @@ extern "C" int _fltused=0; static void -hideCursor(DWORD thread) +attachThreadToForeground() { - // we should be running the context of the window who's cursor - // we want to hide so we shouldn't have to attach thread input - // but we'll check to make sure. - g_cursorThread = thread; - if (g_cursorThread != 0) { - DWORD myThread = GetCurrentThreadId(); - if (myThread != g_cursorThread) - AttachThreadInput(myThread, g_cursorThread, TRUE); - g_cursor = SetCursor(NULL); - if (myThread != g_cursorThread) - AttachThreadInput(myThread, g_cursorThread, FALSE); + // only attach threads if using low level hooks. a low level hook + // runs in the thread that installed the hook but we have to make + // changes that require being attached to the target thread (which + // should be the foreground window). a regular hook runs in the + // thread that just removed the event from its queue so we're + // already in the right thread. + if (g_hookThread != 0) { + HWND window = GetForegroundWindow(); + DWORD threadID = GetWindowThreadProcessId(window, NULL); + // skip if no change + if (g_attachedThread != threadID) { + // detach from previous thread + if (g_attachedThread != 0 && g_attachedThread != g_hookThread) { + AttachThreadInput(g_hookThread, g_attachedThread, FALSE); + } + // attach to new thread + g_attachedThread = threadID; + if (g_attachedThread != 0 && g_attachedThread != g_hookThread) { + AttachThreadInput(g_hookThread, g_attachedThread, TRUE); + } + } } } static void -restoreCursor() +detachThread() { - // restore the show cursor in the window we hid it last - if (g_cursor != NULL && g_cursorThread != 0) { - DWORD myThread = GetCurrentThreadId(); - if (myThread != g_cursorThread) - AttachThreadInput(myThread, g_cursorThread, TRUE); - SetCursor(g_cursor); - if (myThread != g_cursorThread) - AttachThreadInput(myThread, g_cursorThread, FALSE); + if (g_attachedThread != 0) { + AttachThreadInput(g_hookThread, g_attachedThread, FALSE); + g_attachedThread = 0; } - g_cursor = NULL; - g_cursorThread = 0; } #if !NO_GRAB_KEYBOARD +static +WPARAM +makeKeyMsg(UINT virtKey, char c) +{ + return MAKEWPARAM(MAKEWORD(virtKey & 0xff, (BYTE)c), 0); +} + +static +void +keyboardGetState(BYTE keys[256]) +{ + if (g_hookThread != 0) { + GetKeyboardState(keys); + } + else { + SHORT key; + for (int i = 0; i < 256; ++i) { + key = GetAsyncKeyState(i); + keys[i] = (BYTE)((key < 0) ? 0x80u : 0); + } + key = GetKeyState(VK_CAPITAL); + keys[VK_CAPITAL] = (BYTE)(((key < 0) ? 0x80 : 0) | (key & 1)); + } +} + static bool keyboardHookHandler(WPARAM wParam, LPARAM lParam) { + attachThreadToForeground(); + + // check for dead keys. we don't forward those to our window. + // instead we'll leave the key in the keyboard layout (a buffer + // internal to the system) for translation when the next key is + // pressed. + UINT c = MapVirtualKey(wParam, 2); + if ((c & 0x80000000u) != 0) { + if ((lParam & 0x80000000u) == 0) { + if (g_deadVirtKey == 0) { + // dead key press, no dead key in the buffer + g_deadVirtKey = wParam; + g_deadLParam = lParam; + keyboardGetState(g_deadKeyState); + return false; + } + // second dead key press in a row so let it pass + } + else { + // dead key release + return false; + } + } + + // convert key to a character. this combines a saved dead key, + // if any, with this key. however, the dead key must remain in + // the keyboard layout for the application receiving this event + // so it can also convert the key to a character. we only do + // this on a key press. + WPARAM charAndVirtKey = (wParam & 0xffu); + if (c != 0) { + // we need the keyboard state for ToAscii() + BYTE keys[256]; + keyboardGetState(keys); + + // ToAscii() maps ctrl+letter to the corresponding control code + // and ctrl+backspace to delete. we don't want those translations + // so clear the control modifier state. however, if we want to + // simulate AltGr (which is ctrl+alt) then we must not clear it. + BYTE control = keys[VK_CONTROL]; + BYTE menu = keys[VK_MENU]; + if ((control & 0x80) == 0 || (menu & 0x80) == 0) { + keys[VK_LCONTROL] = 0; + keys[VK_RCONTROL] = 0; + keys[VK_CONTROL] = 0; + } + else { + keys[VK_LCONTROL] = 0x80; + keys[VK_CONTROL] = 0x80; + keys[VK_LMENU] = 0x80; + keys[VK_MENU] = 0x80; + } + + // ToAscii() needs to know if a menu is active for some reason. + // we don't know and there doesn't appear to be any way to find + // out. so we'll just assume a menu is active if the menu key + // is down. + // XXX -- figure out some way to check if a menu is active + UINT flags = 0; + if ((menu & 0x80) != 0) + flags |= 1; + + // map the key event to a character. this has the side + // effect of removing the dead key from the system's keyboard + // layout buffer. + WORD c = 0; + UINT scanCode = ((lParam & 0x00ff0000u) >> 16); + int n = ToAscii(wParam, scanCode, keys, &c, flags); + + // if mapping failed and ctrl and alt are pressed then try again + // with both not pressed. this handles the case where ctrl and + // alt are being used as individual modifiers rather than AltGr. + // we have to put the dead key back first, if there was one. + if (n == 0 && (control & 0x80) != 0 && (menu & 0x80) != 0) { + if (g_deadVirtKey != 0) { + ToAscii(g_deadVirtKey, (g_deadLParam & 0x00ff0000u) >> 16, + g_deadKeyState, &c, flags); + } + keys[VK_LCONTROL] = 0; + keys[VK_RCONTROL] = 0; + keys[VK_CONTROL] = 0; + keys[VK_LMENU] = 0; + keys[VK_RMENU] = 0; + keys[VK_MENU] = 0; + n = ToAscii(wParam, scanCode, keys, &c, flags); + } + + switch (n) { + default: + // key is a dead key; we're not expecting this since we + // bailed out above for any dead key. + g_deadVirtKey = wParam; + g_deadLParam = lParam; + break; + + case 0: + // key doesn't map to a character. this can happen if + // non-character keys are pressed after a dead key. + break; + + case 1: + // key maps to a character composed with dead key + charAndVirtKey = makeKeyMsg(wParam, (char)LOBYTE(c)); + break; + + case 2: { + // previous dead key not composed. send a fake key press + // and release for the dead key to our window. + WPARAM deadCharAndVirtKey = + makeKeyMsg(g_deadVirtKey, (char)LOBYTE(c)); + PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, + deadCharAndVirtKey, g_deadLParam & 0x7fffffffu); + PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, + deadCharAndVirtKey, g_deadLParam | 0x80000000u); + + // use uncomposed character + charAndVirtKey = makeKeyMsg(wParam, (char)HIBYTE(c)); + break; + } + } + + // put back the dead key, if any, for the application to use + if (g_deadVirtKey != 0) { + ToAscii(g_deadVirtKey, (g_deadLParam & 0x00ff0000u) >> 16, + g_deadKeyState, &c, flags); + } + + // clear out old dead key state + g_deadVirtKey = 0; + g_deadLParam = 0; + } + // 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); + PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, charAndVirtKey, lParam); + + // send fake key release if the user just pressed two dead keys + // in a row, otherwise we'll lose the release because we always + // return from the top of this function for all dead key releases. + if ((c & 0x80000000u) != 0) { + PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, + charAndVirtKey, lParam | 0x80000000u); + } if (g_mode == kHOOK_RELAY_EVENTS) { // let certain keys pass through @@ -160,8 +337,21 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam) // lights may not stay synchronized. break; + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_MENU: + case VK_LMENU: + case VK_RMENU: + case VK_HANGUL: + // always pass the shift modifiers + break; + default: - // discard event + // discard return true; } } @@ -174,6 +364,8 @@ static bool mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data) { + attachThreadToForeground(); + switch (wParam) { case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: @@ -213,19 +405,6 @@ mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data) case WM_NCMOUSEMOVE: case WM_MOUSEMOVE: if (g_mode == kHOOK_RELAY_EVENTS) { - // 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; @@ -347,7 +526,7 @@ getMessageHook(int code, WPARAM wParam, LPARAM lParam) return CallNextHookEx(g_getMessage, code, wParam, lParam); } -#if (_WIN32_WINNT >= 0x0400) +#if (_WIN32_WINNT >= 0x0400) && !NO_LOWLEVEL_HOOKS // // low-level keyboard hook -- this allows us to capture and handle @@ -524,15 +703,13 @@ init(DWORD threadID) g_threadID = threadID; // set defaults - g_mode = kHOOK_DISABLE; - g_zoneSides = 0; - g_zoneSize = 0; - g_xScreen = 0; - g_yScreen = 0; - g_wScreen = 0; - g_hScreen = 0; - g_cursor = NULL; - g_cursorThread = 0; + g_mode = kHOOK_DISABLE; + g_zoneSides = 0; + g_zoneSize = 0; + g_xScreen = 0; + g_yScreen = 0; + g_wScreen = 0; + g_hScreen = 0; return 1; } @@ -562,6 +739,10 @@ install() return kHOOK_FAILED; } + // discard old dead keys + g_deadVirtKey = 0; + g_deadLParam = 0; + // check for mouse wheel support g_wheelSupport = getWheelSupport(); @@ -573,35 +754,43 @@ install() 0); } - // install keyboard hook -#if !NO_GRAB_KEYBOARD -#if (_WIN32_WINNT >= 0x0400) - g_keyboardLL = SetWindowsHookEx(WH_KEYBOARD_LL, - &keyboardLLHook, - g_hinstance, - 0); -#endif - if (g_keyboardLL == NULL) { - g_keyboard = SetWindowsHookEx(WH_KEYBOARD, - &keyboardHook, - g_hinstance, - 0); - } -#endif - - // install mouse hook -#if (_WIN32_WINNT >= 0x0400) + // install low-level hooks. we require that they both get installed. +#if (_WIN32_WINNT >= 0x0400) && !NO_LOWLEVEL_HOOKS g_mouseLL = SetWindowsHookEx(WH_MOUSE_LL, &mouseLLHook, g_hinstance, 0); +#if !NO_GRAB_KEYBOARD + g_keyboardLL = SetWindowsHookEx(WH_KEYBOARD_LL, + &keyboardLLHook, + g_hinstance, + 0); + if (g_mouseLL == NULL || g_keyboardLL == NULL) { + if (g_keyboardLL != NULL) { + UnhookWindowsHookEx(g_keyboardLL); + g_keyboardLL = NULL; + } + if (g_mouseLL != NULL) { + UnhookWindowsHookEx(g_mouseLL); + g_mouseLL = NULL; + } + } #endif +#endif + + // install regular hooks if (g_mouseLL == NULL) { g_mouse = SetWindowsHookEx(WH_MOUSE, &mouseHook, g_hinstance, 0); } + if (g_keyboardLL == NULL) { + g_keyboard = SetWindowsHookEx(WH_KEYBOARD, + &keyboardHook, + g_hinstance, + 0); + } // check that we got all the hooks we wanted if ((g_getMessage == NULL && g_wheelSupport == kWheelOld) || @@ -614,6 +803,7 @@ install() } if (g_keyboardLL != NULL || g_mouseLL != NULL) { + g_hookThread = GetCurrentThreadId(); return kHOOK_OKAY_LL; } @@ -625,6 +815,13 @@ uninstall(void) { assert(g_hinstance != NULL); + // discard old dead keys + g_deadVirtKey = 0; + g_deadLParam = 0; + + // detach from thread + detachThread(); + // uninstall hooks if (g_keyboardLL != NULL) { UnhookWindowsHookEx(g_keyboardLL); @@ -648,9 +845,6 @@ uninstall(void) } g_wheelSupport = kWheelNone; - // show the cursor - restoreCursor(); - return 1; } @@ -719,9 +913,6 @@ setMode(EHookMode mode) return; } g_mode = mode; - if (g_mode != kHOOK_RELAY_EVENTS) { - restoreCursor(); - } } } diff --git a/lib/server/CServer.cpp b/lib/server/CServer.cpp index 5ee5b946..b870b968 100644 --- a/lib/server/CServer.cpp +++ b/lib/server/CServer.cpp @@ -1153,7 +1153,6 @@ void CServer::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button) { LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button)); -LOG((CLOG_INFO "onKeyDown: id=%d mask=0x%04x button=0x%04x", id, mask, button)); assert(m_active != NULL); // handle command keys @@ -1169,7 +1168,6 @@ void CServer::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button) { LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button)); -LOG((CLOG_INFO "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button)); assert(m_active != NULL); // handle command keys @@ -1186,7 +1184,6 @@ CServer::onKeyRepeat(KeyID id, KeyModifierMask mask, SInt32 count, KeyButton button) { LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button)); -LOG((CLOG_INFO "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button)); assert(m_active != NULL); // handle command keys @@ -1225,7 +1222,10 @@ CServer::onMouseMovePrimary(SInt32 x, SInt32 y) LOG((CLOG_DEBUG2 "onMouseMovePrimary %d,%d", x, y)); // mouse move on primary (server's) screen - assert(m_active == m_primaryClient); + if (m_active != m_primaryClient) { + // stale event -- we're actually on a secondary screen + return false; + } // save position m_x = x; @@ -1282,13 +1282,7 @@ CServer::onMouseMoveSecondary(SInt32 dx, SInt32 dy) // mouse move on secondary (client's) screen assert(m_active != NULL); if (m_active == m_primaryClient) { - // we're actually on the primary screen. this can happen - // when the primary screen begins processing a mouse move - // for a secondary screen, then the active (secondary) - // screen disconnects causing us to jump to the primary - // screen, and finally the primary screen finishes - // processing the mouse move, still thinking it's for - // a secondary screen. we just ignore the motion. + // stale event -- we're actually on the primary screen return; }