From 0909e202538ef0b2971fbda8f103061baef7307d Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 14 May 2021 12:18:46 +0200 Subject: [PATCH] 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 | 194 +++++++++++++------------------------ vncviewer/Viewport.h | 13 ++- 4 files changed, 179 insertions(+), 135 deletions(-) diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx index b4017dba8e..eb9972f109 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 3f277d7163..af6051d1a8 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 e29c877cf5..868858071a 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -28,7 +28,6 @@ #include #include #include -#include #include #include @@ -115,11 +114,19 @@ 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), #ifdef WIN32 - altGrArmed(false), + altGrArmed(false), leftShiftDown(false), rightShiftDown(false), #endif firstLEDState(true), pendingClientClipboard(false), menuCtrlKey(false), menuAltKey(false), cursor(nullptr) @@ -499,18 +506,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); } } @@ -560,7 +567,6 @@ int Viewport::handle(int event) { std::string filtered; int buttonMask, wheelMask; - DownMap::const_iterator iter; switch (event) { case FL_PASTE: @@ -637,9 +643,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 +828,17 @@ void Viewport::handlePointerTimeout(void *data) void Viewport::resetKeyboard() { - while (!downKeySym.empty()) - handleKeyRelease(downKeySym.begin()->first); + try { + cc->releaseAllKeys(); + } catch (rdr::Exception& e) { + vlog.error("%s", e.str()); + abort_connection_with_unexpected_error(e); + } } -void Viewport::handleKeyPress(int keyCode, uint32_t keySym) +void Viewport::handleKeyPress(int systemKeyCode, + uint32_t keyCode, uint32_t keySym) { static bool menuRecursion = false; @@ -843,48 +854,8 @@ 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 - // 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. - downKeySym[keyCode] = keySym; - - vlog.debug("Key pressed: 0x%04x => XK_%s (0x%04x)", - keyCode, KeySymName(keySym), keySym); - try { - // Fake keycode? - if (keyCode > 0xff) - cc->writer()->writeKeyEvent(keySym, 0, true); - else - 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); @@ -892,35 +863,17 @@ 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()) { - // These occur somewhat frequently so let's not spam them unless - // logging is turned up. - vlog.debug("Unexpected release of key code %d", keyCode); - return; - } - - vlog.debug("Key released: 0x%04x => XK_%s (0x%04x)", - keyCode, KeySymName(iter->second), iter->second); - try { - if (keyCode > 0xff) - cc->writer()->writeKeyEvent(iter->second, 0, false); - else - cc->writer()->writeKeyEvent(iter->second, keyCode, false); + cc->sendKeyRelease(systemKeyCode); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); abort_connection_with_unexpected_error(e); } - - downKeySym.erase(iter); } @@ -1057,7 +1010,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) { @@ -1069,6 +1022,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 +1071,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->downKeySym.count(0x2a)) + if (self->leftShiftDown) self->handleKeyRelease(0x2a); - if (self->downKeySym.count(0x36)) + if (self->rightShiftDown) self->handleKeyRelease(0x36); + self->leftShiftDown = false; + self->rightShiftDown = false; } return 1; @@ -1128,31 +1089,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,41 +1130,16 @@ 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)"), (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(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 +1155,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 +1164,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 +1276,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 5f4c1ca7f1..72df22f4be 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -20,8 +20,6 @@ #ifndef __VIEWPORT_H__ #define __VIEWPORT_H__ -#include - #include #include @@ -86,8 +84,9 @@ class Viewport : public Fl_Widget, public EmulateMB { 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,12 +112,12 @@ class Viewport : public Fl_Widget, public EmulateMB { rfb::Point lastPointerPos; uint8_t lastButtonMask; - typedef std::map DownMap; - DownMap downKeySym; - #ifdef WIN32 bool altGrArmed; unsigned int altGrCtrlTime; + + bool leftShiftDown; + bool rightShiftDown; #endif bool firstLEDState;