diff --git a/lib/platform/CMSWindowsDesks.cpp b/lib/platform/CMSWindowsDesks.cpp index 0e2b7734..28fcca22 100644 --- a/lib/platform/CMSWindowsDesks.cpp +++ b/lib/platform/CMSWindowsDesks.cpp @@ -13,7 +13,6 @@ */ #include "CMSWindowsDesks.h" -#include "CMSWindowsDesktop.h" #include "CMSWindowsScreen.h" #include "IScreenSaver.h" #include "XScreen.h" @@ -200,6 +199,27 @@ CMSWindowsDesks::fakeKeyEvent( KeyButton button, UINT virtualKey, bool press, bool /*isAutoRepeat*/) const { + // win 95 family doesn't understand handed modifier virtual keys + if (m_is95Family) { + switch (virtualKey) { + case VK_LSHIFT: + case VK_RSHIFT: + virtualKey = VK_SHIFT; + break; + + case VK_LCONTROL: + case VK_RCONTROL: + virtualKey = VK_CONTROL; + break; + + case VK_LMENU: + case VK_RMENU: + virtualKey = VK_MENU; + break; + } + } + + // synthesize event DWORD flags = 0; if (((button & 0x100u) != 0)) { flags |= KEYEVENTF_EXTENDEDKEY; @@ -552,6 +572,14 @@ CMSWindowsDesks::deskLeave(CDesk* desk, HKL keyLayout) SetWindowPos(desk->m_window, HWND_TOPMOST, x, y, w, h, SWP_NOACTIVATE | SWP_SHOWWINDOW); + // if not using low-level hooks we have to also activate the + // window to ensure we don't lose keyboard focus. + // FIXME -- see if this can be avoided. if so then always + // disable the window (see handling of SYNERGY_MSG_SWITCH). + if (!desk->m_lowLevel) { + SetActiveWindow(desk->m_window); + } + // switch to requested keyboard layout ActivateKeyboardLayout(keyLayout, 0); } @@ -594,11 +622,6 @@ CMSWindowsDesks::deskThread(void* vdesk) // ignore LOG((CLOG_DEBUG "can't create desk window for %s", desk->m_name.c_str())); } - - // a window on the primary screen should never activate - if (m_isPrimary && desk->m_window != NULL) { - EnableWindow(desk->m_window, FALSE); - } } // tell main thread that we're ready @@ -636,6 +659,10 @@ CMSWindowsDesks::deskThread(void* vdesk) desk->m_lowLevel = true; break; } + + // a window on the primary screen with low-level hooks + // should never activate. + EnableWindow(desk->m_window, desk->m_lowLevel ? FALSE : TRUE); } break; diff --git a/lib/platform/CMSWindowsKeyState.cpp b/lib/platform/CMSWindowsKeyState.cpp index 1d9554d7..427323f9 100644 --- a/lib/platform/CMSWindowsKeyState.cpp +++ b/lib/platform/CMSWindowsKeyState.cpp @@ -573,7 +573,8 @@ const KeyID CMSWindowsKeyState::s_virtualKey[][2] = /* 0xff */ kKeyNone, kKeyNone // reserved }; -// map special KeyID keys to virtual key codes +// map special KeyID keys to virtual key codes plus whether or not +// the key maps to an extended scan code const UINT CMSWindowsKeyState::s_mapE000[] = { /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0, @@ -597,15 +598,15 @@ const UINT CMSWindowsKeyState::s_mapE000[] = /* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x98 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 */ 0, 0, 0, 0, - /* 0xa4 */ 0, 0, VK_BROWSER_BACK, VK_BROWSER_FORWARD, - /* 0xa8 */ VK_BROWSER_REFRESH, VK_BROWSER_STOP, - /* 0xaa */ VK_BROWSER_SEARCH, VK_BROWSER_FAVORITES, - /* 0xac */ VK_BROWSER_HOME, VK_VOLUME_MUTE, - /* 0xae */ VK_VOLUME_DOWN, VK_VOLUME_UP, - /* 0xb0 */ VK_MEDIA_NEXT_TRACK, VK_MEDIA_PREV_TRACK, - /* 0xb2 */ VK_MEDIA_STOP, VK_MEDIA_PLAY_PAUSE, - /* 0xb4 */ VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, - /* 0xb6 */ VK_LAUNCH_APP1, VK_LAUNCH_APP2, + /* 0xa4 */ 0, 0, VK_BROWSER_BACK | 0x100u, VK_BROWSER_FORWARD | 0x100u, + /* 0xa8 */ VK_BROWSER_REFRESH | 0x100u, VK_BROWSER_STOP | 0x100u, + /* 0xaa */ VK_BROWSER_SEARCH | 0x100u, VK_BROWSER_FAVORITES | 0x100u, + /* 0xac */ VK_BROWSER_HOME | 0x100u, VK_VOLUME_MUTE | 0x100u, + /* 0xae */ VK_VOLUME_DOWN | 0x100u, VK_VOLUME_UP | 0x100u, + /* 0xb0 */ VK_MEDIA_NEXT_TRACK | 0x100u, VK_MEDIA_PREV_TRACK | 0x100u, + /* 0xb2 */ VK_MEDIA_STOP | 0x100u, VK_MEDIA_PLAY_PAUSE | 0x100u, + /* 0xb4 */ VK_LAUNCH_MAIL | 0x100u, VK_LAUNCH_MEDIA_SELECT | 0x100u, + /* 0xb6 */ VK_LAUNCH_APP1 | 0x100u, VK_LAUNCH_APP2 | 0x100u, /* 0xb8 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc8 */ 0, 0, 0, 0, 0, 0, 0, 0, @@ -666,35 +667,41 @@ const UINT CMSWindowsKeyState::s_mapEF00[] = /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0x48 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x50 */ VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, - /* 0x54 */ VK_DOWN, VK_PRIOR, VK_NEXT, VK_END, + /* 0x50 */ VK_HOME | 0x100u, VK_LEFT | 0x100u, + /* 0x52 */ VK_UP | 0x100u, VK_RIGHT | 0x100u, + /* 0x54 */ VK_DOWN | 0x100u, VK_PRIOR | 0x100u, + /* 0x56 */ VK_NEXT | 0x100u, VK_END | 0x100u, /* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x60 */ VK_SELECT, VK_SNAPSHOT, VK_EXECUTE, VK_INSERT, - /* 0x64 */ 0, 0, 0, VK_APPS, - /* 0x68 */ 0, 0, VK_HELP, VK_CANCEL, 0, 0, 0, 0, + /* 0x60 */ VK_SELECT, VK_SNAPSHOT, VK_EXECUTE, VK_INSERT | 0x100u, + /* 0x64 */ 0, 0, 0, VK_APPS | 0x100u, + /* 0x68 */ 0, 0, VK_HELP, VK_CANCEL | 0x100u, 0, 0, 0, 0, /* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0x78 */ 0, 0, 0, 0, 0, 0, 0, VK_NUMLOCK, + /* 0x78 */ 0, 0, 0, 0, 0, 0, 0, VK_NUMLOCK | 0x100u, /* 0x80 */ VK_SPACE, 0, 0, 0, 0, 0, 0, 0, - /* 0x88 */ 0, VK_TAB, 0, 0, 0, VK_RETURN, 0, 0, + /* 0x88 */ 0, VK_TAB, 0, 0, 0, VK_RETURN | 0x100u, 0, 0, /* 0x90 */ 0, 0, 0, 0, 0, VK_HOME, VK_LEFT, VK_UP, /* 0x98 */ VK_RIGHT, VK_DOWN, VK_PRIOR, VK_NEXT, /* 0x9c */ VK_END, 0, VK_INSERT, VK_DELETE, /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa8 */ 0, 0, VK_MULTIPLY, VK_ADD, - /* 0xac */ VK_DECIMAL, VK_SUBTRACT, VK_DECIMAL, VK_DIVIDE, + /* 0xac */ VK_DECIMAL, VK_SUBTRACT, VK_DECIMAL, VK_DIVIDE | 0x100u, /* 0xb0 */ VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, /* 0xb4 */ VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, /* 0xb8 */ VK_NUMPAD8, VK_NUMPAD9, 0, 0, 0, 0, VK_F1, VK_F2, /* 0xc0 */ VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_F10, - /* 0xc8 */ VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, - /* 0xd0 */ VK_F19, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, 0, 0, + /* 0xc8 */ VK_F11, VK_F12, VK_F13 | 0x100u, VK_F14 | 0x100u, + /* 0xcc */ VK_F15 | 0x100u, VK_F16 | 0x100u, + /* 0xce */ VK_F17 | 0x100u, VK_F18 | 0x100u, + /* 0xd0 */ VK_F19 | 0x100u, VK_F20 | 0x100u, + /* 0xd2 */ VK_F21 | 0x100u, VK_F22 | 0x100u, + /* 0xd4 */ VK_F23 | 0x100u, VK_F24 | 0x100u, 0, 0, /* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0xe0 */ 0, VK_LSHIFT, VK_RSHIFT, VK_LCONTROL, - /* 0xe4 */ VK_RCONTROL, VK_CAPITAL, 0, 0, - /* 0xe8 */ 0, VK_LMENU, VK_RMENU, VK_LWIN, - /* 0xec */ VK_RWIN, 0, 0, 0, + /* 0xe0 */ 0, VK_LSHIFT, VK_RSHIFT | 0x100u, VK_LCONTROL, + /* 0xe4 */ VK_RCONTROL | 0x100u, VK_CAPITAL, 0, 0, + /* 0xe8 */ 0, VK_LMENU, VK_RMENU | 0x100u, VK_LWIN | 0x100u, + /* 0xec */ VK_RWIN | 0x100u, 0, 0, 0, /* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0, - /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, VK_DELETE + /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, VK_DELETE | 0x100u }; CMSWindowsKeyState::CMSWindowsKeyState(CMSWindowsDesks* desks) : @@ -733,7 +740,7 @@ CMSWindowsKeyState::fixKey(void* target, UINT virtualKey) KeyID key = mapKeyFromEvent(virtualKey, lParam, &mask); LOG((CLOG_DEBUG1 "event: fake key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); CKeyState::sendKeyEvent(target, false, false, key, mask, 1, button); - CKeyState::setKeyDown(button, false); + setKeyDown(button, false); } } @@ -741,6 +748,7 @@ KeyID CMSWindowsKeyState::mapKeyFromEvent(WPARAM charAndVirtKey, LPARAM info, KeyModifierMask* maskOut) const { +// FIXME -- look into this // note: known microsoft bugs // Q72583 -- MapVirtualKey() maps keypad keys incorrectly // 95,98: num pad vk code -> invalid scan code @@ -837,27 +845,6 @@ CMSWindowsKeyState::virtualKeyToButton(UINT virtualKey) const return m_virtKeyToScanCode[virtualKey & 0xffu]; } -void -CMSWindowsKeyState::setKeyDown(KeyButton button, bool down) -{ - CKeyState::setKeyDown(button, down); - - // special case: we detect ctrl+alt+del being pressed on some - // systems but we don't detect the release of those keys. so - // if ctrl, alt, and del are down then mark them up. - if (down && isKeyDown(m_virtKeyToScanCode[VK_DELETE])) { - KeyModifierMask mask = getActiveModifiers(); - if ((mask & (KeyModifierControl | KeyModifierAlt)) == - (KeyModifierControl | KeyModifierAlt)) { - CKeyState::setKeyDown(m_virtKeyToScanCode[VK_LCONTROL], false); - CKeyState::setKeyDown(m_virtKeyToScanCode[VK_RCONTROL], false); - CKeyState::setKeyDown(m_virtKeyToScanCode[VK_LMENU], false); - CKeyState::setKeyDown(m_virtKeyToScanCode[VK_RMENU], false); - CKeyState::setKeyDown(m_virtKeyToScanCode[VK_DELETE], false); - } - } -} - void CMSWindowsKeyState::sendKeyEvent(void* target, bool press, bool isAutoRepeat, @@ -865,7 +852,7 @@ CMSWindowsKeyState::sendKeyEvent(void* target, SInt32 count, KeyButton button) { if (press || isAutoRepeat) { - // if AltGr required for this key then make sure + // if AltGr is required for this key then make sure // the ctrl and alt keys are *not* down on the // client. windows simulates AltGr with ctrl and // alt for some inexplicable reason and clients @@ -950,37 +937,6 @@ CMSWindowsKeyState::sendKeyEvent(void* target, } } else { - // key release. if the key isn't down according to - // our table then we never got the key press event - // for it. if it's not a modifier key then we'll - // synthesize the press first. only do this on - // the windows 95 family, which eats certain special - // keys like alt+tab, ctrl+esc, etc. - if (m_is95Family && isKeyDown(button)) { - switch (m_scanCodeToVirtKey[button]) { - case VK_LSHIFT: - case VK_RSHIFT: - case VK_SHIFT: - case VK_LCONTROL: - case VK_RCONTROL: - case VK_CONTROL: - case VK_LMENU: - case VK_RMENU: - case VK_MENU: - case VK_CAPITAL: - case VK_NUMLOCK: - case VK_SCROLL: - case VK_LWIN: - case VK_RWIN: - break; - - default: - CKeyState::sendKeyEvent(target, - true, false, key, mask, 1, button); - break; - } - } - // do key up CKeyState::sendKeyEvent(target, false, false, key, mask, 1, button); } @@ -1065,47 +1021,35 @@ CMSWindowsKeyState::doUpdateKeys() // KeyModifierModeSwitch mask can be converted to keystrokes. it // must be mapped before the Alt modifier so that the Alt modifier // takes precedence when mapping keystrokes to modifier masks. - // - // we have to explicitly set the extended key flag for some - // modifiers because the win32 API is inadequate. KeyButtons keys; - keys.push_back((KeyButton)(MapVirtualKey(VK_RMENU, 0) | 0x100)); + keys.push_back(mapVirtKeyToButton(VK_RMENU)); addModifier(KeyModifierModeSwitch, keys); keys.clear(); - keys.push_back((KeyButton)MapVirtualKey(VK_LSHIFT, 0)); - keys.push_back((KeyButton)(MapVirtualKey(VK_RSHIFT, 0) | 0x100)); + keys.push_back(mapVirtKeyToButton(VK_LSHIFT)); + keys.push_back(mapVirtKeyToButton(VK_RSHIFT)); addModifier(KeyModifierShift, keys); keys.clear(); - keys.push_back((KeyButton)MapVirtualKey(VK_LCONTROL, 0)); - keys.push_back((KeyButton)(MapVirtualKey(VK_RCONTROL, 0) | 0x100)); + keys.push_back(mapVirtKeyToButton(VK_LCONTROL)); + keys.push_back(mapVirtKeyToButton(VK_RCONTROL)); addModifier(KeyModifierControl, keys); keys.clear(); - keys.push_back((KeyButton)MapVirtualKey(VK_LMENU, 0)); - keys.push_back((KeyButton)(MapVirtualKey(VK_RMENU, 0) | 0x100)); + keys.push_back(mapVirtKeyToButton(VK_LMENU)); + keys.push_back(mapVirtKeyToButton(VK_RMENU)); addModifier(KeyModifierAlt, keys); keys.clear(); - keys.push_back((KeyButton)(MapVirtualKey(VK_LWIN, 0) | 0x100)); - keys.push_back((KeyButton)(MapVirtualKey(VK_RWIN, 0) | 0x100)); + keys.push_back(mapVirtKeyToButton(VK_LWIN)); + keys.push_back(mapVirtKeyToButton(VK_RWIN)); addModifier(KeyModifierSuper, keys); keys.clear(); - keys.push_back((KeyButton)MapVirtualKey(VK_CAPITAL, 0)); + keys.push_back(mapVirtKeyToButton(VK_CAPITAL)); addModifier(KeyModifierCapsLock, keys); keys.clear(); - keys.push_back((KeyButton)(MapVirtualKey(VK_NUMLOCK, 0) | 0x100)); + keys.push_back(mapVirtKeyToButton(VK_NUMLOCK)); addModifier(KeyModifierNumLock, keys); keys.clear(); - keys.push_back((KeyButton)MapVirtualKey(VK_SCROLL, 0)); + keys.push_back(mapVirtKeyToButton(VK_SCROLL)); addModifier(KeyModifierScrollLock, keys); -/* FIXME -- potential problem here on win me - // win me (sony vaio laptop): - // MapVirtualKey(vk, 0): - // VK_SHIFT mapped; VK_LSHIFT, VK_RSHIFT not mapped - // VK_CONTROL mapped; VK_LCONTROL, VK_RCONTROL not mapped - // VK_MENU mapped; VK_LMENU, VK_RMENU not mapped - // MapVirtualKey(sc, 3): - // all scan codes unmapped (function apparently unimplemented) -*/ BYTE keyState[256]; GetKeyboardState(keyState); for (UINT i = 1; i < 256; ++i) { @@ -1123,63 +1067,20 @@ CMSWindowsKeyState::doUpdateKeys() } // map to a scancode and back to a virtual key - UINT scancode = MapVirtualKey(i, 0); - UINT virtKey = MapVirtualKey(scancode, 3); - if (virtKey == 0) { - // assume MapVirtualKey(xxx, 3) is unimplemented - virtKey = i; - } - else if (scancode == 0) { - // the VK_PAUSE virtual key doesn't map properly - if (i == VK_PAUSE) { - // i hope this works on all keyboards - scancode = 0x45; - virtKey = i; - } - else { - continue; - } - } - - // we need some adjustments due to inadequacies in the API. - // the API provides no means to query the keyboard by scan - // code that i can see. so we're doing it by virtual key. - // but a single virtual key can map to multiple physical - // keys. for example, VK_HOME maps to NumPad 7 and to the - // (extended key) Home key. this means we can never tell - // which of the two keys is pressed. - // - // this is a problem if a key is down when this method is - // called. if the extended key is down we'll record the - // non-extended key as being down. when the extended key - // goes up, we'll record that correctly and leave the - // non-extended key as being down. to deal with that we - // always re-check the keyboard state if we think we're - // locked to a screen because a key is down. the re-check - // should clear it up. - // - // the win32 functions that take scan codes are: - - // - // if the mapped virtual key doesn't match the starting - // point then there's a really good chance that that virtual - // key is mapped to an extended key. however, this is not - // the case for modifiers that don't distinguish between left - // and right. also VK_NUMLOCK gets mapped to a non-extended - // key but it should be. - if (virtKey != i || i == VK_NUMLOCK) { - if (i != VK_SHIFT && i != VK_CONTROL && i != VK_MENU) { - scancode |= 0x100; - } + KeyButton button2; + KeyButton button = mapVirtKeyToButton(i, button2); + if (button == 0) { + continue; } // okay, now we have the scan code for the virtual key. - m_scanCodeToVirtKey[scancode] = i; - m_virtKeyToScanCode[i] = (KeyButton)scancode; + m_scanCodeToVirtKey[button] = i; + m_scanCodeToVirtKey[button2] = i; + m_virtKeyToScanCode[i] = button; // save the key state if ((keyState[i] & 0x80) != 0) { - setKeyDown((KeyButton)scancode, true); + setKeyDown(button, true); } // toggle state applies to all keys but we only want it for @@ -1214,27 +1115,27 @@ KeyButton CMSWindowsKeyState::mapKey(Keystrokes& keys, KeyID id, KeyModifierMask mask, bool isAutoRepeat) const { - UINT virtualKey = 0; + UINT extVirtualKey = 0; // check for special keys if ((id & 0xfffff000u) == 0xe000u) { if ((id & 0xff00u) == 0xe000u) { - virtualKey = s_mapE000[id & 0xffu]; + extVirtualKey = s_mapE000[id & 0xffu]; } else if ((id & 0xff00) == 0xee00) { - virtualKey = s_mapEE00[id & 0xffu]; + extVirtualKey = s_mapEE00[id & 0xffu]; } else if ((id & 0xff00) == 0xef00) { - virtualKey = s_mapEF00[id & 0xffu]; + extVirtualKey = s_mapEF00[id & 0xffu]; } - if (virtualKey == 0) { + if (extVirtualKey == 0) { LOG((CLOG_DEBUG2 "unknown special key")); return 0; } } // special handling of VK_SNAPSHOT - if (virtualKey == VK_SNAPSHOT) { + if (extVirtualKey == VK_SNAPSHOT) { // ignore key repeats on print screen if (!isAutoRepeat) { // active window (with alt) or fullscreen (without alt)? @@ -1251,7 +1152,7 @@ CMSWindowsKeyState::mapKey(Keystrokes& keys, KeyID id, } // handle other special keys - if (virtualKey != 0) { + if (extVirtualKey != 0) { // compute required modifiers KeyModifierMask requiredMask = 0; KeyModifierMask outMask = 0; @@ -1262,6 +1163,7 @@ CMSWindowsKeyState::mapKey(Keystrokes& keys, KeyID id, // (e.g. add, multiply) and their main keyboard counterparts. // therefore, we can ignore the num-lock state for movement virtual // keys but not for numeric keys. + UINT virtualKey = (extVirtualKey & 0xffu); if (virtualKey >= VK_NUMPAD0 && virtualKey <= VK_DIVIDE) { requiredMask |= KeyModifierNumLock; if ((getActiveModifiers() & KeyModifierNumLock) != 0) { @@ -1277,9 +1179,12 @@ CMSWindowsKeyState::mapKey(Keystrokes& keys, KeyID id, } // now generate the keystrokes and return the resulting modifier mask - KeyButton scanCode = m_virtKeyToScanCode[virtualKey]; - LOG((CLOG_DEBUG2 "KeyID 0x%08x to virtual key %d scan code 0x%04x mask 0x%04x", id, virtualKey, scanCode, outMask)); - return mapToKeystrokes(keys, scanCode, + KeyButton button = m_virtKeyToScanCode[virtualKey]; + if ((extVirtualKey & 0x100u) != 0) { + button |= 0x100u; + } + LOG((CLOG_DEBUG2 "KeyID 0x%08x to virtual key %d scan code 0x%03x mask 0x%04x", id, virtualKey, button, outMask)); + return mapToKeystrokes(keys, button, outMask, requiredMask, isAutoRepeat); } @@ -1407,6 +1312,196 @@ CMSWindowsKeyState::getCodePageFromLangID(LANGID langid) const return codePage; } +KeyButton +CMSWindowsKeyState::mapVirtKeyToButton(UINT virtualKey, + KeyButton& extended) const +{ + // this method does what MapVirtualKey(virtualKey, 0) should do. + // we have to explicitly set the extended key flag for some + // modifiers because the win32 API is inadequate. we also find + // the unextended and the extended scancodes for those virtual + // keys that have both except for VK_SHIFT, VK_CONTROL, and VK_MENU. + // + // the windows 95 family doesn't map the side distinguishing virtual + // keys. but we know that VK_CONTROL maps to VK_LCONTROL and + // that VK_RCONTROL is the same scan code | 0x100. similarly for + // VK_MENU. but VK_RSHIFT cannot be determined that way so we + // search for it. + extended = 0; + KeyButton button; + if (m_is95Family) { + UINT scancode; + switch (virtualKey) { + case VK_LSHIFT: + button = (KeyButton)MapVirtualKey(VK_SHIFT, 0); + break; + + case VK_RSHIFT: + // we have to search + scancode = MapVirtualKey(VK_SHIFT, 0); + for (UINT i = 1; i < 256; ++i) { + if (i != scancode && MapVirtualKey(i, 1) == VK_SHIFT) { + return (KeyButton)(i); + } + } + return 0; + + case VK_LCONTROL: + case VK_RCONTROL: + button = (KeyButton)MapVirtualKey(VK_CONTROL, 0); + break; + + case VK_LMENU: + case VK_RMENU: + button = (KeyButton)MapVirtualKey(VK_MENU, 0); + break; + + case VK_PAUSE: + // mapped to 0. i hope this works on all keyboards. + button = (KeyButton)0x45u; + break; + + case VK_DIVIDE: + // mapped to 0. i hope this works on all keyboards. + button = (KeyButton)0x35u; + break; + + default: + button = (KeyButton)MapVirtualKey(virtualKey, 0); + break; + } + } + else { + switch (virtualKey) { + case VK_PAUSE: + // mapped to 0. i hope this works on all keyboards. + button = (KeyButton)0x45u; + break; + + default: + button = (KeyButton)MapVirtualKey(virtualKey, 0); + break; + } + } + + // map extended keys + switch (virtualKey) { + case VK_RETURN: // Return/numpad Enter + case VK_PRIOR: // numpad PageUp/PageUp + case VK_NEXT: // numpad PageDown/PageDown + case VK_END: // numpad End/End + case VK_HOME: // numpad Home/Home + case VK_LEFT: // numpad Left/Left + case VK_UP: // numpad Up/Up + case VK_RIGHT: // numpad Right/Right + case VK_DOWN: // numpad Down/Down + case VK_INSERT: // numpad Insert/Insert + case VK_DELETE: // numpad Delete/Delete +// case VK_SELECT: +// case VK_EXECUTE: +// case VK_HELP: + extended = (KeyButton)(button | 0x100u); + break; + } + + // see if the win32 API can help us determine an extended key. + // if the remapped virtual key doesn't match the starting + // point then there's a really good chance that that virtual + // key is mapped to an extended key. however, this is not + // the case for modifiers that don't distinguish between left + // and right. + UINT virtualKey2 = MapVirtualKey(button, 3); + if (virtualKey2 != 0 && virtualKey2 != virtualKey) { + switch (virtualKey) { + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + break; + + case VK_NUMPAD0: + case VK_NUMPAD1: + case VK_NUMPAD2: + case VK_NUMPAD3: + case VK_NUMPAD4: + case VK_NUMPAD5: + case VK_NUMPAD6: + case VK_NUMPAD7: + case VK_NUMPAD8: + case VK_NUMPAD9: + case VK_MULTIPLY: + case VK_ADD: + case VK_SEPARATOR: + case VK_SUBTRACT: + case VK_DECIMAL: + break; + + default: + button |= 0x100u; + extended = 0; + break; + } + return button; + } + + // note other extended keys that the win32 API won't help us with. + // on the windows 95 family this is the only way to find extended + // keys since MapVirtualKey(N, 3) is unimplemented. + switch (virtualKey) { + case VK_CANCEL: + case VK_LWIN: + case VK_RWIN: + case VK_APPS: +// case VK_SEPARATOR: + case VK_DIVIDE: + case VK_F13: + case VK_F14: + case VK_F15: + case VK_F16: + case VK_F17: + case VK_F18: + case VK_F19: + case VK_F20: + case VK_F21: + case VK_F22: + case VK_F23: + case VK_F24: + case VK_NUMLOCK: + case VK_RSHIFT: + case VK_RCONTROL: + case VK_RMENU: + case VK_BROWSER_BACK: + case VK_BROWSER_FORWARD: + case VK_BROWSER_REFRESH: + case VK_BROWSER_STOP: + case VK_BROWSER_SEARCH: + case VK_BROWSER_FAVORITES: + case VK_BROWSER_HOME: + case VK_VOLUME_MUTE: + case VK_VOLUME_DOWN: + case VK_VOLUME_UP: + case VK_MEDIA_NEXT_TRACK: + case VK_MEDIA_PREV_TRACK: + case VK_MEDIA_STOP: + case VK_MEDIA_PLAY_PAUSE: + case VK_LAUNCH_MAIL: + case VK_LAUNCH_MEDIA_SELECT: + case VK_LAUNCH_APP1: + case VK_LAUNCH_APP2: + button |= 0x100u; + extended = 0; + break; + } + + return button; +} + +KeyButton +CMSWindowsKeyState::mapVirtKeyToButton(UINT virtualKey) const +{ + KeyButton dummy; + return mapVirtKeyToButton(virtualKey, dummy); +} + KeyButton CMSWindowsKeyState::mapCharacter(Keystrokes& keys, char c, HKL hkl, bool isAutoRepeat) const diff --git a/lib/platform/CMSWindowsKeyState.h b/lib/platform/CMSWindowsKeyState.h index cc888103..d4c7e1e0 100644 --- a/lib/platform/CMSWindowsKeyState.h +++ b/lib/platform/CMSWindowsKeyState.h @@ -64,7 +64,6 @@ public: //@} // IKeyState overrides - virtual void setKeyDown(KeyButton button, bool down); virtual void sendKeyEvent(void* target, bool press, bool isAutoRepeat, KeyID key, KeyModifierMask mask, @@ -88,6 +87,14 @@ private: // convert a language ID to a code page UINT getCodePageFromLangID(LANGID langid) const; + // map a virtual key to a button. this tries to deal with the + // broken win32 API as best it can. + KeyButton mapVirtKeyToButton(UINT virtualKey, + KeyButton& extended) const; + + // same as above and discard extended + KeyButton mapVirtKeyToButton(UINT virtualKey) const; + // map character \c c given keyboard layout \c hkl to the keystrokes // to generate it. KeyButton mapCharacter(Keystrokes& keys, diff --git a/lib/platform/CMSWindowsScreen.cpp b/lib/platform/CMSWindowsScreen.cpp index e6213f6b..8f627928 100644 --- a/lib/platform/CMSWindowsScreen.cpp +++ b/lib/platform/CMSWindowsScreen.cpp @@ -83,6 +83,7 @@ CMSWindowsScreen::CMSWindowsScreen(bool isPrimary, m_mark(0), m_markReceived(0), m_keyLayout(NULL), + m_fixTimer(NULL), m_screensaver(NULL), m_screensaverNotify(false), m_nextClipboardWindow(NULL), @@ -218,6 +219,13 @@ CMSWindowsScreen::disable() CArchMiscWindows::kDISPLAY); } + // uninstall fix key timer + if (m_fixTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_fixTimer); + EVENTQUEUE->deleteTimer(m_fixTimer); + m_fixTimer = NULL; + } + // stop snooping the clipboard ChangeClipboardChain(m_window, m_nextClipboardWindow); m_nextClipboardWindow = NULL; @@ -680,6 +688,10 @@ CMSWindowsScreen::onPreDispatch(HWND hwnd, switch (message) { case SYNERGY_MSG_SCREEN_SAVER: return onScreensaver(wParam != 0); + + case SYNERGY_MSG_DEBUG: + LOG((CLOG_INFO "hook: 0x%08x 0x%08x", wParam, lParam)); + return true; } if (m_isPrimary) { @@ -693,27 +705,6 @@ bool CMSWindowsScreen::onPreDispatchPrimary(HWND, UINT message, WPARAM wParam, LPARAM lParam) { - // fake a key release for the windows keys if we think they're - // down but they're really up. we have to do this because if the - // user presses and releases a windows key without pressing any - // other key while it's down then the system will eat the key - // release. if we don't detect that and synthesize the release - // then the client won't take the usual windows key release action - // (which on windows is to show the start menu). - // - // since the key could go up at any time we'll check the state on - // every event. only check on the windows 95 family since the NT - // family reports the key release as usual. obviously we skip - // this if the event is for a windows key itself. - if (m_is95Family && message != SYNERGY_MSG_KEY) { - if (wParam != VK_LWIN) { - m_keyState->fixKey(getEventTarget(), VK_LWIN); - } - if (wParam != VK_RWIN) { - m_keyState->fixKey(getEventTarget(), VK_RWIN); - } - } - // handle event switch (message) { case SYNERGY_MSG_MARK: @@ -840,22 +831,67 @@ CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam) { LOG((CLOG_DEBUG1 "event: Key char=%d, vk=0x%02x, lParam=0x%08x", (wParam & 0xff00u) >> 8, wParam & 0xffu, lParam)); - // update key state. ignore key repeats. + // fix up key state + fixKeys(); + + // get key info KeyButton button = (KeyButton)((lParam & 0x01ff0000) >> 16); - if ((lParam & 0xc0000000u) == 0x00000000) { + bool down = ((lParam & 0xc0000000u) == 0x00000000u); + bool up = ((lParam & 0x80000000u) == 0x80000000u); + bool wasDown = isKeyDown(button); + + // the windows keys are a royal pain on the windows 95 family. + // the system eats the key up events if and only if the windows + // key wasn't combined with another key, i.e. it was tapped. + // fixKeys() and scheduleFixKeys() are all about synthesizing + // the missing key up. but even windows itself gets a little + // confused and sets bit 30 in lParam if you tap the windows + // key twice. that bit means the key was previously down and + // that makes some sense since the up event was missing. + // anyway, on the windows 95 family we forget about windows + // key repeats and treat anything that's not a key down as a + // key up. + if (m_is95Family && + ((wParam & 0xffu) == VK_LWIN || (wParam & 0xffu) == VK_RWIN)) { + down = !up; + } + + // update key state. ignore key repeats. + if (down) { m_keyState->setKeyDown(button, true); } - else if ((lParam & 0x80000000u) == 0x80000000) { + else if (up) { m_keyState->setKeyDown(button, false); } + // schedule a timer if we need to fix keys later + scheduleFixKeys(); + + // special case: we detect ctrl+alt+del being pressed on some + // systems but we don't detect the release of those keys. so + // if ctrl, alt, and del are down then mark them up. + KeyModifierMask mask = getActiveModifiers(); + bool ctrlAlt = ((mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)); + if (down && ctrlAlt && + isKeyDown(m_keyState->virtualKeyToButton(VK_DELETE))) { + m_keyState->setKeyDown( + m_keyState->virtualKeyToButton(VK_LCONTROL), false); + m_keyState->setKeyDown( + m_keyState->virtualKeyToButton(VK_RCONTROL), false); + m_keyState->setKeyDown( + m_keyState->virtualKeyToButton(VK_LMENU), false); + m_keyState->setKeyDown( + m_keyState->virtualKeyToButton(VK_RMENU), false); + m_keyState->setKeyDown( + m_keyState->virtualKeyToButton(VK_DELETE), false); + } + // ignore message if posted prior to last mark change if (!ignore()) { // check for ctrl+alt+del emulation UINT virtKey = (wParam & 0xffu); - if ((virtKey == VK_PAUSE || virtKey == VK_CANCEL) && - (m_keyState->getActiveModifiers() & - (KeyModifierControl | KeyModifierAlt)) != 0) { + if ((virtKey == VK_PAUSE || virtKey == VK_CANCEL) && ctrlAlt) { LOG((CLOG_DEBUG "emulate ctrl+alt+del")); // switch wParam and lParam to be as if VK_DELETE was // pressed or released @@ -870,9 +906,41 @@ CMSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam) KeyID key = m_keyState->mapKeyFromEvent(wParam, lParam, &mask); button = static_cast((lParam & 0x01ff0000u) >> 16); if (key != kKeyNone) { + // fix up key. if the key isn't down according to + // our table then we never got the key press event + // for it. if it's not a modifier key then we'll + // synthesize the press first. only do this on + // the windows 95 family, which eats certain special + // keys like alt+tab, ctrl+esc, etc. + if (m_is95Family && !wasDown && up) { + switch (virtKey) { + case VK_LSHIFT: + case VK_RSHIFT: + case VK_SHIFT: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_CONTROL: + case VK_LMENU: + case VK_RMENU: + case VK_MENU: + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + case VK_LWIN: + case VK_RWIN: + break; + + default: + m_keyState->sendKeyEvent(getEventTarget(), + true, false, key, mask, 1, button); + break; + } + } + + // do it m_keyState->sendKeyEvent(getEventTarget(), - ((lParam & 0x80000000) == 0), - ((lParam & 0x40000000) == 1), + ((lParam & 0x80000000u) == 0), + ((lParam & 0x40000000u) == 1), key, mask, (SInt32)(lParam & 0xffff), button); } else { @@ -1225,6 +1293,58 @@ CMSWindowsScreen::mapButtonFromEvent(WPARAM msg, LPARAM button) const } } +void +CMSWindowsScreen::fixKeys() +{ + // fake key releases for the windows keys if we think they're + // down but they're really up. we have to do this because if the + // user presses and releases a windows key without pressing any + // other key while it's down then the system will eat the key + // release. if we don't detect that and synthesize the release + // then the client won't take the usual windows key release action + // (which on windows is to show the start menu). + // + // only check on the windows 95 family since the NT family reports + // the key releases as usual. + if (m_is95Family) { + m_keyState->fixKey(getEventTarget(), VK_LWIN); + m_keyState->fixKey(getEventTarget(), VK_RWIN); + + // check if we need the fix timer anymore + scheduleFixKeys(); + } +} + +void +CMSWindowsScreen::scheduleFixKeys() +{ + if (m_is95Family) { + // see if any keys that need fixing are down + bool fix = + (m_keyState->isKeyDown(m_keyState->virtualKeyToButton(VK_LWIN)) || + m_keyState->isKeyDown(m_keyState->virtualKeyToButton(VK_RWIN))); + + // start or stop fix timer + if (fix && m_fixTimer == NULL) { + m_fixTimer = EVENTQUEUE->newTimer(0.1, NULL); + EVENTQUEUE->adoptHandler(CEvent::kTimer, m_fixTimer, + new TMethodEventJob( + this, &CMSWindowsScreen::handleFixKeys)); + } + else if (!fix && m_fixTimer != NULL) { + EVENTQUEUE->removeHandler(CEvent::kTimer, m_fixTimer); + EVENTQUEUE->deleteTimer(m_fixTimer); + m_fixTimer = NULL; + } + } +} + +void +CMSWindowsScreen::handleFixKeys(const CEvent&, void*) +{ + fixKeys(); +} + void CMSWindowsScreen::updateKeysCB(void*) { diff --git a/lib/platform/CMSWindowsScreen.h b/lib/platform/CMSWindowsScreen.h index 8a43c833..e9e51a06 100644 --- a/lib/platform/CMSWindowsScreen.h +++ b/lib/platform/CMSWindowsScreen.h @@ -23,6 +23,7 @@ #define WIN32_LEAN_AND_MEAN #include +class CEventQueueTimer; class CMSWindowsDesks; class CMSWindowsKeyState; class CMSWindowsScreenSaver; @@ -156,6 +157,16 @@ private: // map a button event to a button ID ButtonID mapButtonFromEvent(WPARAM msg, LPARAM button) const; + // fix the key state, synthesizing fake key releases for keys + // that aren't down anymore. + void fixKeys(); + + // (un)schedule a later call to fixKeys + void scheduleFixKeys(); + + // event handler to fix the key state + void handleFixKeys(const CEvent&, void*); + // job to update the key state void updateKeysCB(void*); @@ -200,6 +211,9 @@ private: // the keyboard layout to use when off primary screen HKL m_keyLayout; + // the timer used to check for fixing key state + CEventQueueTimer* m_fixTimer; + // screen saver stuff CMSWindowsScreenSaver* m_screensaver; bool m_screensaverNotify; diff --git a/lib/platform/CSynergyHook.cpp b/lib/platform/CSynergyHook.cpp index 955dc2ae..0fb0b79c 100644 --- a/lib/platform/CSynergyHook.cpp +++ b/lib/platform/CSynergyHook.cpp @@ -94,6 +94,7 @@ static SInt32 g_wScreen = 0; static SInt32 g_hScreen = 0; static WPARAM g_deadVirtKey = 0; static LPARAM g_deadLParam = 0; +static WPARAM g_oldDeadVirtKey = 0; static BYTE g_deadKeyState[256] = { 0 }; static DWORD g_hookThread = 0; static DWORD g_attachedThread = 0; @@ -185,21 +186,44 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam) // 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. + // pressed. note that some systems set bit 31 to indicate a + // dead key and others bit 15. nice. UINT c = MapVirtualKey(wParam, 2); - if ((c & 0x80000000u) != 0) { + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | 0x00000000, c); + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | (c << 8) | 0x01000000, lParam); + if ((c & 0x80008000u) != 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); + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | 0x02000000, lParam); return false; } // second dead key press in a row so let it pass + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | 0x03000000, lParam); + } + else if (wParam == g_oldDeadVirtKey) { + // dead key release for second dead key in a row. discard + // because we've already handled it. also take it out of + // the keyboard buffer. + g_oldDeadVirtKey = 0; + WORD c; + UINT scanCode = ((lParam & 0x00ff0000u) >> 16); + ToAscii(wParam, scanCode, g_deadKeyState, &c, 0); + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | 0x09000000, lParam); + return true; } else { // dead key release + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | 0x04000000, lParam); return false; } } @@ -256,6 +280,8 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam) // 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) { + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | 0x05000000, lParam); if (g_deadVirtKey != 0) { ToAscii(g_deadVirtKey, (g_deadLParam & 0x00ff0000u) >> 16, g_deadKeyState, &c, flags); @@ -269,6 +295,9 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam) n = ToAscii(wParam, scanCode, keys, &c, flags); } + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | (c << 8) | ((n & 0xff) << 16) | 0x06000000, + lParam); switch (n) { default: // key is a dead key; we're not expecting this since we @@ -307,6 +336,9 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam) if (g_deadVirtKey != 0) { ToAscii(g_deadVirtKey, (g_deadLParam & 0x00ff0000u) >> 16, g_deadKeyState, &c, flags); + for (int i = 0; i < 256; ++i) { + g_deadKeyState[i] = 0; + } } // clear out old dead key state @@ -318,12 +350,17 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam) // 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_DEBUG, + charAndVirtKey | 0x07000000, 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) { + if ((c & 0x80008000u) != 0) { + g_oldDeadVirtKey = wParam; + PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, + wParam | 0x08000000, lParam); PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, charAndVirtKey, lParam | 0x80000000u); } @@ -342,6 +379,14 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam) case VK_SHIFT: case VK_LSHIFT: case VK_RSHIFT: + // pass the shift modifiers. if we don't do this + // we may not get the right dead key when caps lock + // is on. for example, on the french layout (with + // english keycaps) on caps lock then press shift + [ + // and q. instead of an A with ^ above it you get an + // A with dots above it. + break; + case VK_CONTROL: case VK_LCONTROL: case VK_RCONTROL: @@ -349,8 +394,8 @@ keyboardHookHandler(WPARAM wParam, LPARAM lParam) case VK_LMENU: case VK_RMENU: case VK_HANGUL: - // always pass the shift modifiers - break; + // discard the control and alt modifiers + return true; default: // discard @@ -787,12 +832,14 @@ install() g_hinstance, 0); } +#if !NO_GRAB_KEYBOARD if (g_keyboardLL == NULL) { g_keyboard = SetWindowsHookEx(WH_KEYBOARD, &keyboardHook, g_hinstance, 0); } +#endif // check that we got all the hooks we wanted if ((g_getMessage == NULL && g_wheelSupport == kWheelOld) || diff --git a/lib/platform/CSynergyHook.h b/lib/platform/CSynergyHook.h index a9ab2b0c..045d96e7 100644 --- a/lib/platform/CSynergyHook.h +++ b/lib/platform/CSynergyHook.h @@ -38,9 +38,10 @@ #define SYNERGY_MSG_POST_WARP WM_APP + 0x0016 // ; #define SYNERGY_MSG_PRE_WARP WM_APP + 0x0017 // x; y #define SYNERGY_MSG_SCREEN_SAVER WM_APP + 0x0018 // activated; +#define SYNERGY_MSG_DEBUG WM_APP + 0x0019 // data, data #define SYNERGY_MSG_INPUT_FIRST SYNERGY_MSG_KEY #define SYNERGY_MSG_INPUT_LAST SYNERGY_MSG_PRE_WARP -#define SYNERGY_HOOK_LAST_MSG SYNERGY_MSG_SCREEN_SAVER +#define SYNERGY_HOOK_LAST_MSG SYNERGY_MSG_DEBUG extern "C" { diff --git a/lib/synergy/CKeyState.h b/lib/synergy/CKeyState.h index 0ed89f2d..00c28eb4 100644 --- a/lib/synergy/CKeyState.h +++ b/lib/synergy/CKeyState.h @@ -33,10 +33,9 @@ public: //! Mark key as being down /*! - Sets the state of \p button to down or up. If this is overridden - it must forward to the superclass. + Sets the state of \p button to down or up. */ - virtual void setKeyDown(KeyButton button, bool down); + void setKeyDown(KeyButton button, bool down); //! Mark modifier as being toggled on /*!