From 16f98788710fbac557069f39dc760219cdbd21c7 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 16 Oct 2024 10:51:23 +0200 Subject: Track keys using system key code We might not always be able to map to an RFB key code, so let's use the platform specific code to track which keys are pressed. --- vncviewer/Viewport.cxx | 136 +++++++++++++++++++++++-------------------------- vncviewer/Viewport.h | 13 +++-- 2 files changed, 74 insertions(+), 75 deletions(-) diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index e29c877c..e7b4ac9f 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -115,6 +115,14 @@ enum { ID_DISCONNECT, ID_FULLSCREEN, ID_MINIMIZE, ID_RESIZE, static const WORD SCAN_FAKE = 0xaa; #endif +// Used for fake key presses from the menu +static const int FAKE_CTRL_KEY_CODE = 0x10001; +static const int FAKE_ALT_KEY_CODE = 0x10002; +static const int FAKE_DEL_KEY_CODE = 0x10003; + +// Used for fake key presses for lock key sync +static const int FAKE_KEY_CODE = 0xffff; + Viewport::Viewport(int w, int h, const rfb::PixelFormat& /*serverPF*/, CConn* cc_) : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(nullptr), lastPointerPos(0, 0), lastButtonMask(0), @@ -499,18 +507,18 @@ void Viewport::pushLEDState() if ((ledState & ledCapsLock) != (cc->server.ledState() & ledCapsLock)) { vlog.debug("Inserting fake CapsLock to get in sync with server"); - handleKeyPress(0x3a, XK_Caps_Lock); - handleKeyRelease(0x3a); + handleKeyPress(FAKE_KEY_CODE, 0x3a, XK_Caps_Lock); + handleKeyRelease(FAKE_KEY_CODE); } if ((ledState & ledNumLock) != (cc->server.ledState() & ledNumLock)) { vlog.debug("Inserting fake NumLock to get in sync with server"); - handleKeyPress(0x45, XK_Num_Lock); - handleKeyRelease(0x45); + handleKeyPress(FAKE_KEY_CODE, 0x45, XK_Num_Lock); + handleKeyRelease(FAKE_KEY_CODE); } if ((ledState & ledScrollLock) != (cc->server.ledState() & ledScrollLock)) { vlog.debug("Inserting fake ScrollLock to get in sync with server"); - handleKeyPress(0x46, XK_Scroll_Lock); - handleKeyRelease(0x46); + handleKeyPress(FAKE_KEY_CODE, 0x46, XK_Scroll_Lock); + handleKeyRelease(FAKE_KEY_CODE); } } @@ -637,9 +645,9 @@ int Viewport::handle(int event) // Resend Ctrl/Alt if needed if (menuCtrlKey) - handleKeyPress(0x1d, XK_Control_L); + handleKeyPress(FAKE_CTRL_KEY_CODE, 0x1d, XK_Control_L); if (menuAltKey) - handleKeyPress(0x38, XK_Alt_L); + handleKeyPress(FAKE_ALT_KEY_CODE, 0x38, XK_Alt_L); // Yes, we would like some focus please! return 1; @@ -822,12 +830,13 @@ void Viewport::handlePointerTimeout(void *data) void Viewport::resetKeyboard() { - while (!downKeySym.empty()) - handleKeyRelease(downKeySym.begin()->first); + while (!downKeys.empty()) + handleKeyRelease(downKeys.begin()->first); } -void Viewport::handleKeyPress(int keyCode, uint32_t keySym) +void Viewport::handleKeyPress(int systemKeyCode, + uint32_t keyCode, uint32_t keySym) { static bool menuRecursion = false; @@ -843,11 +852,6 @@ void Viewport::handleKeyPress(int keyCode, uint32_t keySym) if (viewOnly) return; - if (keyCode == 0) { - vlog.error(_("No key code specified on key press")); - return; - } - #ifdef __APPLE__ // Alt on OS X behaves more like AltGr on other systems, and to get // sane behaviour we should translate things in that manner for the @@ -874,17 +878,14 @@ void Viewport::handleKeyPress(int keyCode, uint32_t keySym) // symbol on release as when pressed. This breaks the VNC protocol however, // so we need to keep track of what keysym a key _code_ generated on press // and send the same on release. - downKeySym[keyCode] = keySym; + downKeys[systemKeyCode].keyCode = keyCode; + downKeys[systemKeyCode].keySym = keySym; - vlog.debug("Key pressed: 0x%04x => XK_%s (0x%04x)", - keyCode, KeySymName(keySym), keySym); + vlog.debug("Key pressed: %d => 0x%02x / XK_%s (0x%04x)", + systemKeyCode, keyCode, KeySymName(keySym), keySym); try { - // Fake keycode? - if (keyCode > 0xff) - cc->writer()->writeKeyEvent(keySym, 0, true); - else - cc->writer()->writeKeyEvent(keySym, keyCode, true); + cc->writer()->writeKeyEvent(keySym, keyCode, true); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); abort_connection_with_unexpected_error(e); @@ -892,35 +893,34 @@ void Viewport::handleKeyPress(int keyCode, uint32_t keySym) } -void Viewport::handleKeyRelease(int keyCode) +void Viewport::handleKeyRelease(int systemKeyCode) { DownMap::iterator iter; if (viewOnly) return; - iter = downKeySym.find(keyCode); - if (iter == downKeySym.end()) { + iter = downKeys.find(systemKeyCode); + if (iter == downKeys.end()) { // These occur somewhat frequently so let's not spam them unless // logging is turned up. - vlog.debug("Unexpected release of key code %d", keyCode); + vlog.debug("Unexpected release of key code %d", systemKeyCode); return; } - vlog.debug("Key released: 0x%04x => XK_%s (0x%04x)", - keyCode, KeySymName(iter->second), iter->second); + vlog.debug("Key released: %d => 0x%02x / XK_%s (0x%04x)", + systemKeyCode, iter->second.keyCode, + KeySymName(iter->second.keySym), iter->second.keySym); try { - if (keyCode > 0xff) - cc->writer()->writeKeyEvent(iter->second, 0, false); - else - cc->writer()->writeKeyEvent(iter->second, keyCode, false); + cc->writer()->writeKeyEvent(iter->second.keySym, + iter->second.keyCode, false); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); abort_connection_with_unexpected_error(e); } - downKeySym.erase(iter); + downKeys.erase(iter); } @@ -1057,7 +1057,7 @@ int Viewport::handleSystemEvent(void *event, void *data) } } - self->handleKeyPress(keyCode, keySym); + self->handleKeyPress(keyCode, keyCode, keySym); // We don't get reliable WM_KEYUP for these switch (keySym) { @@ -1112,9 +1112,9 @@ int Viewport::handleSystemEvent(void *event, void *data) // 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 ((keyCode == 0x2a) || (keyCode == 0x36)) { - if (self->downKeySym.count(0x2a)) + if (self->downKeys.count(0x2a)) self->handleKeyRelease(0x2a); - if (self->downKeySym.count(0x36)) + if (self->downKeys.count(0x36)) self->handleKeyRelease(0x36); } @@ -1128,31 +1128,33 @@ int Viewport::handleSystemEvent(void *event, void *data) } if (cocoa_is_keyboard_event(event)) { - int keyCode; + int systemKeyCode; - keyCode = cocoa_event_keycode(event); - if ((unsigned)keyCode >= code_map_osx_to_qnum_len) - keyCode = 0; - else - keyCode = code_map_osx_to_qnum[keyCode]; + systemKeyCode = cocoa_event_keycode(event); if (cocoa_is_key_press(event)) { + uint32_t keyCode; uint32_t keySym; + if ((unsigned)systemKeyCode >= code_map_osx_to_qnum_len) + keyCode = 0; + else + keyCode = code_map_osx_to_qnum[systemKeyCode]; + keySym = cocoa_event_keysym(event); if (keySym == NoSymbol) { vlog.error(_("No symbol for key code 0x%02x (in the current state)"), (int)keyCode); } - self->handleKeyPress(keyCode, keySym); + self->handleKeyPress(systemKeyCode, keyCode, keySym); // We don't get any release events for CapsLock, so we have to // send the release right away. if (keySym == XK_Caps_Lock) - self->handleKeyRelease(keyCode); + self->handleKeyRelease(systemKeyCode); } else { - self->handleKeyRelease(keyCode); + self->handleKeyRelease(systemKeyCode); } return 1; @@ -1167,11 +1169,6 @@ int Viewport::handleSystemEvent(void *event, void *data) keycode = code_map_keycode_to_qnum[xevent->xkey.keycode]; - // Generate a fake keycode just for tracking if we can't figure - // out the proper one - if (keycode == 0) - keycode = 0x100 | xevent->xkey.keycode; - XLookupString(&xevent->xkey, &str, 1, &keysym, nullptr); if (keysym == NoSymbol) { vlog.error(_("No symbol for key code %d (in the current state)"), @@ -1195,13 +1192,10 @@ int Viewport::handleSystemEvent(void *event, void *data) break; } - self->handleKeyPress(keycode, keysym); + self->handleKeyPress(xevent->xkey.keycode, keycode, keysym); return 1; } else if (xevent->type == KeyRelease) { - int keycode = code_map_keycode_to_qnum[xevent->xkey.keycode]; - if (keycode == 0) - keycode = 0x100 | xevent->xkey.keycode; - self->handleKeyRelease(keycode); + self->handleKeyRelease(xevent->xkey.keycode); return 1; } #endif @@ -1217,7 +1211,7 @@ void Viewport::handleAltGrTimeout(void *data) assert(self); self->altGrArmed = false; - self->handleKeyPress(0x1d, XK_Control_L); + self->handleKeyPress(0x1d, 0x1d, XK_Control_L); } void Viewport::resolveAltGrDetection(bool isAltGrSequence) @@ -1226,7 +1220,7 @@ void Viewport::resolveAltGrDetection(bool isAltGrSequence) Fl::remove_timeout(handleAltGrTimeout); // when it's not an AltGr sequence we can't supress the Ctrl anymore if (!isAltGrSequence) - handleKeyPress(0x1d, XK_Control_L); + handleKeyPress(0x1d, 0x1d, XK_Control_L); } #endif @@ -1338,30 +1332,30 @@ void Viewport::popupContextMenu() break; case ID_CTRL: if (m->value()) - handleKeyPress(0x1d, XK_Control_L); + handleKeyPress(FAKE_CTRL_KEY_CODE, 0x1d, XK_Control_L); else - handleKeyRelease(0x1d); + handleKeyRelease(FAKE_CTRL_KEY_CODE); menuCtrlKey = !menuCtrlKey; break; case ID_ALT: if (m->value()) - handleKeyPress(0x38, XK_Alt_L); + handleKeyPress(FAKE_ALT_KEY_CODE, 0x38, XK_Alt_L); else - handleKeyRelease(0x38); + handleKeyRelease(FAKE_ALT_KEY_CODE); menuAltKey = !menuAltKey; break; case ID_MENUKEY: - handleKeyPress(menuKeyCode, menuKeySym); - handleKeyRelease(menuKeyCode); + handleKeyPress(FAKE_KEY_CODE, menuKeyCode, menuKeySym); + handleKeyRelease(FAKE_KEY_CODE); break; case ID_CTRLALTDEL: - handleKeyPress(0x1d, XK_Control_L); - handleKeyPress(0x38, XK_Alt_L); - handleKeyPress(0xd3, XK_Delete); + handleKeyPress(FAKE_CTRL_KEY_CODE, 0x1d, XK_Control_L); + handleKeyPress(FAKE_ALT_KEY_CODE, 0x38, XK_Alt_L); + handleKeyPress(FAKE_DEL_KEY_CODE, 0xd3, XK_Delete); - handleKeyRelease(0xd3); - handleKeyRelease(0x38); - handleKeyRelease(0x1d); + handleKeyRelease(FAKE_DEL_KEY_CODE); + handleKeyRelease(FAKE_ALT_KEY_CODE); + handleKeyRelease(FAKE_CTRL_KEY_CODE); break; case ID_REFRESH: cc->refreshFramebuffer(); diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index 5f4c1ca7..16fa1da8 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -86,8 +86,9 @@ private: void resetKeyboard(); - void handleKeyPress(int keyCode, uint32_t keySym); - void handleKeyRelease(int keyCode); + void handleKeyPress(int systemKeyCode, + uint32_t keyCode, uint32_t keySym); + void handleKeyRelease(int systemKeyCode); static int handleSystemEvent(void *event, void *data); @@ -113,8 +114,12 @@ private: rfb::Point lastPointerPos; uint8_t lastButtonMask; - typedef std::map DownMap; - DownMap downKeySym; + struct DownKey { + uint32_t keyCode; + uint32_t keySym; + }; + typedef std::map DownMap; + DownMap downKeys; #ifdef WIN32 bool altGrArmed; -- cgit v1.2.3 From 245c5421323966a87e8d6d70e391bef0d85b2e65 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 14 May 2021 12:18:46 +0200 Subject: Move keyboard tracking to CConnection This is a general thing so move it in to the core library instead, letting vncviewer focus on just translation of system events to VNC ones. --- common/rfb/CConnection.cxx | 87 ++++++++++++++++++++++++++++++++++++++++ common/rfb/CConnection.h | 20 ++++++++++ vncviewer/Viewport.cxx | 99 +++++++++++----------------------------------- vncviewer/Viewport.h | 12 ++---- 4 files changed, 134 insertions(+), 84 deletions(-) diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx index b4017dba..eb9972f1 100644 --- a/common/rfb/CConnection.cxx +++ b/common/rfb/CConnection.cxx @@ -40,6 +40,10 @@ #include #include +#define XK_MISCELLANY +#define XK_XKB_KEYS +#include + #include #include @@ -693,6 +697,89 @@ void CConnection::sendClipboardData(const char* data) } } +void CConnection::sendKeyPress(int systemKeyCode, + uint32_t keyCode, uint32_t keySym) +{ + // For the first few years, there wasn't a good consensus on what the + // Windows keys should be mapped to for X11. So we need to help out a + // bit and map all variants to the same key... + switch (keySym) { + case XK_Hyper_L: + keySym = XK_Super_L; + break; + case XK_Hyper_R: + keySym = XK_Super_R; + break; + // There has been several variants for Shift-Tab over the years. + // RFB states that we should always send a normal tab. + case XK_ISO_Left_Tab: + keySym = XK_Tab; + break; + } + +#ifdef __APPLE__ + // Alt on OS X behaves more like AltGr on other systems, and to get + // sane behaviour we should translate things in that manner for the + // remote VNC server. However that means we lose the ability to use + // Alt as a shortcut modifier. Do what RealVNC does and hijack the + // left command key as an Alt replacement. + switch (keySym) { + case XK_Super_L: + keySym = XK_Alt_L; + break; + case XK_Super_R: + keySym = XK_Super_L; + break; + case XK_Alt_L: + keySym = XK_Mode_switch; + break; + case XK_Alt_R: + keySym = XK_ISO_Level3_Shift; + break; + } +#endif + + // Because of the way keyboards work, we cannot expect to have the same + // symbol on release as when pressed. This breaks the VNC protocol however, + // so we need to keep track of what keysym a key _code_ generated on press + // and send the same on release. + downKeys[systemKeyCode].keyCode = keyCode; + downKeys[systemKeyCode].keySym = keySym; + + vlog.debug("Key pressed: %d => 0x%02x / XK_%s (0x%04x)", + systemKeyCode, keyCode, KeySymName(keySym), keySym); + + writer()->writeKeyEvent(keySym, keyCode, true); +} + +void CConnection::sendKeyRelease(int systemKeyCode) +{ + DownMap::iterator iter; + + iter = downKeys.find(systemKeyCode); + if (iter == downKeys.end()) { + // These occur somewhat frequently so let's not spam them unless + // logging is turned up. + vlog.debug("Unexpected release of key code %d", systemKeyCode); + return; + } + + vlog.debug("Key released: %d => 0x%02x / XK_%s (0x%04x)", + systemKeyCode, iter->second.keyCode, + KeySymName(iter->second.keySym), iter->second.keySym); + + writer()->writeKeyEvent(iter->second.keySym, + iter->second.keyCode, false); + + downKeys.erase(iter); +} + +void CConnection::releaseAllKeys() +{ + while (!downKeys.empty()) + sendKeyRelease(downKeys.begin()->first); +} + void CConnection::refreshFramebuffer() { forceNonincremental = true; diff --git a/common/rfb/CConnection.h b/common/rfb/CConnection.h index 3f277d71..af6051d1 100644 --- a/common/rfb/CConnection.h +++ b/common/rfb/CConnection.h @@ -24,6 +24,7 @@ #ifndef __RFB_CCONNECTION_H__ #define __RFB_CCONNECTION_H__ +#include #include #include @@ -196,6 +197,18 @@ namespace rfb { // clipboard via handleClipboardRequest(). virtual void sendClipboardData(const char* data); + // sendKeyPress()/sendKeyRelease() send keyboard events to the + // server + void sendKeyPress(int systemKeyCode, uint32_t keyCode, uint32_t keySym); + void sendKeyRelease(int systemKeyCode); + + // releaseAllKeys() sends keyboard release events to the server for + // all keys that are currently pressed down by this client, + // avoiding keys getting stuck. This can be useful if the client + // loses keyboard focus or otherwise no longer gets keyboard events + // from the system. + void releaseAllKeys(); + // refreshFramebuffer() forces a complete refresh of the entire // framebuffer void refreshFramebuffer(); @@ -313,6 +326,13 @@ namespace rfb { bool hasRemoteClipboard; bool hasLocalClipboard; bool unsolicitedClipboardAttempt; + + struct DownKey { + uint32_t keyCode; + uint32_t keySym; + }; + typedef std::map DownMap; + DownMap downKeys; }; } #endif diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index e7b4ac9f..f5bc166d 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -28,7 +28,6 @@ #include #include #include -#include #include #include @@ -127,7 +126,7 @@ Viewport::Viewport(int w, int h, const rfb::PixelFormat& /*serverPF*/, CConn* cc : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(nullptr), lastPointerPos(0, 0), lastButtonMask(0), #ifdef WIN32 - altGrArmed(false), + altGrArmed(false), leftShiftDown(false), rightShiftDown(false), #endif firstLEDState(true), pendingClientClipboard(false), menuCtrlKey(false), menuAltKey(false), cursor(nullptr) @@ -568,7 +567,6 @@ int Viewport::handle(int event) { std::string filtered; int buttonMask, wheelMask; - DownMap::const_iterator iter; switch (event) { case FL_PASTE: @@ -830,8 +828,17 @@ void Viewport::handlePointerTimeout(void *data) void Viewport::resetKeyboard() { - while (!downKeys.empty()) - handleKeyRelease(downKeys.begin()->first); + try { + cc->releaseAllKeys(); + } catch (rdr::Exception& e) { + vlog.error("%s", e.str()); + abort_connection_with_unexpected_error(e); + } + +#ifdef WIN32 + leftShiftDown = false; + rightShiftDown = false; +#endif } @@ -852,40 +859,8 @@ void Viewport::handleKeyPress(int systemKeyCode, if (viewOnly) return; -#ifdef __APPLE__ - // Alt on OS X behaves more like AltGr on other systems, and to get - // sane behaviour we should translate things in that manner for the - // remote VNC server. However that means we lose the ability to use - // Alt as a shortcut modifier. Do what RealVNC does and hijack the - // left command key as an Alt replacement. - switch (keySym) { - case XK_Super_L: - keySym = XK_Alt_L; - break; - case XK_Super_R: - keySym = XK_Super_L; - break; - case XK_Alt_L: - keySym = XK_Mode_switch; - break; - case XK_Alt_R: - keySym = XK_ISO_Level3_Shift; - break; - } -#endif - - // Because of the way keyboards work, we cannot expect to have the same - // symbol on release as when pressed. This breaks the VNC protocol however, - // so we need to keep track of what keysym a key _code_ generated on press - // and send the same on release. - downKeys[systemKeyCode].keyCode = keyCode; - downKeys[systemKeyCode].keySym = keySym; - - vlog.debug("Key pressed: %d => 0x%02x / XK_%s (0x%04x)", - systemKeyCode, keyCode, KeySymName(keySym), keySym); - try { - cc->writer()->writeKeyEvent(keySym, keyCode, true); + cc->sendKeyPress(systemKeyCode, keyCode, keySym); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); abort_connection_with_unexpected_error(e); @@ -895,32 +870,15 @@ void Viewport::handleKeyPress(int systemKeyCode, void Viewport::handleKeyRelease(int systemKeyCode) { - DownMap::iterator iter; - if (viewOnly) return; - iter = downKeys.find(systemKeyCode); - if (iter == downKeys.end()) { - // These occur somewhat frequently so let's not spam them unless - // logging is turned up. - vlog.debug("Unexpected release of key code %d", systemKeyCode); - return; - } - - vlog.debug("Key released: %d => 0x%02x / XK_%s (0x%04x)", - systemKeyCode, iter->second.keyCode, - KeySymName(iter->second.keySym), iter->second.keySym); - try { - cc->writer()->writeKeyEvent(iter->second.keySym, - iter->second.keyCode, false); + cc->sendKeyRelease(systemKeyCode); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); abort_connection_with_unexpected_error(e); } - - downKeys.erase(iter); } @@ -1069,6 +1027,12 @@ int Viewport::handleSystemEvent(void *event, void *data) self->handleKeyRelease(keyCode); } + // Shift key tracking, see below + if (keyCode == 0x2a) + self->leftShiftDown = true; + if (keyCode == 0x36) + self->rightShiftDown = true; + return 1; } else if ((msg->message == WM_KEYUP) || (msg->message == WM_SYSKEYUP)) { UINT vKey; @@ -1112,10 +1076,12 @@ int Viewport::handleSystemEvent(void *event, void *data) // 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 ((keyCode == 0x2a) || (keyCode == 0x36)) { - if (self->downKeys.count(0x2a)) + if (self->leftShiftDown) self->handleKeyRelease(0x2a); - if (self->downKeys.count(0x36)) + if (self->rightShiftDown) self->handleKeyRelease(0x36); + self->leftShiftDown = false; + self->rightShiftDown = false; } return 1; @@ -1175,23 +1141,6 @@ int Viewport::handleSystemEvent(void *event, void *data) (int)xevent->xkey.keycode); } - switch (keysym) { - // For the first few years, there wasn't a good consensus on what the - // Windows keys should be mapped to for X11. So we need to help out a - // bit and map all variants to the same key... - case XK_Hyper_L: - keysym = XK_Super_L; - break; - case XK_Hyper_R: - keysym = XK_Super_R; - break; - // There has been several variants for Shift-Tab over the years. - // RFB states that we should always send a normal tab. - case XK_ISO_Left_Tab: - keysym = XK_Tab; - break; - } - self->handleKeyPress(xevent->xkey.keycode, keycode, keysym); return 1; } else if (xevent->type == KeyRelease) { diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index 16fa1da8..72df22f4 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -20,8 +20,6 @@ #ifndef __VIEWPORT_H__ #define __VIEWPORT_H__ -#include - #include #include @@ -114,16 +112,12 @@ private: rfb::Point lastPointerPos; uint8_t lastButtonMask; - struct DownKey { - uint32_t keyCode; - uint32_t keySym; - }; - typedef std::map DownMap; - DownMap downKeys; - #ifdef WIN32 bool altGrArmed; unsigned int altGrCtrlTime; + + bool leftShiftDown; + bool rightShiftDown; #endif bool firstLEDState; -- cgit v1.2.3 From 7e7e05075bccece85efa92f14f516c065a8fa531 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 14 Dec 2021 12:26:56 +0100 Subject: Move keyboard handling to separate classes Encapsulate all the platform specific magic around keyboard in to specific classes, in order to keep the core code more readable. --- vncviewer/CMakeLists.txt | 13 +- vncviewer/Keyboard.h | 49 ++++ vncviewer/KeyboardMacOS.h | 60 +++++ vncviewer/KeyboardMacOS.mm | 504 ++++++++++++++++++++++++++++++++++ vncviewer/KeyboardWin32.cxx | 639 ++++++++++++++++++++++++++++++++++++++++++++ vncviewer/KeyboardWin32.h | 58 ++++ vncviewer/KeyboardX11.cxx | 219 +++++++++++++++ vncviewer/KeyboardX11.h | 42 +++ vncviewer/Viewport.cxx | 570 ++------------------------------------- vncviewer/Viewport.h | 26 +- vncviewer/cocoa.h | 14 - vncviewer/cocoa.mm | 401 --------------------------- vncviewer/win32.c | 306 --------------------- vncviewer/win32.h | 4 - 14 files changed, 1611 insertions(+), 1294 deletions(-) create mode 100644 vncviewer/Keyboard.h create mode 100644 vncviewer/KeyboardMacOS.h create mode 100644 vncviewer/KeyboardMacOS.mm create mode 100644 vncviewer/KeyboardWin32.cxx create mode 100644 vncviewer/KeyboardWin32.h create mode 100644 vncviewer/KeyboardX11.cxx create mode 100644 vncviewer/KeyboardX11.h diff --git a/vncviewer/CMakeLists.txt b/vncviewer/CMakeLists.txt index 143c8fa0..72904b25 100644 --- a/vncviewer/CMakeLists.txt +++ b/vncviewer/CMakeLists.txt @@ -18,7 +18,6 @@ add_executable(vncviewer PlatformPixelBuffer.cxx Viewport.cxx parameters.cxx - keysym2ucs.c touch.cxx MonitorIndicesParameter.cxx vncviewer.cxx) @@ -37,17 +36,15 @@ endif() if(WIN32) target_sources(vncviewer PRIVATE Win32TouchHandler.cxx win32.c) -elseif(APPLE) - target_sources(vncviewer PRIVATE cocoa.mm osx_to_qnum.c) -else() - target_sources(vncviewer PRIVATE GestureHandler.cxx XInputTouchHandler.cxx xkb_to_qnum.c) -endif() - -if(WIN32) + target_sources(vncviewer PRIVATE KeyboardWin32.cxx keysym2ucs.c) target_sources(vncviewer PRIVATE Surface_Win32.cxx) elseif(APPLE) + target_sources(vncviewer PRIVATE cocoa.mm) + target_sources(vncviewer PRIVATE KeyboardMacOS.mm osx_to_qnum.c keysym2ucs.c) target_sources(vncviewer PRIVATE Surface_OSX.cxx) else() + target_sources(vncviewer PRIVATE GestureHandler.cxx XInputTouchHandler.cxx) + target_sources(vncviewer PRIVATE KeyboardX11.cxx xkb_to_qnum.c) target_sources(vncviewer PRIVATE Surface_X11.cxx) endif() diff --git a/vncviewer/Keyboard.h b/vncviewer/Keyboard.h new file mode 100644 index 00000000..81360252 --- /dev/null +++ b/vncviewer/Keyboard.h @@ -0,0 +1,49 @@ +/* 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. + */ + +#ifndef __KEYBOARD_H__ +#define __KEYBOARD_H__ + +#include + +class KeyboardHandler +{ +public: + virtual void handleKeyPress(int systemKeyCode, + uint32_t keyCode, uint32_t keySym) = 0; + virtual void handleKeyRelease(int systemKeyCode) = 0; +}; + +class Keyboard +{ +public: + Keyboard(KeyboardHandler* handler_) : handler(handler_) {}; + virtual ~Keyboard() {}; + + virtual bool handleEvent(const void* event) = 0; + + virtual void reset() {}; + + virtual unsigned getLEDState() = 0; + virtual void setLEDState(unsigned state) = 0; + +protected: + KeyboardHandler* handler; +}; + +#endif diff --git a/vncviewer/KeyboardMacOS.h b/vncviewer/KeyboardMacOS.h new file mode 100644 index 00000000..0901664b --- /dev/null +++ b/vncviewer/KeyboardMacOS.h @@ -0,0 +1,60 @@ +/* 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. + */ + +#ifndef __KEYBOARDMACOS_H__ +#define __KEYBOARDMACOS_H__ + +#include "Keyboard.h" + +#ifdef __OBJC__ +@class NSEvent; +@class NSString; +#else +class NSEvent; +class NSString; +#endif + +class KeyboardMacOS : public Keyboard +{ +public: + KeyboardMacOS(KeyboardHandler* handler); + virtual ~KeyboardMacOS(); + + bool handleEvent(const void* event) override; + + unsigned getLEDState() override; + void setLEDState(unsigned state) override; + + // Special helper on macOS + static bool isKeyboardSync(const void* event); + +protected: + bool isKeyboardEvent(const NSEvent* nsevent); + bool isKeyPress(const NSEvent* nsevent); + uint32_t translateSystemKeyCode(int systemKeyCode); + unsigned getSystemKeyCode(const NSEvent* nsevent); + + NSString* keyTranslate(unsigned keyCode, unsigned modifierFlags); + uint32_t translateEventKeysym(const NSEvent* nsevent); + + int openHID(unsigned int* ioc); + int getModifierLockState(int modifier, bool* on); + int setModifierLockState(int modifier, bool on); +}; + +#endif diff --git a/vncviewer/KeyboardMacOS.mm b/vncviewer/KeyboardMacOS.mm new file mode 100644 index 00000000..e0a10dc8 --- /dev/null +++ b/vncviewer/KeyboardMacOS.mm @@ -0,0 +1,504 @@ +/* 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 + +#import +#import + +#include +#include + +// This wasn't added until 10.12 +#if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12 +const int kVK_RightCommand = 0x36; +#endif +// And this is still missing +const int kVK_Menu = 0x6E; + +#define XK_LATIN1 +#define XK_MISCELLANY +#include +#include +#include +#include + +#define NoSymbol 0 + +#include "i18n.h" +#include "keysym2ucs.h" +#include "KeyboardMacOS.h" + +extern const unsigned short code_map_osx_to_qnum[]; +extern const unsigned int code_map_osx_to_qnum_len; + +static rfb::LogWriter vlog("KeyboardMacOS"); + +static const int kvk_map[][2] = { + { kVK_Return, XK_Return }, + { kVK_Tab, XK_Tab }, + { kVK_Space, XK_space }, + { kVK_Delete, XK_BackSpace }, + { kVK_Escape, XK_Escape }, + { kVK_RightCommand, XK_Super_R }, + { kVK_Command, XK_Super_L }, + { kVK_Shift, XK_Shift_L }, + { kVK_CapsLock, XK_Caps_Lock }, + { kVK_Option, XK_Alt_L }, + { kVK_Control, XK_Control_L }, + { kVK_RightShift, XK_Shift_R }, + { kVK_RightOption, XK_Alt_R }, + { kVK_RightControl, XK_Control_R }, + { kVK_F17, XK_F17 }, + { kVK_VolumeUp, XF86XK_AudioRaiseVolume }, + { kVK_VolumeDown, XF86XK_AudioLowerVolume }, + { kVK_Mute, XF86XK_AudioMute }, + { kVK_F18, XK_F18 }, + { kVK_F19, XK_F19 }, + { kVK_F20, XK_F20 }, + { kVK_F5, XK_F5 }, + { kVK_F6, XK_F6 }, + { kVK_F7, XK_F7 }, + { kVK_F3, XK_F3 }, + { kVK_F8, XK_F8 }, + { kVK_F9, XK_F9 }, + { kVK_F11, XK_F11 }, + { kVK_F13, XK_F13 }, + { kVK_F16, XK_F16 }, + { kVK_F14, XK_F14 }, + { kVK_F10, XK_F10 }, + { kVK_Menu, XK_Menu }, + { kVK_F12, XK_F12 }, + { kVK_F15, XK_F15 }, + // Should we send Insert here? + { kVK_Help, XK_Help }, + { kVK_Home, XK_Home }, + { kVK_PageUp, XK_Page_Up }, + { kVK_ForwardDelete, XK_Delete }, + { kVK_F4, XK_F4 }, + { kVK_End, XK_End }, + { kVK_F2, XK_F2 }, + { kVK_PageDown, XK_Page_Down }, + { kVK_F1, XK_F1 }, + { kVK_LeftArrow, XK_Left }, + { kVK_RightArrow, XK_Right }, + { kVK_DownArrow, XK_Down }, + { kVK_UpArrow, XK_Up }, + + // The OS X headers claim these keys are not layout independent. + // Could it be because of the state of the decimal key? + /* { kVK_ANSI_KeypadDecimal, XK_KP_Decimal }, */ // see below + { kVK_ANSI_KeypadMultiply, XK_KP_Multiply }, + { kVK_ANSI_KeypadPlus, XK_KP_Add }, + // OS X doesn't have NumLock, so is this really correct? + { kVK_ANSI_KeypadClear, XK_Num_Lock }, + { kVK_ANSI_KeypadDivide, XK_KP_Divide }, + { kVK_ANSI_KeypadEnter, XK_KP_Enter }, + { kVK_ANSI_KeypadMinus, XK_KP_Subtract }, + { kVK_ANSI_KeypadEquals, XK_KP_Equal }, + { kVK_ANSI_Keypad0, XK_KP_0 }, + { kVK_ANSI_Keypad1, XK_KP_1 }, + { kVK_ANSI_Keypad2, XK_KP_2 }, + { kVK_ANSI_Keypad3, XK_KP_3 }, + { kVK_ANSI_Keypad4, XK_KP_4 }, + { kVK_ANSI_Keypad5, XK_KP_5 }, + { kVK_ANSI_Keypad6, XK_KP_6 }, + { kVK_ANSI_Keypad7, XK_KP_7 }, + { kVK_ANSI_Keypad8, XK_KP_8 }, + { kVK_ANSI_Keypad9, XK_KP_9 }, + // Japanese Keyboard Support + { kVK_JIS_Eisu, XK_Eisu_toggle }, + { kVK_JIS_Kana, XK_Hiragana_Katakana }, +}; + +KeyboardMacOS::KeyboardMacOS(KeyboardHandler* handler_) + : Keyboard(handler_) +{ +} + +KeyboardMacOS::~KeyboardMacOS() +{ +} + +bool KeyboardMacOS::handleEvent(const void* event) +{ + const NSEvent* nsevent = (NSEvent*)event; + unsigned systemKeyCode; + + assert(event); + + if (!isKeyboardEvent(nsevent)) + return false; + + systemKeyCode = getSystemKeyCode(nsevent); + + if (isKeyPress(nsevent)) { + uint32_t keyCode; + uint32_t keySym; + + keyCode = translateSystemKeyCode(systemKeyCode); + + keySym = translateEventKeysym(nsevent); + if (keySym == NoSymbol) { + vlog.error(_("No symbol for key code 0x%02x (in the current state)"), + systemKeyCode); + } + + handler->handleKeyPress(systemKeyCode, keyCode, keySym); + + // We don't get any release events for CapsLock, so we have to + // send the release right away. + if (keySym == XK_Caps_Lock) + handler->handleKeyRelease(systemKeyCode); + } else { + handler->handleKeyRelease(systemKeyCode); + } + + return true; +} + +unsigned KeyboardMacOS::getLEDState() +{ + unsigned state; + int ret; + bool on; + + state = 0; + + ret = getModifierLockState(kIOHIDCapsLockState, &on); + if (ret != 0) { + vlog.error(_("Failed to get keyboard LED state: %d"), ret); + return rfb::ledUnknown; + } + if (on) + state |= rfb::ledCapsLock; + + ret = getModifierLockState(kIOHIDNumLockState, &on); + if (ret != 0) { + vlog.error(_("Failed to get keyboard LED state: %d"), ret); + return rfb::ledUnknown; + } + if (on) + state |= rfb::ledNumLock; + + // No support for Scroll Lock // + + return state; +} + +void KeyboardMacOS::setLEDState(unsigned state) +{ + int ret; + + ret = setModifierLockState(kIOHIDCapsLockState, state & rfb::ledCapsLock); + if (ret != 0) { + vlog.error(_("Failed to update keyboard LED state: %d"), ret); + return; + } + + ret = setModifierLockState(kIOHIDNumLockState, state & rfb::ledNumLock); + if (ret != 0) { + vlog.error(_("Failed to update keyboard LED state: %d"), ret); + return; + } + + // No support for Scroll Lock // +} + +bool KeyboardMacOS::isKeyboardSync(const void* event) +{ + const NSEvent* nsevent = (const NSEvent*)event; + + assert(event); + + // If we get a NSFlagsChanged event with key code 0 then this isn't + // an actual keyboard event but rather the system trying to sync up + // modifier state after it has stolen input for some reason (e.g. + // Cmd+Tab) + + if ([nsevent type] != NSFlagsChanged) + return false; + if ([nsevent keyCode] != 0) + return false; + + return true; +} + +bool KeyboardMacOS::isKeyboardEvent(const NSEvent* nsevent) +{ + switch ([nsevent type]) { + case NSKeyDown: + case NSKeyUp: + return true; + case NSFlagsChanged: + if (isKeyboardSync(nsevent)) + return false; + return true; + default: + return false; + } +} + +bool KeyboardMacOS::isKeyPress(const NSEvent* nsevent) +{ + if ([nsevent type] == NSKeyDown) + return true; + + if ([nsevent type] == NSFlagsChanged) { + UInt32 mask; + + // We don't see any event on release of CapsLock + if ([nsevent keyCode] == kVK_CapsLock) + return true; + + // These are entirely undocumented, but I cannot find any other way + // of differentiating between left and right keys + switch ([nsevent keyCode]) { + case kVK_RightCommand: + mask = 0x0010; + break; + case kVK_Command: + mask = 0x0008; + break; + case kVK_Shift: + mask = 0x0002; + break; + case kVK_CapsLock: + // We don't see any event on release of CapsLock + return 1; + case kVK_Option: + mask = 0x0020; + break; + case kVK_Control: + mask = 0x0001; + break; + case kVK_RightShift: + mask = 0x0004; + break; + case kVK_RightOption: + mask = 0x0040; + break; + case kVK_RightControl: + mask = 0x2000; + break; + default: + return false; + } + + if ([nsevent modifierFlags] & mask) + return true; + else + return false; + } + + return false; +} + +unsigned KeyboardMacOS::getSystemKeyCode(const NSEvent* nsevent) +{ + unsigned keycode; + + keycode = [nsevent keyCode]; + + // macOS swaps these two keys for unknown reasons for ISO layouts + if (KBGetLayoutType(LMGetKbdType()) == kKeyboardISO) { + if (keycode == kVK_ANSI_Grave) + return kVK_ISO_Section; + if (keycode == kVK_ISO_Section) + return kVK_ANSI_Grave; + } + + return keycode; +} + +uint32_t KeyboardMacOS::translateSystemKeyCode(int systemKeyCode) +{ + if ((unsigned)systemKeyCode >= code_map_osx_to_qnum_len) + return 0; + + return code_map_osx_to_qnum[systemKeyCode]; +} + +NSString* KeyboardMacOS::keyTranslate(unsigned keyCode, + unsigned modifierFlags) +{ + const UCKeyboardLayout *layout; + OSStatus err; + + layout = nullptr; + + TISInputSourceRef keyboard; + CFDataRef uchr; + + keyboard = TISCopyCurrentKeyboardLayoutInputSource(); + uchr = (CFDataRef)TISGetInputSourceProperty(keyboard, + kTISPropertyUnicodeKeyLayoutData); + if (uchr == nullptr) + return nil; + + layout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr); + if (layout == nullptr) + return nil; + + UInt32 dead_state; + UniCharCount max_len, actual_len; + UniChar string[255]; + + dead_state = 0; + max_len = sizeof(string)/sizeof(*string); + + modifierFlags = (modifierFlags >> 8) & 0xff; + + err = UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierFlags, + LMGetKbdType(), 0, &dead_state, max_len, &actual_len, + string); + if (err != noErr) + return nil; + + // Dead key? + if (dead_state != 0) { + // We have no fool proof way of asking what dead key this is. + // Assume we get a spacing equivalent if we press the + // same key again, and try to deduce something from that. + err = UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierFlags, + LMGetKbdType(), 0, &dead_state, max_len, &actual_len, + string); + if (err != noErr) + return nil; + } + + return [NSString stringWithCharacters:string length:actual_len]; +} + +uint32_t KeyboardMacOS::translateEventKeysym(const NSEvent* nsevent) +{ + UInt16 key_code; + size_t i; + + NSString *chars; + UInt32 modifiers; + + key_code = [nsevent keyCode]; + + // Start with keys that either don't generate a symbol, or + // generate the same symbol as some other key. + for (i = 0;i < sizeof(kvk_map)/sizeof(kvk_map[0]);i++) { + if (key_code == kvk_map[i][0]) + return kvk_map[i][1]; + } + + // OS X always sends the same key code for the decimal key on the + // num pad, but X11 wants different keysyms depending on if it should + // be a comma or full stop. + if (key_code == 0x41) { + switch ([[nsevent charactersIgnoringModifiers] UTF8String][0]) { + case ',': + return XK_KP_Separator; + case '.': + return XK_KP_Decimal; + default: + return NoSymbol; + } + } + + // We want a "normal" symbol out of the event, which basically means + // we only respect the shift and alt/altgr modifiers. Cocoa can help + // us if we only wanted shift, but as we also want alt/altgr, we'll + // have to do some lookup ourselves. This matches our behaviour on + // other platforms. + + modifiers = 0; + if ([nsevent modifierFlags] & NSAlphaShiftKeyMask) + modifiers |= alphaLock; + if ([nsevent modifierFlags] & NSShiftKeyMask) + modifiers |= shiftKey; + if ([nsevent modifierFlags] & NSAlternateKeyMask) + modifiers |= optionKey; + + chars = keyTranslate(key_code, modifiers); + if (chars == nil) + return NoSymbol; + + // FIXME: Some dead keys are given as NBSP + combining character + if ([chars length] != 1) + return NoSymbol; + + // Dead key? + if ([[nsevent characters] length] == 0) + return ucs2keysym(ucs2combining([chars characterAtIndex:0])); + + return ucs2keysym([chars characterAtIndex:0]); +} + +int KeyboardMacOS::openHID(unsigned int* ioc) +{ + kern_return_t ret; + io_service_t ios; + CFMutableDictionaryRef mdict; + + mdict = IOServiceMatching(kIOHIDSystemClass); + ios = IOServiceGetMatchingService(kIOMasterPortDefault, + (CFDictionaryRef) mdict); + if (!ios) + return KERN_FAILURE; + + ret = IOServiceOpen(ios, mach_task_self(), kIOHIDParamConnectType, ioc); + IOObjectRelease(ios); + if (ret != KERN_SUCCESS) + return ret; + + return KERN_SUCCESS; +} + +int KeyboardMacOS::getModifierLockState(int modifier, bool* on) +{ + kern_return_t ret; + io_connect_t ioc; + + ret = openHID(&ioc); + if (ret != KERN_SUCCESS) + return ret; + + ret = IOHIDGetModifierLockState(ioc, modifier, on); + IOServiceClose(ioc); + if (ret != KERN_SUCCESS) + return ret; + + return KERN_SUCCESS; +} + +int KeyboardMacOS::setModifierLockState(int modifier, bool on) +{ + kern_return_t ret; + io_connect_t ioc; + + ret = openHID(&ioc); + if (ret != KERN_SUCCESS) + return ret; + + ret = IOHIDSetModifierLockState(ioc, modifier, on); + IOServiceClose(ioc); + if (ret != KERN_SUCCESS) + return ret; + + return KERN_SUCCESS; +} diff --git a/vncviewer/KeyboardWin32.cxx b/vncviewer/KeyboardWin32.cxx new file mode 100644 index 00000000..ad396485 --- /dev/null +++ b/vncviewer/KeyboardWin32.cxx @@ -0,0 +1,639 @@ +/* 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_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); +} diff --git a/vncviewer/KeyboardWin32.h b/vncviewer/KeyboardWin32.h new file mode 100644 index 00000000..336fe6da --- /dev/null +++ b/vncviewer/KeyboardWin32.h @@ -0,0 +1,58 @@ +/* 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. + */ + +#ifndef __KEYBOARDWIN32_H__ +#define __KEYBOARDWIN32_H__ + +#include "Keyboard.h" + +class KeyboardWin32 : public Keyboard +{ +public: + KeyboardWin32(KeyboardHandler* handler); + virtual ~KeyboardWin32(); + + bool handleEvent(const void* event) override; + + void reset() override; + + unsigned getLEDState() override; + void setLEDState(unsigned state) override; + +protected: + uint32_t translateSystemKeyCode(int systemKeyCode); + uint32_t lookupVKeyMap(unsigned vkey, bool extended, + const UINT map[][3], size_t size); + uint32_t translateVKey(unsigned vkey, bool extended); + + bool hasAltGr(); + static void handleAltGrTimeout(void *data); + void resolveAltGrDetection(bool isAltGrSequence); + +private: + int cachedHasAltGr; + HKL currentLayout; + + bool altGrArmed; + unsigned int altGrCtrlTime; + + bool leftShiftDown; + bool rightShiftDown; +}; + +#endif diff --git a/vncviewer/KeyboardX11.cxx b/vncviewer/KeyboardX11.cxx new file mode 100644 index 00000000..90a4cedc --- /dev/null +++ b/vncviewer/KeyboardX11.cxx @@ -0,0 +1,219 @@ +/* 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 +#include + +#include +#include +#include + +#include "i18n.h" +#include "KeyboardX11.h" + +extern const struct _code_map_xkb_to_qnum { + const char * from; + const unsigned short to; +} code_map_xkb_to_qnum[]; +extern const unsigned int code_map_xkb_to_qnum_len; + +static rfb::LogWriter vlog("KeyboardX11"); + +KeyboardX11::KeyboardX11(KeyboardHandler* handler_) + : Keyboard(handler_) +{ + XkbDescPtr xkb; + Status status; + + xkb = XkbGetMap(fl_display, 0, XkbUseCoreKbd); + if (!xkb) + throw rfb::Exception("XkbGetMap"); + + status = XkbGetNames(fl_display, XkbKeyNamesMask, xkb); + if (status != Success) + throw rfb::Exception("XkbGetNames"); + + memset(code_map_keycode_to_qnum, 0, sizeof(code_map_keycode_to_qnum)); + for (KeyCode keycode = xkb->min_key_code; + keycode < xkb->max_key_code; + keycode++) { + const char *keyname = xkb->names->keys[keycode].name; + unsigned short rfbcode; + + if (keyname[0] == '\0') + continue; + + rfbcode = 0; + for (unsigned i = 0;i < code_map_xkb_to_qnum_len;i++) { + if (strncmp(code_map_xkb_to_qnum[i].from, + keyname, XkbKeyNameLength) == 0) { + rfbcode = code_map_xkb_to_qnum[i].to; + break; + } + } + if (rfbcode != 0) + code_map_keycode_to_qnum[keycode] = rfbcode; + else + vlog.debug("No key mapping for key %.4s", keyname); + } + + XkbFreeKeyboard(xkb, 0, True); +} + +KeyboardX11::~KeyboardX11() +{ +} + +bool KeyboardX11::handleEvent(const void* event) +{ + const XEvent *xevent = (const XEvent*)event; + + assert(event); + + if (xevent->type == KeyPress) { + int keycode; + char str; + KeySym keysym; + + keycode = code_map_keycode_to_qnum[xevent->xkey.keycode]; + + XLookupString((XKeyEvent*)&xevent->xkey, &str, 1, &keysym, nullptr); + if (keysym == NoSymbol) { + vlog.error(_("No symbol for key code %d (in the current state)"), + (int)xevent->xkey.keycode); + } + + handler->handleKeyPress(xevent->xkey.keycode, keycode, keysym); + return true; + } else if (xevent->type == KeyRelease) { + handler->handleKeyRelease(xevent->xkey.keycode); + return true; + } + + return false; +} + +unsigned KeyboardX11::getLEDState() +{ + unsigned state; + + unsigned int mask; + + Status status; + XkbStateRec xkbState; + + status = XkbGetState(fl_display, XkbUseCoreKbd, &xkbState); + if (status != Success) { + vlog.error(_("Failed to get keyboard LED state: %d"), status); + return rfb::ledUnknown; + } + + state = 0; + + if (xkbState.locked_mods & LockMask) + state |= rfb::ledCapsLock; + + mask = getModifierMask(XK_Num_Lock); + if (xkbState.locked_mods & mask) + state |= rfb::ledNumLock; + + mask = getModifierMask(XK_Scroll_Lock); + if (xkbState.locked_mods & mask) + state |= rfb::ledScrollLock; + + return state; +} + +void KeyboardX11::setLEDState(unsigned state) +{ + unsigned int affect, values; + unsigned int mask; + + Bool ret; + + affect = values = 0; + + affect |= LockMask; + if (state & rfb::ledCapsLock) + values |= LockMask; + + mask = getModifierMask(XK_Num_Lock); + affect |= mask; + if (state & rfb::ledNumLock) + values |= mask; + + mask = getModifierMask(XK_Scroll_Lock); + affect |= mask; + if (state & rfb::ledScrollLock) + values |= mask; + + ret = XkbLockModifiers(fl_display, XkbUseCoreKbd, affect, values); + if (!ret) + vlog.error(_("Failed to update keyboard LED state")); +} + +unsigned KeyboardX11::getModifierMask(uint32_t keysym) +{ + XkbDescPtr xkb; + unsigned int mask, keycode; + XkbAction *act; + + mask = 0; + + xkb = XkbGetMap(fl_display, XkbAllComponentsMask, XkbUseCoreKbd); + if (xkb == nullptr) + return 0; + + for (keycode = xkb->min_key_code; keycode <= xkb->max_key_code; keycode++) { + unsigned int state_out; + KeySym ks; + + XkbTranslateKeyCode(xkb, keycode, 0, &state_out, &ks); + if (ks == NoSymbol) + continue; + + if (ks == keysym) + break; + } + + // KeySym not mapped? + if (keycode > xkb->max_key_code) + goto out; + + act = XkbKeyAction(xkb, keycode, 0); + if (act == nullptr) + goto out; + if (act->type != XkbSA_LockMods) + goto out; + + if (act->mods.flags & XkbSA_UseModMapMods) + mask = xkb->map->modmap[keycode]; + else + mask = act->mods.mask; + +out: + XkbFreeKeyboard(xkb, XkbAllComponentsMask, True); + + return mask; +} diff --git a/vncviewer/KeyboardX11.h b/vncviewer/KeyboardX11.h new file mode 100644 index 00000000..ba9a88f9 --- /dev/null +++ b/vncviewer/KeyboardX11.h @@ -0,0 +1,42 @@ +/* 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. + */ + +#ifndef __KEYBOARDX11_H__ +#define __KEYBOARDX11_H__ + +#include "Keyboard.h" + +class KeyboardX11 : public Keyboard +{ +public: + KeyboardX11(KeyboardHandler* handler); + virtual ~KeyboardX11(); + + bool handleEvent(const void* event) override; + + unsigned getLEDState() override; + void setLEDState(unsigned state) override; + +protected: + unsigned getModifierMask(uint32_t keysym); + +private: + int code_map_keycode_to_qnum[256]; +}; + +#endif diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index f5bc166d..6a09f407 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2019 Pierre Ossman for Cendio AB + * 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 @@ -33,29 +33,10 @@ // FLTK can pull in the X11 headers on some systems #ifndef XK_VoidSymbol -#define XK_LATIN1 #define XK_MISCELLANY -#define XK_XKB_KEYS #include #endif -#ifndef XF86XK_ModeLock -#include -#endif - -#if ! (defined(WIN32) || defined(__APPLE__)) -#include -#endif - -#ifndef NoSymbol -#define NoSymbol 0 -#endif - -// Missing in at least some versions of MinGW -#ifndef MAPVK_VK_TO_VSC -#define MAPVK_VK_TO_VSC 0 -#endif - #include "fltk/layout.h" #include "fltk/util.h" #include "Viewport.h" @@ -64,7 +45,6 @@ #include "DesktopWindow.h" #include "i18n.h" #include "parameters.h" -#include "keysym2ucs.h" #include "menukey.h" #include "vncviewer.h" @@ -77,28 +57,18 @@ #include #include -#if !defined(WIN32) && !defined(__APPLE__) -#include -extern const struct _code_map_xkb_to_qnum { - const char * from; - const unsigned short to; -} code_map_xkb_to_qnum[]; -extern const unsigned int code_map_xkb_to_qnum_len; - -static int code_map_keycode_to_qnum[256]; +#if defined(WIN32) +#include "KeyboardWin32.h" +#elif defined(__APPLE__) +#include "KeyboardMacOS.h" +#else +#include "KeyboardX11.h" #endif #ifdef __APPLE__ #include "cocoa.h" -extern const unsigned short code_map_osx_to_qnum[]; -extern const unsigned int code_map_osx_to_qnum_len; -#endif - -#ifdef WIN32 -#include "win32.h" #endif - using namespace rfb; static rfb::LogWriter vlog("Viewport"); @@ -109,11 +79,6 @@ enum { ID_DISCONNECT, ID_FULLSCREEN, ID_MINIMIZE, ID_RESIZE, ID_CTRL, ID_ALT, ID_MENUKEY, ID_CTRLALTDEL, ID_REFRESH, ID_OPTIONS, ID_INFO, ID_ABOUT }; -// Used to detect fake input (0xaa is not a real key) -#ifdef WIN32 -static const WORD SCAN_FAKE = 0xaa; -#endif - // Used for fake key presses from the menu static const int FAKE_CTRL_KEY_CODE = 0x10001; static const int FAKE_ALT_KEY_CODE = 0x10002; @@ -125,49 +90,16 @@ static const int FAKE_KEY_CODE = 0xffff; Viewport::Viewport(int w, int h, const rfb::PixelFormat& /*serverPF*/, CConn* cc_) : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(nullptr), lastPointerPos(0, 0), lastButtonMask(0), -#ifdef WIN32 - altGrArmed(false), leftShiftDown(false), rightShiftDown(false), -#endif + keyboard(nullptr), firstLEDState(true), pendingClientClipboard(false), menuCtrlKey(false), menuAltKey(false), cursor(nullptr) { -#if !defined(WIN32) && !defined(__APPLE__) - XkbDescPtr xkb; - Status status; - - xkb = XkbGetMap(fl_display, 0, XkbUseCoreKbd); - if (!xkb) - throw rfb::Exception("XkbGetMap"); - - status = XkbGetNames(fl_display, XkbKeyNamesMask, xkb); - if (status != Success) - throw rfb::Exception("XkbGetNames"); - - memset(code_map_keycode_to_qnum, 0, sizeof(code_map_keycode_to_qnum)); - for (KeyCode keycode = xkb->min_key_code; - keycode < xkb->max_key_code; - keycode++) { - const char *keyname = xkb->names->keys[keycode].name; - unsigned short rfbcode; - - if (keyname[0] == '\0') - continue; - - rfbcode = 0; - for (unsigned i = 0;i < code_map_xkb_to_qnum_len;i++) { - if (strncmp(code_map_xkb_to_qnum[i].from, - keyname, XkbKeyNameLength) == 0) { - rfbcode = code_map_xkb_to_qnum[i].to; - break; - } - } - if (rfbcode != 0) - code_map_keycode_to_qnum[keycode] = rfbcode; - else - vlog.debug("No key mapping for key %.4s", keyname); - } - - XkbFreeKeyboard(xkb, 0, True); +#if defined(WIN32) + keyboard = new KeyboardWin32(this); +#elif defined(__APPLE__) + keyboard = new KeyboardMacOS(this); +#else + keyboard = new KeyboardX11(this); #endif Fl::add_clipboard_notify(handleClipboardChange, this); @@ -204,9 +136,6 @@ Viewport::~Viewport() // Unregister all timeouts in case they get a change tro trigger // again later when this object is already gone. Fl::remove_timeout(handlePointerTimeout, this); -#ifdef WIN32 - Fl::remove_timeout(handleAltGrTimeout, this); -#endif Fl::remove_system_handler(handleSystemEvent); @@ -220,6 +149,8 @@ Viewport::~Viewport() delete cursor; } + delete keyboard; + // FLTK automatically deletes all child widgets, so we shouldn't touch // them ourselves here } @@ -354,90 +285,7 @@ void Viewport::setLEDState(unsigned int ledState) if (!hasFocus()) return; -#if defined(WIN32) - INPUT input[6]; - UINT count; - UINT ret; - - memset(input, 0, sizeof(input)); - count = 0; - - if (!!(ledState & 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 (!!(ledState & 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 (!!(ledState & 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()); -#elif defined(__APPLE__) - int ret; - - ret = cocoa_set_caps_lock_state(ledState & ledCapsLock); - if (ret != 0) { - vlog.error(_("Failed to update keyboard LED state: %d"), ret); - return; - } - - ret = cocoa_set_num_lock_state(ledState & ledNumLock); - if (ret != 0) { - vlog.error(_("Failed to update keyboard LED state: %d"), ret); - return; - } - - // No support for Scroll Lock // - -#else - unsigned int affect, values; - unsigned int mask; - - Bool ret; - - affect = values = 0; - - affect |= LockMask; - if (ledState & ledCapsLock) - values |= LockMask; - - mask = getModifierMask(XK_Num_Lock); - affect |= mask; - if (ledState & ledNumLock) - values |= mask; - - mask = getModifierMask(XK_Scroll_Lock); - affect |= mask; - if (ledState & ledScrollLock) - values |= mask; - - ret = XkbLockModifiers(fl_display, XkbUseCoreKbd, affect, values); - if (!ret) - vlog.error(_("Failed to update keyboard LED state")); -#endif + keyboard->setLEDState(ledState); } void Viewport::pushLEDState() @@ -448,60 +296,13 @@ void Viewport::pushLEDState() if (cc->server.ledState() == ledUnknown) return; - ledState = 0; - -#if defined(WIN32) - if (GetKeyState(VK_CAPITAL) & 0x1) - ledState |= ledCapsLock; - if (GetKeyState(VK_NUMLOCK) & 0x1) - ledState |= ledNumLock; - if (GetKeyState(VK_SCROLL) & 0x1) - ledState |= ledScrollLock; -#elif defined(__APPLE__) - int ret; - bool on; - - ret = cocoa_get_caps_lock_state(&on); - if (ret != 0) { - vlog.error(_("Failed to get keyboard LED state: %d"), ret); - return; - } - if (on) - ledState |= ledCapsLock; - - ret = cocoa_get_num_lock_state(&on); - if (ret != 0) { - vlog.error(_("Failed to get keyboard LED state: %d"), ret); + ledState = keyboard->getLEDState(); + if (ledState == ledUnknown) return; - } - if (on) - ledState |= ledNumLock; +#if defined(__APPLE__) // No support for Scroll Lock // ledState |= (cc->server.ledState() & ledScrollLock); - -#else - unsigned int mask; - - Status status; - XkbStateRec xkbState; - - status = XkbGetState(fl_display, XkbUseCoreKbd, &xkbState); - if (status != Success) { - vlog.error(_("Failed to get keyboard LED state: %d"), status); - return; - } - - if (xkbState.locked_mods & LockMask) - ledState |= ledCapsLock; - - mask = getModifierMask(XK_Num_Lock); - if (xkbState.locked_mods & mask) - ledState |= ledNumLock; - - mask = getModifierMask(XK_Scroll_Lock); - if (xkbState.locked_mods & mask) - ledState |= ledScrollLock; #endif if ((ledState & ledCapsLock) != (cc->server.ledState() & ledCapsLock)) { @@ -698,54 +499,6 @@ bool Viewport::hasFocus() return focus == this; } -#if ! (defined(WIN32) || defined(__APPLE__)) -unsigned int Viewport::getModifierMask(unsigned int keysym) -{ - XkbDescPtr xkb; - unsigned int mask, keycode; - XkbAction *act; - - mask = 0; - - xkb = XkbGetMap(fl_display, XkbAllComponentsMask, XkbUseCoreKbd); - if (xkb == nullptr) - return 0; - - for (keycode = xkb->min_key_code; keycode <= xkb->max_key_code; keycode++) { - unsigned int state_out; - KeySym ks; - - XkbTranslateKeyCode(xkb, keycode, 0, &state_out, &ks); - if (ks == NoSymbol) - continue; - - if (ks == keysym) - break; - } - - // KeySym not mapped? - if (keycode > xkb->max_key_code) - goto out; - - act = XkbKeyAction(xkb, keycode, 0); - if (act == nullptr) - goto out; - if (act->type != XkbSA_LockMods) - goto out; - - if (act->mods.flags & XkbSA_UseModMapMods) - mask = xkb->map->modmap[keycode]; - else - mask = act->mods.mask; - -out: - XkbFreeKeyboard(xkb, XkbAllComponentsMask, True); - - return mask; -} -#endif - - void Viewport::handleClipboardChange(int source, void *data) { Viewport *self = (Viewport *)data; @@ -835,10 +588,7 @@ void Viewport::resetKeyboard() abort_connection_with_unexpected_error(e); } -#ifdef WIN32 - leftShiftDown = false; - rightShiftDown = false; -#endif + keyboard->reset(); } @@ -885,294 +635,28 @@ void Viewport::handleKeyRelease(int systemKeyCode) int Viewport::handleSystemEvent(void *event, void *data) { Viewport *self = (Viewport *)data; + bool consumed; assert(self); if (!self->hasFocus()) return 0; - assert(event); - -#if defined(WIN32) - MSG *msg = (MSG*)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_MOUSEWHEEL) || - (msg->message == WM_MOUSEHWHEEL)) { - // We can't get a mouse event in the middle of an AltGr sequence, so - // abort that detection - if (self->altGrArmed) - self->resolveAltGrDetection(false); - - return 0; // We didn't really consume the mouse event - } else if ((msg->message == WM_KEYDOWN) || (msg->message == WM_SYSKEYDOWN)) { - UINT vKey; - bool isExtended; - int keyCode; - uint32_t keySym; - - vKey = msg->wParam; - isExtended = (msg->lParam & (1 << 24)) != 0; - - keyCode = ((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 && (keyCode == 0x00) && (vKey == VK_MENU)) { - isExtended = true; - keyCode = 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 (self->altGrArmed) { - bool altPressed = isExtended && - (keyCode == 0x38) && - (vKey == VK_MENU) && - ((msg->time - self->altGrCtrlTime) < 50); - self->resolveAltGrDetection(altPressed); - } - - if (keyCode == SCAN_FAKE) { - vlog.debug("Ignoring fake key press (virtual key 0x%02x)", vKey); - return 1; - } - - // Windows sets the scan code to 0x00 for multimedia keys, so we - // have to do a reverse lookup based on the vKey. - if (keyCode == 0x00) { - keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC); - if (keyCode == 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 1; - } - } - - if (keyCode & ~0x7f) { - vlog.error(_("Invalid scan code 0x%02x"), (int)keyCode); - return 1; - } - - if (isExtended) - keyCode |= 0x80; - - - // 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 (keyCode == 0x45) - keyCode = 0xc6; - - // And NumLock incorrectly has the extended bit set - if (keyCode == 0xc5) - keyCode = 0x45; - - // And Alt+PrintScreen (i.e. SysRq) sends a different code than - // PrintScreen - if (keyCode == 0xb7) - keyCode = 0x54; - - keySym = win32_vkey_to_keysym(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) && (keyCode == 0x36)) - keySym = XK_Shift_R; - - // AltGr handling (see above) - if (win32_has_altgr()) { - if ((keyCode == 0xb8) && (keySym == XK_Alt_R)) - keySym = XK_ISO_Level3_Shift; - - // Possible start of AltGr sequence? - if ((keyCode == 0x1d) && (keySym == XK_Control_L)) { - self->altGrArmed = true; - self->altGrCtrlTime = msg->time; - Fl::add_timeout(0.1, handleAltGrTimeout, self); - return 1; - } - } - - self->handleKeyPress(keyCode, 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: - self->handleKeyRelease(keyCode); - } - - // Shift key tracking, see below - if (keyCode == 0x2a) - self->leftShiftDown = true; - if (keyCode == 0x36) - self->rightShiftDown = true; - - return 1; - } else if ((msg->message == WM_KEYUP) || (msg->message == WM_SYSKEYUP)) { - UINT vKey; - bool isExtended; - int keyCode; - - vKey = msg->wParam; - isExtended = (msg->lParam & (1 << 24)) != 0; - - keyCode = ((msg->lParam >> 16) & 0xff); - - // Touch keyboard AltGr (see above) - if (!isExtended && (keyCode == 0x00) && (vKey == VK_MENU)) { - isExtended = true; - keyCode = 0x38; - } - - // We can't get a release in the middle of an AltGr sequence, so - // abort that detection - if (self->altGrArmed) - self->resolveAltGrDetection(false); - - if (keyCode == SCAN_FAKE) { - vlog.debug("Ignoring fake key release (virtual key 0x%02x)", vKey); - return 1; - } - - if (keyCode == 0x00) - keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC); - if (isExtended) - keyCode |= 0x80; - if (keyCode == 0x45) - keyCode = 0xc6; - if (keyCode == 0xc5) - keyCode = 0x45; - if (keyCode == 0xb7) - keyCode = 0x54; - - self->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 ((keyCode == 0x2a) || (keyCode == 0x36)) { - if (self->leftShiftDown) - self->handleKeyRelease(0x2a); - if (self->rightShiftDown) - self->handleKeyRelease(0x36); - self->leftShiftDown = false; - self->rightShiftDown = false; - } - - return 1; - } -#elif defined(__APPLE__) +#ifdef __APPLE__ // Special event that means we temporarily lost some input - if (cocoa_is_keyboard_sync(event)) { + if (KeyboardMacOS::isKeyboardSync(event)) { self->resetKeyboard(); return 1; } +#endif - if (cocoa_is_keyboard_event(event)) { - int systemKeyCode; - - systemKeyCode = cocoa_event_keycode(event); - - if (cocoa_is_key_press(event)) { - uint32_t keyCode; - uint32_t keySym; - - if ((unsigned)systemKeyCode >= code_map_osx_to_qnum_len) - keyCode = 0; - else - keyCode = code_map_osx_to_qnum[systemKeyCode]; - - keySym = cocoa_event_keysym(event); - if (keySym == NoSymbol) { - vlog.error(_("No symbol for key code 0x%02x (in the current state)"), - (int)keyCode); - } - - self->handleKeyPress(systemKeyCode, keyCode, keySym); - - // We don't get any release events for CapsLock, so we have to - // send the release right away. - if (keySym == XK_Caps_Lock) - self->handleKeyRelease(systemKeyCode); - } else { - self->handleKeyRelease(systemKeyCode); - } - - return 1; - } -#else - XEvent *xevent = (XEvent*)event; - - if (xevent->type == KeyPress) { - int keycode; - char str; - KeySym keysym; - - keycode = code_map_keycode_to_qnum[xevent->xkey.keycode]; - - XLookupString(&xevent->xkey, &str, 1, &keysym, nullptr); - if (keysym == NoSymbol) { - vlog.error(_("No symbol for key code %d (in the current state)"), - (int)xevent->xkey.keycode); - } - - self->handleKeyPress(xevent->xkey.keycode, keycode, keysym); - return 1; - } else if (xevent->type == KeyRelease) { - self->handleKeyRelease(xevent->xkey.keycode); + consumed = self->keyboard->handleEvent(event); + if (consumed) return 1; - } -#endif return 0; } -#ifdef WIN32 -void Viewport::handleAltGrTimeout(void *data) -{ - Viewport *self = (Viewport *)data; - - assert(self); - - self->altGrArmed = false; - self->handleKeyPress(0x1d, 0x1d, XK_Control_L); -} - -void Viewport::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) - handleKeyPress(0x1d, 0x1d, XK_Control_L); -} -#endif - // FIXME: gcc confuses ID_DISCONNECT with NULL #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index 72df22f4..af1eaf2a 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2019 Pierre Ossman for Cendio AB + * 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 @@ -25,15 +25,18 @@ #include #include "EmulateMB.h" +#include "Keyboard.h" class Fl_Menu_Button; class Fl_RGB_Image; class CConn; +class Keyboard; class PlatformPixelBuffer; class Surface; -class Viewport : public Fl_Widget, public EmulateMB { +class Viewport : public Fl_Widget, protected EmulateMB, + protected KeyboardHandler { public: Viewport(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_); @@ -73,8 +76,6 @@ protected: private: bool hasFocus(); - unsigned int getModifierMask(unsigned int keysym); - static void handleClipboardChange(int source, void *data); void flushPendingClipboard(); @@ -85,16 +86,11 @@ private: void resetKeyboard(); void handleKeyPress(int systemKeyCode, - uint32_t keyCode, uint32_t keySym); - void handleKeyRelease(int systemKeyCode); + uint32_t keyCode, uint32_t keySym) override; + void handleKeyRelease(int systemKeyCode) override; static int handleSystemEvent(void *event, void *data); -#ifdef WIN32 - static void handleAltGrTimeout(void *data); - void resolveAltGrDetection(bool isAltGrSequence); -#endif - void pushLEDState(); void initContextMenu(); @@ -112,13 +108,7 @@ private: rfb::Point lastPointerPos; uint8_t lastButtonMask; -#ifdef WIN32 - bool altGrArmed; - unsigned int altGrCtrlTime; - - bool leftShiftDown; - bool rightShiftDown; -#endif + Keyboard* keyboard; bool firstLEDState; diff --git a/vncviewer/cocoa.h b/vncviewer/cocoa.h index 63b2a535..64acefbf 100644 --- a/vncviewer/cocoa.h +++ b/vncviewer/cocoa.h @@ -34,18 +34,4 @@ CGColorSpaceRef cocoa_win_color_space(Fl_Window *win); bool cocoa_win_is_zoomed(Fl_Window *win); void cocoa_win_zoom(Fl_Window *win); -int cocoa_is_keyboard_sync(const void *event); -int cocoa_is_keyboard_event(const void *event); - -int cocoa_is_key_press(const void *event); - -int cocoa_event_keycode(const void *event); -int cocoa_event_keysym(const void *event); - -int cocoa_set_caps_lock_state(bool on); -int cocoa_set_num_lock_state(bool on); - -int cocoa_get_caps_lock_state(bool *on); -int cocoa_get_num_lock_state(bool *on); - #endif diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm index a355b484..1d63b750 100644 --- a/vncviewer/cocoa.mm +++ b/vncviewer/cocoa.mm @@ -25,29 +25,9 @@ #include #import -#import -#include -#include - -#define XK_LATIN1 -#define XK_MISCELLANY -#define XK_XKB_KEYS -#include -#include #include -#include "keysym2ucs.h" - -#define NoSymbol 0 - -// This wasn't added until 10.12 -#if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12 -const int kVK_RightCommand = 0x36; -#endif -// And this is still missing -const int kVK_Menu = 0x6E; - static bool captured = false; int cocoa_get_level(Fl_Window *win) @@ -179,384 +159,3 @@ void cocoa_win_zoom(Fl_Window *win) nsw = (NSWindow*)fl_xid(win); [nsw zoom:nsw]; } - -int cocoa_is_keyboard_sync(const void *event) -{ - const NSEvent* nsevent = (const NSEvent*)event; - - assert(event); - - // If we get a NSFlagsChanged event with key code 0 then this isn't - // an actual keyboard event but rather the system trying to sync up - // modifier state after it has stolen input for some reason (e.g. - // Cmd+Tab) - - if ([nsevent type] != NSFlagsChanged) - return 0; - if ([nsevent keyCode] != 0) - return 0; - - return 1; -} - -int cocoa_is_keyboard_event(const void *event) -{ - NSEvent *nsevent; - - nsevent = (NSEvent*)event; - - switch ([nsevent type]) { - case NSKeyDown: - case NSKeyUp: - case NSFlagsChanged: - if (cocoa_is_keyboard_sync(event)) - return 0; - return 1; - default: - return 0; - } -} - -int cocoa_is_key_press(const void *event) -{ - NSEvent *nsevent; - - nsevent = (NSEvent*)event; - - if ([nsevent type] == NSKeyDown) - return 1; - - if ([nsevent type] == NSFlagsChanged) { - UInt32 mask; - - // We don't see any event on release of CapsLock - if ([nsevent keyCode] == kVK_CapsLock) - return 1; - - // These are entirely undocumented, but I cannot find any other way - // of differentiating between left and right keys - switch ([nsevent keyCode]) { - case kVK_RightCommand: - mask = 0x0010; - break; - case kVK_Command: - mask = 0x0008; - break; - case kVK_Shift: - mask = 0x0002; - break; - case kVK_CapsLock: - // We don't see any event on release of CapsLock - return 1; - case kVK_Option: - mask = 0x0020; - break; - case kVK_Control: - mask = 0x0001; - break; - case kVK_RightShift: - mask = 0x0004; - break; - case kVK_RightOption: - mask = 0x0040; - break; - case kVK_RightControl: - mask = 0x2000; - break; - default: - return 0; - } - - if ([nsevent modifierFlags] & mask) - return 1; - else - return 0; - } - - return 0; -} - -int cocoa_event_keycode(const void *event) -{ - NSEvent *nsevent; - int keycode; - - nsevent = (NSEvent*)event; - - keycode = [nsevent keyCode]; - - // macOS swaps these two keys for unknown reasons for ISO layouts - if (KBGetLayoutType(LMGetKbdType()) == kKeyboardISO) { - if (keycode == kVK_ANSI_Grave) - return kVK_ISO_Section; - if (keycode == kVK_ISO_Section) - return kVK_ANSI_Grave; - } - - return keycode; -} - -static NSString *key_translate(UInt16 keyCode, UInt32 modifierFlags) -{ - const UCKeyboardLayout *layout; - OSStatus err; - - layout = nullptr; - - TISInputSourceRef keyboard; - CFDataRef uchr; - - keyboard = TISCopyCurrentKeyboardLayoutInputSource(); - uchr = (CFDataRef)TISGetInputSourceProperty(keyboard, - kTISPropertyUnicodeKeyLayoutData); - if (uchr == nullptr) - return nil; - - layout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr); - if (layout == nullptr) - return nil; - - UInt32 dead_state; - UniCharCount max_len, actual_len; - UniChar string[255]; - - dead_state = 0; - max_len = sizeof(string)/sizeof(*string); - - modifierFlags = (modifierFlags >> 8) & 0xff; - - err = UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierFlags, - LMGetKbdType(), 0, &dead_state, max_len, &actual_len, - string); - if (err != noErr) - return nil; - - // Dead key? - if (dead_state != 0) { - // We have no fool proof way of asking what dead key this is. - // Assume we get a spacing equivalent if we press the - // same key again, and try to deduce something from that. - err = UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierFlags, - LMGetKbdType(), 0, &dead_state, max_len, &actual_len, - string); - if (err != noErr) - return nil; - } - - return [NSString stringWithCharacters:string length:actual_len]; -} - -static const int kvk_map[][2] = { - { kVK_Return, XK_Return }, - { kVK_Tab, XK_Tab }, - { kVK_Space, XK_space }, - { kVK_Delete, XK_BackSpace }, - { kVK_Escape, XK_Escape }, - { kVK_RightCommand, XK_Super_R }, - { kVK_Command, XK_Super_L }, - { kVK_Shift, XK_Shift_L }, - { kVK_CapsLock, XK_Caps_Lock }, - { kVK_Option, XK_Alt_L }, - { kVK_Control, XK_Control_L }, - { kVK_RightShift, XK_Shift_R }, - { kVK_RightOption, XK_Alt_R }, - { kVK_RightControl, XK_Control_R }, - { kVK_F17, XK_F17 }, - { kVK_VolumeUp, XF86XK_AudioRaiseVolume }, - { kVK_VolumeDown, XF86XK_AudioLowerVolume }, - { kVK_Mute, XF86XK_AudioMute }, - { kVK_F18, XK_F18 }, - { kVK_F19, XK_F19 }, - { kVK_F20, XK_F20 }, - { kVK_F5, XK_F5 }, - { kVK_F6, XK_F6 }, - { kVK_F7, XK_F7 }, - { kVK_F3, XK_F3 }, - { kVK_F8, XK_F8 }, - { kVK_F9, XK_F9 }, - { kVK_F11, XK_F11 }, - { kVK_F13, XK_F13 }, - { kVK_F16, XK_F16 }, - { kVK_F14, XK_F14 }, - { kVK_F10, XK_F10 }, - { kVK_Menu, XK_Menu }, - { kVK_F12, XK_F12 }, - { kVK_F15, XK_F15 }, - // Should we send Insert here? - { kVK_Help, XK_Help }, - { kVK_Home, XK_Home }, - { kVK_PageUp, XK_Page_Up }, - { kVK_ForwardDelete, XK_Delete }, - { kVK_F4, XK_F4 }, - { kVK_End, XK_End }, - { kVK_F2, XK_F2 }, - { kVK_PageDown, XK_Page_Down }, - { kVK_F1, XK_F1 }, - { kVK_LeftArrow, XK_Left }, - { kVK_RightArrow, XK_Right }, - { kVK_DownArrow, XK_Down }, - { kVK_UpArrow, XK_Up }, - - // The OS X headers claim these keys are not layout independent. - // Could it be because of the state of the decimal key? - /* { kVK_ANSI_KeypadDecimal, XK_KP_Decimal }, */ // see below - { kVK_ANSI_KeypadMultiply, XK_KP_Multiply }, - { kVK_ANSI_KeypadPlus, XK_KP_Add }, - // OS X doesn't have NumLock, so is this really correct? - { kVK_ANSI_KeypadClear, XK_Num_Lock }, - { kVK_ANSI_KeypadDivide, XK_KP_Divide }, - { kVK_ANSI_KeypadEnter, XK_KP_Enter }, - { kVK_ANSI_KeypadMinus, XK_KP_Subtract }, - { kVK_ANSI_KeypadEquals, XK_KP_Equal }, - { kVK_ANSI_Keypad0, XK_KP_0 }, - { kVK_ANSI_Keypad1, XK_KP_1 }, - { kVK_ANSI_Keypad2, XK_KP_2 }, - { kVK_ANSI_Keypad3, XK_KP_3 }, - { kVK_ANSI_Keypad4, XK_KP_4 }, - { kVK_ANSI_Keypad5, XK_KP_5 }, - { kVK_ANSI_Keypad6, XK_KP_6 }, - { kVK_ANSI_Keypad7, XK_KP_7 }, - { kVK_ANSI_Keypad8, XK_KP_8 }, - { kVK_ANSI_Keypad9, XK_KP_9 }, - // Japanese Keyboard Support - { kVK_JIS_Eisu, XK_Eisu_toggle }, - { kVK_JIS_Kana, XK_Hiragana_Katakana }, -}; - -int cocoa_event_keysym(const void *event) -{ - NSEvent *nsevent; - - UInt16 key_code; - size_t i; - - NSString *chars; - UInt32 modifiers; - - nsevent = (NSEvent*)event; - - key_code = [nsevent keyCode]; - - // Start with keys that either don't generate a symbol, or - // generate the same symbol as some other key. - for (i = 0;i < sizeof(kvk_map)/sizeof(kvk_map[0]);i++) { - if (key_code == kvk_map[i][0]) - return kvk_map[i][1]; - } - - // OS X always sends the same key code for the decimal key on the - // num pad, but X11 wants different keysyms depending on if it should - // be a comma or full stop. - if (key_code == 0x41) { - switch ([[nsevent charactersIgnoringModifiers] UTF8String][0]) { - case ',': - return XK_KP_Separator; - case '.': - return XK_KP_Decimal; - default: - return NoSymbol; - } - } - - // We want a "normal" symbol out of the event, which basically means - // we only respect the shift and alt/altgr modifiers. Cocoa can help - // us if we only wanted shift, but as we also want alt/altgr, we'll - // have to do some lookup ourselves. This matches our behaviour on - // other platforms. - - modifiers = 0; - if ([nsevent modifierFlags] & NSAlphaShiftKeyMask) - modifiers |= alphaLock; - if ([nsevent modifierFlags] & NSShiftKeyMask) - modifiers |= shiftKey; - if ([nsevent modifierFlags] & NSAlternateKeyMask) - modifiers |= optionKey; - - chars = key_translate(key_code, modifiers); - if (chars == nil) - return NoSymbol; - - // FIXME: Some dead keys are given as NBSP + combining character - if ([chars length] != 1) - return NoSymbol; - - // Dead key? - if ([[nsevent characters] length] == 0) - return ucs2keysym(ucs2combining([chars characterAtIndex:0])); - - return ucs2keysym([chars characterAtIndex:0]); -} - -static int cocoa_open_hid(io_connect_t *ioc) -{ - kern_return_t ret; - io_service_t ios; - CFMutableDictionaryRef mdict; - - mdict = IOServiceMatching(kIOHIDSystemClass); - ios = IOServiceGetMatchingService(kIOMasterPortDefault, - (CFDictionaryRef) mdict); - if (!ios) - return KERN_FAILURE; - - ret = IOServiceOpen(ios, mach_task_self(), kIOHIDParamConnectType, ioc); - IOObjectRelease(ios); - if (ret != KERN_SUCCESS) - return ret; - - return KERN_SUCCESS; -} - -static int cocoa_set_modifier_lock_state(int modifier, bool on) -{ - kern_return_t ret; - io_connect_t ioc; - - ret = cocoa_open_hid(&ioc); - if (ret != KERN_SUCCESS) - return ret; - - ret = IOHIDSetModifierLockState(ioc, modifier, on); - IOServiceClose(ioc); - if (ret != KERN_SUCCESS) - return ret; - - return KERN_SUCCESS; -} - -static int cocoa_get_modifier_lock_state(int modifier, bool *on) -{ - kern_return_t ret; - io_connect_t ioc; - - ret = cocoa_open_hid(&ioc); - if (ret != KERN_SUCCESS) - return ret; - - ret = IOHIDGetModifierLockState(ioc, modifier, on); - IOServiceClose(ioc); - if (ret != KERN_SUCCESS) - return ret; - - return KERN_SUCCESS; -} - -int cocoa_set_caps_lock_state(bool on) -{ - return cocoa_set_modifier_lock_state(kIOHIDCapsLockState, on); -} - -int cocoa_set_num_lock_state(bool on) -{ - return cocoa_set_modifier_lock_state(kIOHIDNumLockState, on); -} - -int cocoa_get_caps_lock_state(bool *on) -{ - return cocoa_get_modifier_lock_state(kIOHIDCapsLockState, on); -} - -int cocoa_get_num_lock_state(bool *on) -{ - return cocoa_get_modifier_lock_state(kIOHIDNumLockState, on); -} diff --git a/vncviewer/win32.c b/vncviewer/win32.c index 77366f96..b0a3813c 100644 --- a/vncviewer/win32.c +++ b/vncviewer/win32.c @@ -22,25 +22,6 @@ #endif #include -#include - -#define XK_MISCELLANY -#define XK_XKB_KEYS -#define XK_KOREAN -#include -#include - -#include "keysym2ucs.h" - -#define NoSymbol 0 - -// Missing in at least some versions of MinGW -#ifndef MAPVK_VK_TO_CHAR -#define MAPVK_VK_TO_CHAR 2 -#endif - -int has_altgr; -HKL current_layout = 0; static HANDLE thread; static DWORD thread_id; @@ -48,8 +29,6 @@ static DWORD thread_id; static HHOOK hook = 0; static HWND target_wnd = 0; -#define ARRAY_SIZE(a) (sizeof(a)/sizeof(*a)) - static int is_system_hotkey(int vkCode) { switch (vkCode) { case VK_LWIN: @@ -140,288 +119,3 @@ void win32_disable_lowlevel_keyboard(HWND hwnd) CloseHandle(thread); thread = NULL; } - -// 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 }, -}; - -static int lookup_vkey_map(UINT vkey, int 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; -} - -int win32_vkey_to_keysym(UINT vkey, int 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 = lookup_vkey_map(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 = lookup_vkey_map(vkey, extended, - vkey_map_jp, ARRAY_SIZE(vkey_map_jp)); - if (ret != NoSymbol) - return ret; - } - - if (primary_lang == LANG_KOREAN) { - ret = lookup_vkey_map(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; -} - -int win32_has_altgr(void) -{ - BYTE orig_state[256]; - BYTE altgr_state[256]; - - if (current_layout == GetKeyboardLayout(0)) - return has_altgr; - - // Save current keyboard state so we can get things sane again after - // we're done - if (!GetKeyboardState(orig_state)) - 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. - - has_altgr = 0; - - memset(altgr_state, 0, sizeof(altgr_state)); - altgr_state[VK_CONTROL] = 0x80; - altgr_state[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, altgr_state, wstr, - sizeof(wstr)/sizeof(wstr[0]), 0); - if (ret == 1) { - has_altgr = 1; - break; - } - - if (ret == -1) { - // Dead key, need to clear out state before we proceed - do { - ret = ToUnicode(vkey, 0, altgr_state, wstr, - sizeof(wstr)/sizeof(wstr[0]), 0); - } while (ret < 0); - } - } - - SetKeyboardState(orig_state); - - current_layout = GetKeyboardLayout(0); - - return has_altgr; -} diff --git a/vncviewer/win32.h b/vncviewer/win32.h index ebcfccb0..c8168c62 100644 --- a/vncviewer/win32.h +++ b/vncviewer/win32.h @@ -29,10 +29,6 @@ extern "C" { int win32_enable_lowlevel_keyboard(HWND hwnd); void win32_disable_lowlevel_keyboard(HWND hwnd); - -int win32_vkey_to_keysym(UINT vkey, int extended); - -int win32_has_altgr(void); }; #endif -- cgit v1.2.3