/* Copyright 2011-2021 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include // Missing in at least some versions of MinGW #ifndef MAPVK_VK_TO_CHAR #define MAPVK_VK_TO_CHAR 2 #endif #include #define XK_MISCELLANY #define XK_XKB_KEYS #define XK_KOREAN #include #include #include #include #define NoSymbol 0 #include "i18n.h" #include "keysym2ucs.h" #include "KeyboardWin32.h" #define ARRAY_SIZE(a) (sizeof(a)/sizeof(*a)) // Used to detect fake input (0xaa is not a real key) static const WORD SCAN_FAKE = 0xaa; static rfb::LogWriter vlog("KeyboardWin32"); // Layout independent keys static const UINT vkey_map[][3] = { { VK_CANCEL, NoSymbol, XK_Break }, { VK_BACK, XK_BackSpace, NoSymbol }, { VK_TAB, XK_Tab, NoSymbol }, { VK_CLEAR, XK_Clear, NoSymbol }, { VK_RETURN, XK_Return, XK_KP_Enter }, { VK_SHIFT, XK_Shift_L, NoSymbol }, { VK_CONTROL, XK_Control_L, XK_Control_R }, { VK_MENU, XK_Alt_L, XK_Alt_R }, { VK_PAUSE, XK_Pause, NoSymbol }, { VK_CAPITAL, XK_Caps_Lock, NoSymbol }, { VK_ESCAPE, XK_Escape, NoSymbol }, { VK_CONVERT, XK_Henkan, NoSymbol }, { VK_NONCONVERT, XK_Muhenkan, NoSymbol }, { VK_PRIOR, XK_KP_Prior, XK_Prior }, { VK_NEXT, XK_KP_Next, XK_Next }, { VK_END, XK_KP_End, XK_End }, { VK_HOME, XK_KP_Home, XK_Home }, { VK_LEFT, XK_KP_Left, XK_Left }, { VK_UP, XK_KP_Up, XK_Up }, { VK_RIGHT, XK_KP_Right, XK_Right }, { VK_DOWN, XK_KP_Down, XK_Down }, { VK_SNAPSHOT, XK_Sys_Req, XK_Print }, { VK_INSERT, XK_KP_Insert, XK_Insert }, { VK_DELETE, XK_KP_Delete, XK_Delete }, { VK_LWIN, NoSymbol, XK_Super_L }, { VK_RWIN, NoSymbol, XK_Super_R }, { VK_APPS, NoSymbol, XK_Menu }, { VK_SLEEP, NoSymbol, XF86XK_Sleep }, { VK_NUMPAD0, XK_KP_0, NoSymbol }, { VK_NUMPAD1, XK_KP_1, NoSymbol }, { VK_NUMPAD2, XK_KP_2, NoSymbol }, { VK_NUMPAD3, XK_KP_3, NoSymbol }, { VK_NUMPAD4, XK_KP_4, NoSymbol }, { VK_NUMPAD5, XK_KP_5, NoSymbol }, { VK_NUMPAD6, XK_KP_6, NoSymbol }, { VK_NUMPAD7, XK_KP_7, NoSymbol }, { VK_NUMPAD8, XK_KP_8, NoSymbol }, { VK_NUMPAD9, XK_KP_9, NoSymbol }, { VK_MULTIPLY, XK_KP_Multiply, NoSymbol }, { VK_ADD, XK_KP_Add, NoSymbol }, { VK_SUBTRACT, XK_KP_Subtract, NoSymbol }, { VK_DIVIDE, NoSymbol, XK_KP_Divide }, /* VK_SEPARATOR and VK_DECIMAL left out on purpose. See further down. */ { VK_F1, XK_F1, NoSymbol }, { VK_F2, XK_F2, NoSymbol }, { VK_F3, XK_F3, NoSymbol }, { VK_F4, XK_F4, NoSymbol }, { VK_F5, XK_F5, NoSymbol }, { VK_F6, XK_F6, NoSymbol }, { VK_F7, XK_F7, NoSymbol }, { VK_F8, XK_F8, NoSymbol }, { VK_F9, XK_F9, NoSymbol }, { VK_F10, XK_F10, NoSymbol }, { VK_F11, XK_F11, NoSymbol }, { VK_F12, XK_F12, NoSymbol }, { VK_F13, XK_F13, NoSymbol }, { VK_F14, XK_F14, NoSymbol }, { VK_F15, XK_F15, NoSymbol }, { VK_F16, XK_F16, NoSymbol }, { VK_F17, XK_F17, NoSymbol }, { VK_F18, XK_F18, NoSymbol }, { VK_F19, XK_F19, NoSymbol }, { VK_F20, XK_F20, NoSymbol }, { VK_F21, XK_F21, NoSymbol }, { VK_F22, XK_F22, NoSymbol }, { VK_F23, XK_F23, NoSymbol }, { VK_F24, XK_F24, NoSymbol }, { VK_NUMLOCK, NoSymbol, XK_Num_Lock }, { VK_SCROLL, XK_Scroll_Lock, NoSymbol }, { VK_BROWSER_BACK, NoSymbol, XF86XK_Back }, { VK_BROWSER_FORWARD, NoSymbol, XF86XK_Forward }, { VK_BROWSER_REFRESH, NoSymbol, XF86XK_Refresh }, { VK_BROWSER_STOP, NoSymbol, XF86XK_Stop }, { VK_BROWSER_SEARCH, NoSymbol, XF86XK_Search }, { VK_BROWSER_FAVORITES, NoSymbol, XF86XK_Favorites }, { VK_BROWSER_HOME, NoSymbol, XF86XK_HomePage }, { VK_VOLUME_MUTE, NoSymbol, XF86XK_AudioMute }, { VK_VOLUME_DOWN, NoSymbol, XF86XK_AudioLowerVolume }, { VK_VOLUME_UP, NoSymbol, XF86XK_AudioRaiseVolume }, { VK_MEDIA_NEXT_TRACK, NoSymbol, XF86XK_AudioNext }, { VK_MEDIA_PREV_TRACK, NoSymbol, XF86XK_AudioPrev }, { VK_MEDIA_STOP, NoSymbol, XF86XK_AudioStop }, { VK_MEDIA_PLAY_PAUSE, NoSymbol, XF86XK_AudioPlay }, { VK_LAUNCH_MAIL, NoSymbol, XF86XK_Mail }, { VK_LAUNCH_APP2, NoSymbol, XF86XK_Calculator }, }; // Layout dependent keys, but without useful symbols // Japanese static const UINT vkey_map_jp[][3] = { { VK_KANA, XK_Hiragana_Katakana, NoSymbol }, { VK_KANJI, XK_Kanji, NoSymbol }, { VK_OEM_ATTN, XK_Eisu_toggle, NoSymbol }, { VK_OEM_FINISH, XK_Katakana, NoSymbol }, { VK_OEM_COPY, XK_Hiragana, NoSymbol }, // These are really XK_Zenkaku/XK_Hankaku but we have no way of // keeping the client and server in sync { VK_OEM_AUTO, XK_Zenkaku_Hankaku, NoSymbol }, { VK_OEM_ENLW, XK_Zenkaku_Hankaku, NoSymbol }, { VK_OEM_BACKTAB, XK_Romaji, NoSymbol }, { VK_ATTN, XK_Romaji, NoSymbol }, }; // Korean static const UINT vkey_map_ko[][3] = { { VK_HANGUL, XK_Hangul, NoSymbol }, { VK_HANJA, XK_Hangul_Hanja, NoSymbol }, }; KeyboardWin32::KeyboardWin32(KeyboardHandler* handler_) : Keyboard(handler_), cachedHasAltGr(false), currentLayout(nullptr), altGrArmed(false), leftShiftDown(false), rightShiftDown(false) { } KeyboardWin32::~KeyboardWin32() { reset(); } bool KeyboardWin32::handleEvent(const void* event) { MSG *msg = (MSG*)event; assert(event); if ((msg->message == WM_MOUSEMOVE) || (msg->message == WM_LBUTTONDOWN) || (msg->message == WM_LBUTTONUP) || (msg->message == WM_RBUTTONDOWN) || (msg->message == WM_RBUTTONUP) || (msg->message == WM_MBUTTONDOWN) || (msg->message == WM_MBUTTONUP) || (msg->message == WM_XBUTTONDOWN) || (msg->message == WM_XBUTTONUP) || (msg->message == WM_MOUSEWHEEL) || (msg->message == WM_MOUSEHWHEEL)) { // We can't get a mouse event in the middle of an AltGr sequence, so // abort that detection if (altGrArmed) resolveAltGrDetection(false); return false; // We didn't really consume the mouse event } else if ((msg->message == WM_KEYDOWN) || (msg->message == WM_SYSKEYDOWN)) { UINT vKey; bool isExtended; int systemKeyCode, keyCode; uint32_t keySym; vKey = msg->wParam; isExtended = (msg->lParam & (1 << 24)) != 0; systemKeyCode = ((msg->lParam >> 16) & 0xff); // Windows' touch keyboard doesn't set a scan code for the Alt // portion of the AltGr sequence, so we need to help it out if (!isExtended && (systemKeyCode == 0x00) && (vKey == VK_MENU)) { isExtended = true; systemKeyCode = 0x38; } // Windows doesn't have a proper AltGr, but handles it using fake // Ctrl+Alt. However the remote end might not be Windows, so we need // to merge those in to a single AltGr event. We detect this case // by seeing the two key events directly after each other with a very // short time between them (<50ms) and supress the Ctrl event. if (altGrArmed) { bool altPressed = isExtended && (systemKeyCode == 0x38) && (vKey == VK_MENU) && ((msg->time - altGrCtrlTime) < 50); resolveAltGrDetection(altPressed); } if (systemKeyCode == SCAN_FAKE) { vlog.debug("Ignoring fake key press (virtual key 0x%02x)", vKey); return true; } // Windows sets the scan code to 0x00 for multimedia keys, so we // have to do a reverse lookup based on the vKey. if (systemKeyCode == 0x00) { systemKeyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC); if (systemKeyCode == 0x00) { if (isExtended) vlog.error(_("No scan code for extended virtual key 0x%02x"), (int)vKey); else vlog.error(_("No scan code for virtual key 0x%02x"), (int)vKey); return true; } } if (systemKeyCode & ~0x7f) { vlog.error(_("Invalid scan code 0x%02x"), (int)systemKeyCode); return true; } if (isExtended) systemKeyCode |= 0x80; keyCode = translateSystemKeyCode(systemKeyCode); keySym = translateVKey(vKey, isExtended); if (keySym == NoSymbol) { if (isExtended) vlog.error(_("No symbol for extended virtual key 0x%02x"), (int)vKey); else vlog.error(_("No symbol for virtual key 0x%02x"), (int)vKey); } // Windows sends the same vKey for both shifts, so we need to look // at the scan code to tell them apart if ((keySym == XK_Shift_L) && (systemKeyCode == 0x36)) keySym = XK_Shift_R; // AltGr handling (see above) if (hasAltGr()) { if ((systemKeyCode == 0xb8) && (keySym == XK_Alt_R)) keySym = XK_ISO_Level3_Shift; // Possible start of AltGr sequence? if ((systemKeyCode == 0x1d) && (keySym == XK_Control_L)) { altGrArmed = true; altGrCtrlTime = msg->time; Fl::add_timeout(0.1, handleAltGrTimeout, this); return true; } } handler->handleKeyPress(systemKeyCode, keyCode, keySym); // We don't get reliable WM_KEYUP for these switch (keySym) { case XK_Zenkaku_Hankaku: case XK_Eisu_toggle: case XK_Katakana: case XK_Hiragana: case XK_Romaji: handler->handleKeyRelease(systemKeyCode); } // Shift key tracking, see below if (systemKeyCode == 0x2a) leftShiftDown = true; if (systemKeyCode == 0x36) rightShiftDown = true; return true; } else if ((msg->message == WM_KEYUP) || (msg->message == WM_SYSKEYUP)) { UINT vKey; bool isExtended; int systemKeyCode, keyCode; vKey = msg->wParam; isExtended = (msg->lParam & (1 << 24)) != 0; systemKeyCode = ((msg->lParam >> 16) & 0xff); // Touch keyboard AltGr (see above) if (!isExtended && (systemKeyCode == 0x00) && (vKey == VK_MENU)) { isExtended = true; systemKeyCode = 0x38; } // We can't get a release in the middle of an AltGr sequence, so // abort that detection if (altGrArmed) resolveAltGrDetection(false); if (systemKeyCode == SCAN_FAKE) { vlog.debug("Ignoring fake key release (virtual key 0x%02x)", vKey); return 1; } if (systemKeyCode == 0x00) systemKeyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC); if (isExtended) systemKeyCode |= 0x80; keyCode = translateSystemKeyCode(systemKeyCode); handler->handleKeyRelease(keyCode); // Windows has a rather nasty bug where it won't send key release // events for a Shift button if the other Shift is still pressed if ((systemKeyCode == 0x2a) || (systemKeyCode == 0x36)) { if (leftShiftDown) handler->handleKeyRelease(0x2a); if (rightShiftDown) handler->handleKeyRelease(0x36); leftShiftDown = false; rightShiftDown = false; } return true; } return false; } void KeyboardWin32::reset() { altGrArmed = false; Fl::remove_timeout(handleAltGrTimeout, this); leftShiftDown = false; rightShiftDown = false; } unsigned KeyboardWin32::getLEDState() { unsigned state; state = 0; if (GetKeyState(VK_CAPITAL) & 0x1) state |= rfb::ledCapsLock; if (GetKeyState(VK_NUMLOCK) & 0x1) state |= rfb::ledNumLock; if (GetKeyState(VK_SCROLL) & 0x1) state |= rfb::ledScrollLock; return state; } void KeyboardWin32::setLEDState(unsigned state) { INPUT input[6]; UINT count; UINT ret; memset(input, 0, sizeof(input)); count = 0; if (!!(state & rfb::ledCapsLock) != !!(GetKeyState(VK_CAPITAL) & 0x1)) { input[count].type = input[count+1].type = INPUT_KEYBOARD; input[count].ki.wVk = input[count+1].ki.wVk = VK_CAPITAL; input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE; input[count].ki.dwFlags = 0; input[count+1].ki.dwFlags = KEYEVENTF_KEYUP; count += 2; } if (!!(state & rfb::ledNumLock) != !!(GetKeyState(VK_NUMLOCK) & 0x1)) { input[count].type = input[count+1].type = INPUT_KEYBOARD; input[count].ki.wVk = input[count+1].ki.wVk = VK_NUMLOCK; input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE; input[count].ki.dwFlags = KEYEVENTF_EXTENDEDKEY; input[count+1].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_EXTENDEDKEY; count += 2; } if (!!(state & rfb::ledScrollLock) != !!(GetKeyState(VK_SCROLL) & 0x1)) { input[count].type = input[count+1].type = INPUT_KEYBOARD; input[count].ki.wVk = input[count+1].ki.wVk = VK_SCROLL; input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE; input[count].ki.dwFlags = 0; input[count+1].ki.dwFlags = KEYEVENTF_KEYUP; count += 2; } if (count == 0) return; ret = SendInput(count, input, sizeof(*input)); if (ret < count) vlog.error(_("Failed to update keyboard LED state: %lu"), GetLastError()); } uint32_t KeyboardWin32::translateSystemKeyCode(int systemKeyCode) { // Fortunately RFB and Windows use the same scan code set (mostly), // so there is no conversion needed // (as long as we encode the extended keys with the high bit) // However Pause sends a code that conflicts with NumLock, so use // the code most RFB implementations use (part of the sequence for // Ctrl+Pause, i.e. Break) if (systemKeyCode == 0x45) return 0xc6; // And NumLock incorrectly has the extended bit set if (systemKeyCode == 0xc5) return 0x45; // And Alt+PrintScreen (i.e. SysRq) sends a different code than // PrintScreen if (systemKeyCode == 0xb7) return 0x54; return systemKeyCode; } uint32_t KeyboardWin32::lookupVKeyMap(unsigned vkey, bool extended, const UINT map[][3], size_t size) { size_t i; for (i = 0;i < size;i++) { if (vkey != map[i][0]) continue; if (extended) return map[i][2]; else return map[i][1]; } return NoSymbol; } uint32_t KeyboardWin32::translateVKey(unsigned vkey, bool extended) { HKL layout; WORD lang, primary_lang; BYTE state[256]; int ret; WCHAR wstr[10]; // Start with keys that either don't generate a symbol, or // generate the same symbol as some other key. ret = lookupVKeyMap(vkey, extended, vkey_map, ARRAY_SIZE(vkey_map)); if (ret != NoSymbol) return ret; layout = GetKeyboardLayout(0); lang = LOWORD(layout); primary_lang = PRIMARYLANGID(lang); if (primary_lang == LANG_JAPANESE) { ret = lookupVKeyMap(vkey, extended, vkey_map_jp, ARRAY_SIZE(vkey_map_jp)); if (ret != NoSymbol) return ret; } if (primary_lang == LANG_KOREAN) { ret = lookupVKeyMap(vkey, extended, vkey_map_ko, ARRAY_SIZE(vkey_map_ko)); if (ret != NoSymbol) return ret; } // Windows is not consistent in which virtual key it uses for // the numpad decimal key, and this is not likely to be fixed: // http://blogs.msdn.com/michkap/archive/2006/09/13/752377.aspx // // To get X11 behaviour, we instead look at the text generated // by they key. if ((vkey == VK_DECIMAL) || (vkey == VK_SEPARATOR)) { UINT ch; ch = MapVirtualKey(vkey, MAPVK_VK_TO_CHAR); switch (ch) { case ',': return XK_KP_Separator; case '.': return XK_KP_Decimal; default: return NoSymbol; } } // MapVirtualKey() doesn't look at modifiers, so it is // insufficient for mapping most keys to a symbol. ToUnicode() // does what we want though. Unfortunately it keeps state, so // we have to be careful around dead characters. GetKeyboardState(state); // Pressing Ctrl wreaks havoc with the symbol lookup, so turn // that off. But AltGr shows up as Ctrl+Alt in Windows, so keep // Ctrl if Alt is active. if (!(state[VK_LCONTROL] & 0x80) || !(state[VK_RMENU] & 0x80)) state[VK_CONTROL] = state[VK_LCONTROL] = state[VK_RCONTROL] = 0; // FIXME: Multi character results, like U+0644 U+0627 // on Arabic layout ret = ToUnicode(vkey, 0, state, wstr, sizeof(wstr)/sizeof(wstr[0]), 0); if (ret == 0) { // Most Ctrl+Alt combinations will fail to produce a symbol, so // try it again with Ctrl unconditionally disabled. state[VK_CONTROL] = state[VK_LCONTROL] = state[VK_RCONTROL] = 0; ret = ToUnicode(vkey, 0, state, wstr, sizeof(wstr)/sizeof(wstr[0]), 0); } if (ret == 1) return ucs2keysym(wstr[0]); if (ret == -1) { WCHAR dead_char; dead_char = wstr[0]; // Need to clear out the state that the dead key has caused. // This is the recommended method by Microsoft's engineers: // http://blogs.msdn.com/b/michkap/archive/2007/10/27/5717859.aspx do { ret = ToUnicode(vkey, 0, state, wstr, sizeof(wstr)/sizeof(wstr[0]), 0); } while (ret < 0); // Dead keys are represented by their spacing equivalent // (or something similar depending on the layout) return ucs2keysym(ucs2combining(dead_char)); } return NoSymbol; } bool KeyboardWin32::hasAltGr() { BYTE origState[256]; BYTE altGrState[256]; if (currentLayout == GetKeyboardLayout(0)) return cachedHasAltGr; // Save current keyboard state so we can get things sane again after // we're done if (!GetKeyboardState(origState)) return 0; // We press Ctrl+Alt (Windows fake AltGr) and then test every key // to see if it produces a printable character. If so then we assume // AltGr is used in the current layout. cachedHasAltGr = false; memset(altGrState, 0, sizeof(altGrState)); altGrState[VK_CONTROL] = 0x80; altGrState[VK_MENU] = 0x80; for (UINT vkey = 0;vkey <= 0xff;vkey++) { int ret; WCHAR wstr[10]; // Need to skip this one as it is a bit magical and will trigger // a false positive if (vkey == VK_PACKET) continue; ret = ToUnicode(vkey, 0, altGrState, wstr, sizeof(wstr)/sizeof(wstr[0]), 0); if (ret == 1) { cachedHasAltGr = true; break; } if (ret == -1) { // Dead key, need to clear out state before we proceed do { ret = ToUnicode(vkey, 0, altGrState, wstr, sizeof(wstr)/sizeof(wstr[0]), 0); } while (ret < 0); } } SetKeyboardState(origState); currentLayout = GetKeyboardLayout(0); return cachedHasAltGr; } void KeyboardWin32::handleAltGrTimeout(void *data) { KeyboardWin32 *self = (KeyboardWin32 *)data; assert(self); self->altGrArmed = false; self->handler->handleKeyPress(0x1d, 0x1d, XK_Control_L); } void KeyboardWin32::resolveAltGrDetection(bool isAltGrSequence) { altGrArmed = false; Fl::remove_timeout(handleAltGrTimeout); // when it's not an AltGr sequence we can't supress the Ctrl anymore if (!isAltGrSequence) handler->handleKeyPress(0x1d, 0x1d, XK_Control_L); }