diff options
author | Pierre Ossman <ossman@cendio.se> | 2024-12-04 17:11:07 +0100 |
---|---|---|
committer | Pierre Ossman <ossman@cendio.se> | 2024-12-04 17:11:07 +0100 |
commit | 994d2c64f8926fe780a2d572183232202145b6a6 (patch) | |
tree | ad63081deeacb09bba7e24bb00e4d5889c3316ba | |
parent | 4680810cbd2d7aaa4d924db361c4c2ab39a3b500 (diff) | |
parent | 7e7e05075bccece85efa92f14f516c065a8fa531 (diff) | |
download | tigervnc-994d2c64f8926fe780a2d572183232202145b6a6.tar.gz tigervnc-994d2c64f8926fe780a2d572183232202145b6a6.zip |
Merge branch 'keysplit' of https://github.com/CendioOssman/tigervnc
-rw-r--r-- | common/rfb/CConnection.cxx | 87 | ||||
-rw-r--r-- | common/rfb/CConnection.h | 20 | ||||
-rw-r--r-- | vncviewer/CMakeLists.txt | 13 | ||||
-rw-r--r-- | vncviewer/Keyboard.h | 49 | ||||
-rw-r--r-- | vncviewer/KeyboardMacOS.h | 60 | ||||
-rw-r--r-- | vncviewer/KeyboardMacOS.mm | 504 | ||||
-rw-r--r-- | vncviewer/KeyboardWin32.cxx | 641 | ||||
-rw-r--r-- | vncviewer/KeyboardWin32.h | 58 | ||||
-rw-r--r-- | vncviewer/KeyboardX11.cxx | 220 | ||||
-rw-r--r-- | vncviewer/KeyboardX11.h | 42 | ||||
-rw-r--r-- | vncviewer/Viewport.cxx | 707 | ||||
-rw-r--r-- | vncviewer/Viewport.h | 29 | ||||
-rw-r--r-- | vncviewer/cocoa.h | 14 | ||||
-rw-r--r-- | vncviewer/cocoa.mm | 401 | ||||
-rw-r--r-- | vncviewer/win32.c | 306 | ||||
-rw-r--r-- | vncviewer/win32.h | 4 |
16 files changed, 1761 insertions, 1394 deletions
diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx index 2c347710..5e7530c8 100644 --- a/common/rfb/CConnection.cxx +++ b/common/rfb/CConnection.cxx @@ -40,6 +40,10 @@ #include <rfb/CConnection.h> #include <rfb/util.h> +#define XK_MISCELLANY +#define XK_XKB_KEYS +#include <rfb/keysymdef.h> + #include <rfb/LogWriter.h> #include <rdr/InStream.h> @@ -695,6 +699,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 9101bf26..07e47e39 100644 --- a/common/rfb/CConnection.h +++ b/common/rfb/CConnection.h @@ -24,6 +24,7 @@ #ifndef __RFB_CCONNECTION_H__ #define __RFB_CCONNECTION_H__ +#include <map> #include <string> #include <rfb/CMsgHandler.h> @@ -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<int, DownKey> DownMap; + DownMap downKeys; }; } #endif 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 <ossman@cendio.se> 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 <stdint.h> + +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 <ossman@cendio.se> 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 <ossman@cendio.se> 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 <config.h> +#endif + +#include <assert.h> + +#import <Cocoa/Cocoa.h> +#import <Carbon/Carbon.h> + +#include <IOKit/hidsystem/IOHIDLib.h> +#include <IOKit/hidsystem/IOHIDParameter.h> + +// 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 <rfb/keysymdef.h> +#include <rfb/XF86keysym.h> +#include <rfb/LogWriter.h> +#include <rfb/ledStates.h> + +#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..1caf4863 --- /dev/null +++ b/vncviewer/KeyboardWin32.cxx @@ -0,0 +1,641 @@ +/* Copyright 2011-2021 Pierre Ossman <ossman@cendio.se> 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 <config.h> +#endif + +#include <windows.h> + +#include <assert.h> + +// Missing in at least some versions of MinGW +#ifndef MAPVK_VK_TO_CHAR +#define MAPVK_VK_TO_CHAR 2 +#endif + +#include <FL/Fl.H> + +#define XK_MISCELLANY +#define XK_XKB_KEYS +#define XK_KOREAN +#include <rfb/keysymdef.h> +#include <rfb/XF86keysym.h> +#include <rfb/LogWriter.h> +#include <rfb/ledStates.h> + +#define NoSymbol 0 + +#include "i18n.h" +#include "keysym2ucs.h" +#include "KeyboardWin32.h" + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(*a)) + +// Used to detect fake input (0xaa is not a real key) +static const WORD SCAN_FAKE = 0xaa; + +static rfb::LogWriter vlog("KeyboardWin32"); + +// Layout independent keys +static const UINT vkey_map[][3] = { + { VK_CANCEL, NoSymbol, XK_Break }, + { VK_BACK, XK_BackSpace, NoSymbol }, + { VK_TAB, XK_Tab, NoSymbol }, + { VK_CLEAR, XK_Clear, NoSymbol }, + { VK_RETURN, XK_Return, XK_KP_Enter }, + { VK_SHIFT, XK_Shift_L, NoSymbol }, + { VK_CONTROL, XK_Control_L, XK_Control_R }, + { VK_MENU, XK_Alt_L, XK_Alt_R }, + { VK_PAUSE, XK_Pause, NoSymbol }, + { VK_CAPITAL, XK_Caps_Lock, NoSymbol }, + { VK_ESCAPE, XK_Escape, NoSymbol }, + { VK_CONVERT, XK_Henkan, NoSymbol }, + { VK_NONCONVERT, XK_Muhenkan, NoSymbol }, + { VK_PRIOR, XK_KP_Prior, XK_Prior }, + { VK_NEXT, XK_KP_Next, XK_Next }, + { VK_END, XK_KP_End, XK_End }, + { VK_HOME, XK_KP_Home, XK_Home }, + { VK_LEFT, XK_KP_Left, XK_Left }, + { VK_UP, XK_KP_Up, XK_Up }, + { VK_RIGHT, XK_KP_Right, XK_Right }, + { VK_DOWN, XK_KP_Down, XK_Down }, + { VK_SNAPSHOT, XK_Sys_Req, XK_Print }, + { VK_INSERT, XK_KP_Insert, XK_Insert }, + { VK_DELETE, XK_KP_Delete, XK_Delete }, + { VK_LWIN, NoSymbol, XK_Super_L }, + { VK_RWIN, NoSymbol, XK_Super_R }, + { VK_APPS, NoSymbol, XK_Menu }, + { VK_SLEEP, NoSymbol, XF86XK_Sleep }, + { VK_NUMPAD0, XK_KP_0, NoSymbol }, + { VK_NUMPAD1, XK_KP_1, NoSymbol }, + { VK_NUMPAD2, XK_KP_2, NoSymbol }, + { VK_NUMPAD3, XK_KP_3, NoSymbol }, + { VK_NUMPAD4, XK_KP_4, NoSymbol }, + { VK_NUMPAD5, XK_KP_5, NoSymbol }, + { VK_NUMPAD6, XK_KP_6, NoSymbol }, + { VK_NUMPAD7, XK_KP_7, NoSymbol }, + { VK_NUMPAD8, XK_KP_8, NoSymbol }, + { VK_NUMPAD9, XK_KP_9, NoSymbol }, + { VK_MULTIPLY, XK_KP_Multiply, NoSymbol }, + { VK_ADD, XK_KP_Add, NoSymbol }, + { VK_SUBTRACT, XK_KP_Subtract, NoSymbol }, + { VK_DIVIDE, NoSymbol, XK_KP_Divide }, + /* VK_SEPARATOR and VK_DECIMAL left out on purpose. See further down. */ + { VK_F1, XK_F1, NoSymbol }, + { VK_F2, XK_F2, NoSymbol }, + { VK_F3, XK_F3, NoSymbol }, + { VK_F4, XK_F4, NoSymbol }, + { VK_F5, XK_F5, NoSymbol }, + { VK_F6, XK_F6, NoSymbol }, + { VK_F7, XK_F7, NoSymbol }, + { VK_F8, XK_F8, NoSymbol }, + { VK_F9, XK_F9, NoSymbol }, + { VK_F10, XK_F10, NoSymbol }, + { VK_F11, XK_F11, NoSymbol }, + { VK_F12, XK_F12, NoSymbol }, + { VK_F13, XK_F13, NoSymbol }, + { VK_F14, XK_F14, NoSymbol }, + { VK_F15, XK_F15, NoSymbol }, + { VK_F16, XK_F16, NoSymbol }, + { VK_F17, XK_F17, NoSymbol }, + { VK_F18, XK_F18, NoSymbol }, + { VK_F19, XK_F19, NoSymbol }, + { VK_F20, XK_F20, NoSymbol }, + { VK_F21, XK_F21, NoSymbol }, + { VK_F22, XK_F22, NoSymbol }, + { VK_F23, XK_F23, NoSymbol }, + { VK_F24, XK_F24, NoSymbol }, + { VK_NUMLOCK, NoSymbol, XK_Num_Lock }, + { VK_SCROLL, XK_Scroll_Lock, NoSymbol }, + { VK_BROWSER_BACK, NoSymbol, XF86XK_Back }, + { VK_BROWSER_FORWARD, NoSymbol, XF86XK_Forward }, + { VK_BROWSER_REFRESH, NoSymbol, XF86XK_Refresh }, + { VK_BROWSER_STOP, NoSymbol, XF86XK_Stop }, + { VK_BROWSER_SEARCH, NoSymbol, XF86XK_Search }, + { VK_BROWSER_FAVORITES, NoSymbol, XF86XK_Favorites }, + { VK_BROWSER_HOME, NoSymbol, XF86XK_HomePage }, + { VK_VOLUME_MUTE, NoSymbol, XF86XK_AudioMute }, + { VK_VOLUME_DOWN, NoSymbol, XF86XK_AudioLowerVolume }, + { VK_VOLUME_UP, NoSymbol, XF86XK_AudioRaiseVolume }, + { VK_MEDIA_NEXT_TRACK, NoSymbol, XF86XK_AudioNext }, + { VK_MEDIA_PREV_TRACK, NoSymbol, XF86XK_AudioPrev }, + { VK_MEDIA_STOP, NoSymbol, XF86XK_AudioStop }, + { VK_MEDIA_PLAY_PAUSE, NoSymbol, XF86XK_AudioPlay }, + { VK_LAUNCH_MAIL, NoSymbol, XF86XK_Mail }, + { VK_LAUNCH_APP2, NoSymbol, XF86XK_Calculator }, +}; + +// Layout dependent keys, but without useful symbols + +// Japanese +static const UINT vkey_map_jp[][3] = { + { VK_KANA, XK_Hiragana_Katakana, NoSymbol }, + { VK_KANJI, XK_Kanji, NoSymbol }, + { VK_OEM_ATTN, XK_Eisu_toggle, NoSymbol }, + { VK_OEM_FINISH, XK_Katakana, NoSymbol }, + { VK_OEM_COPY, XK_Hiragana, NoSymbol }, + // These are really XK_Zenkaku/XK_Hankaku but we have no way of + // keeping the client and server in sync + { VK_OEM_AUTO, XK_Zenkaku_Hankaku, NoSymbol }, + { VK_OEM_ENLW, XK_Zenkaku_Hankaku, NoSymbol }, + { VK_OEM_BACKTAB, XK_Romaji, NoSymbol }, + { VK_ATTN, XK_Romaji, NoSymbol }, +}; + +// Korean +static const UINT vkey_map_ko[][3] = { + { VK_HANGUL, XK_Hangul, NoSymbol }, + { VK_HANJA, XK_Hangul_Hanja, NoSymbol }, +}; + +KeyboardWin32::KeyboardWin32(KeyboardHandler* handler_) + : Keyboard(handler_), cachedHasAltGr(false), currentLayout(nullptr), + altGrArmed(false), leftShiftDown(false), rightShiftDown(false) +{ +} + +KeyboardWin32::~KeyboardWin32() +{ + reset(); +} + +bool KeyboardWin32::handleEvent(const void* event) +{ + MSG *msg = (MSG*)event; + + assert(event); + + if ((msg->message == WM_MOUSEMOVE) || + (msg->message == WM_LBUTTONDOWN) || + (msg->message == WM_LBUTTONUP) || + (msg->message == WM_RBUTTONDOWN) || + (msg->message == WM_RBUTTONUP) || + (msg->message == WM_MBUTTONDOWN) || + (msg->message == WM_MBUTTONUP) || + (msg->message == WM_XBUTTONDOWN) || + (msg->message == WM_XBUTTONUP) || + (msg->message == WM_MOUSEWHEEL) || + (msg->message == WM_MOUSEHWHEEL)) { + // We can't get a mouse event in the middle of an AltGr sequence, so + // abort that detection + if (altGrArmed) + resolveAltGrDetection(false); + return false; // We didn't really consume the mouse event + } else if ((msg->message == WM_KEYDOWN) || (msg->message == WM_SYSKEYDOWN)) { + UINT vKey; + bool isExtended; + int systemKeyCode, keyCode; + uint32_t keySym; + + vKey = msg->wParam; + isExtended = (msg->lParam & (1 << 24)) != 0; + + systemKeyCode = ((msg->lParam >> 16) & 0xff); + + // Windows' touch keyboard doesn't set a scan code for the Alt + // portion of the AltGr sequence, so we need to help it out + if (!isExtended && (systemKeyCode == 0x00) && (vKey == VK_MENU)) { + isExtended = true; + systemKeyCode = 0x38; + } + + // Windows doesn't have a proper AltGr, but handles it using fake + // Ctrl+Alt. However the remote end might not be Windows, so we need + // to merge those in to a single AltGr event. We detect this case + // by seeing the two key events directly after each other with a very + // short time between them (<50ms) and supress the Ctrl event. + if (altGrArmed) { + bool altPressed = isExtended && + (systemKeyCode == 0x38) && + (vKey == VK_MENU) && + ((msg->time - altGrCtrlTime) < 50); + resolveAltGrDetection(altPressed); + } + + if (systemKeyCode == SCAN_FAKE) { + vlog.debug("Ignoring fake key press (virtual key 0x%02x)", vKey); + return true; + } + + // Windows sets the scan code to 0x00 for multimedia keys, so we + // have to do a reverse lookup based on the vKey. + if (systemKeyCode == 0x00) { + systemKeyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC); + if (systemKeyCode == 0x00) { + if (isExtended) + vlog.error(_("No scan code for extended virtual key 0x%02x"), (int)vKey); + else + vlog.error(_("No scan code for virtual key 0x%02x"), (int)vKey); + return true; + } + } + + if (systemKeyCode & ~0x7f) { + vlog.error(_("Invalid scan code 0x%02x"), (int)systemKeyCode); + return true; + } + + if (isExtended) + systemKeyCode |= 0x80; + + keyCode = translateSystemKeyCode(systemKeyCode); + + keySym = translateVKey(vKey, isExtended); + if (keySym == NoSymbol) { + if (isExtended) + vlog.error(_("No symbol for extended virtual key 0x%02x"), (int)vKey); + else + vlog.error(_("No symbol for virtual key 0x%02x"), (int)vKey); + } + + // Windows sends the same vKey for both shifts, so we need to look + // at the scan code to tell them apart + if ((keySym == XK_Shift_L) && (systemKeyCode == 0x36)) + keySym = XK_Shift_R; + + // AltGr handling (see above) + if (hasAltGr()) { + if ((systemKeyCode == 0xb8) && (keySym == XK_Alt_R)) + keySym = XK_ISO_Level3_Shift; + + // Possible start of AltGr sequence? + if ((systemKeyCode == 0x1d) && (keySym == XK_Control_L)) { + altGrArmed = true; + altGrCtrlTime = msg->time; + Fl::add_timeout(0.1, handleAltGrTimeout, this); + return true; + } + } + + handler->handleKeyPress(systemKeyCode, keyCode, keySym); + + // We don't get reliable WM_KEYUP for these + switch (keySym) { + case XK_Zenkaku_Hankaku: + case XK_Eisu_toggle: + case XK_Katakana: + case XK_Hiragana: + case XK_Romaji: + handler->handleKeyRelease(systemKeyCode); + } + + // Shift key tracking, see below + if (systemKeyCode == 0x2a) + leftShiftDown = true; + if (systemKeyCode == 0x36) + rightShiftDown = true; + + return true; + } else if ((msg->message == WM_KEYUP) || (msg->message == WM_SYSKEYUP)) { + UINT vKey; + bool isExtended; + int systemKeyCode, keyCode; + + vKey = msg->wParam; + isExtended = (msg->lParam & (1 << 24)) != 0; + + systemKeyCode = ((msg->lParam >> 16) & 0xff); + + // Touch keyboard AltGr (see above) + if (!isExtended && (systemKeyCode == 0x00) && (vKey == VK_MENU)) { + isExtended = true; + systemKeyCode = 0x38; + } + + // We can't get a release in the middle of an AltGr sequence, so + // abort that detection + if (altGrArmed) + resolveAltGrDetection(false); + + if (systemKeyCode == SCAN_FAKE) { + vlog.debug("Ignoring fake key release (virtual key 0x%02x)", vKey); + return 1; + } + + if (systemKeyCode == 0x00) + systemKeyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC); + if (isExtended) + systemKeyCode |= 0x80; + + keyCode = translateSystemKeyCode(systemKeyCode); + + handler->handleKeyRelease(keyCode); + + // Windows has a rather nasty bug where it won't send key release + // events for a Shift button if the other Shift is still pressed + if ((systemKeyCode == 0x2a) || (systemKeyCode == 0x36)) { + if (leftShiftDown) + handler->handleKeyRelease(0x2a); + if (rightShiftDown) + handler->handleKeyRelease(0x36); + leftShiftDown = false; + rightShiftDown = false; + } + + return true; + } + + return false; +} + +void KeyboardWin32::reset() +{ + altGrArmed = false; + Fl::remove_timeout(handleAltGrTimeout, this); + + leftShiftDown = false; + rightShiftDown = false; +} + +unsigned KeyboardWin32::getLEDState() +{ + unsigned state; + + state = 0; + + if (GetKeyState(VK_CAPITAL) & 0x1) + state |= rfb::ledCapsLock; + if (GetKeyState(VK_NUMLOCK) & 0x1) + state |= rfb::ledNumLock; + if (GetKeyState(VK_SCROLL) & 0x1) + state |= rfb::ledScrollLock; + + return state; +} + +void KeyboardWin32::setLEDState(unsigned state) +{ + INPUT input[6]; + UINT count; + UINT ret; + + memset(input, 0, sizeof(input)); + count = 0; + + if (!!(state & rfb::ledCapsLock) != !!(GetKeyState(VK_CAPITAL) & 0x1)) { + input[count].type = input[count+1].type = INPUT_KEYBOARD; + input[count].ki.wVk = input[count+1].ki.wVk = VK_CAPITAL; + input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE; + input[count].ki.dwFlags = 0; + input[count+1].ki.dwFlags = KEYEVENTF_KEYUP; + count += 2; + } + + if (!!(state & rfb::ledNumLock) != !!(GetKeyState(VK_NUMLOCK) & 0x1)) { + input[count].type = input[count+1].type = INPUT_KEYBOARD; + input[count].ki.wVk = input[count+1].ki.wVk = VK_NUMLOCK; + input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE; + input[count].ki.dwFlags = KEYEVENTF_EXTENDEDKEY; + input[count+1].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_EXTENDEDKEY; + count += 2; + } + + if (!!(state & rfb::ledScrollLock) != !!(GetKeyState(VK_SCROLL) & 0x1)) { + input[count].type = input[count+1].type = INPUT_KEYBOARD; + input[count].ki.wVk = input[count+1].ki.wVk = VK_SCROLL; + input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE; + input[count].ki.dwFlags = 0; + input[count+1].ki.dwFlags = KEYEVENTF_KEYUP; + count += 2; + } + + if (count == 0) + return; + + ret = SendInput(count, input, sizeof(*input)); + if (ret < count) + vlog.error(_("Failed to update keyboard LED state: %lu"), GetLastError()); +} + +uint32_t KeyboardWin32::translateSystemKeyCode(int systemKeyCode) +{ + // Fortunately RFB and Windows use the same scan code set (mostly), + // so there is no conversion needed + // (as long as we encode the extended keys with the high bit) + + // However Pause sends a code that conflicts with NumLock, so use + // the code most RFB implementations use (part of the sequence for + // Ctrl+Pause, i.e. Break) + if (systemKeyCode == 0x45) + return 0xc6; + + // And NumLock incorrectly has the extended bit set + if (systemKeyCode == 0xc5) + return 0x45; + + // And Alt+PrintScreen (i.e. SysRq) sends a different code than + // PrintScreen + if (systemKeyCode == 0xb7) + return 0x54; + + return systemKeyCode; +} + +uint32_t KeyboardWin32::lookupVKeyMap(unsigned vkey, bool extended, + const UINT map[][3], size_t size) +{ + size_t i; + + for (i = 0;i < size;i++) { + if (vkey != map[i][0]) + continue; + + if (extended) + return map[i][2]; + else + return map[i][1]; + } + + return NoSymbol; +} + +uint32_t KeyboardWin32::translateVKey(unsigned vkey, bool extended) +{ + HKL layout; + WORD lang, primary_lang; + + BYTE state[256]; + int ret; + WCHAR wstr[10]; + + // Start with keys that either don't generate a symbol, or + // generate the same symbol as some other key. + + ret = lookupVKeyMap(vkey, extended, vkey_map, ARRAY_SIZE(vkey_map)); + if (ret != NoSymbol) + return ret; + + layout = GetKeyboardLayout(0); + lang = LOWORD(layout); + primary_lang = PRIMARYLANGID(lang); + + if (primary_lang == LANG_JAPANESE) { + ret = lookupVKeyMap(vkey, extended, + vkey_map_jp, ARRAY_SIZE(vkey_map_jp)); + if (ret != NoSymbol) + return ret; + } + + if (primary_lang == LANG_KOREAN) { + ret = lookupVKeyMap(vkey, extended, + vkey_map_ko, ARRAY_SIZE(vkey_map_ko)); + if (ret != NoSymbol) + return ret; + } + + // Windows is not consistent in which virtual key it uses for + // the numpad decimal key, and this is not likely to be fixed: + // http://blogs.msdn.com/michkap/archive/2006/09/13/752377.aspx + // + // To get X11 behaviour, we instead look at the text generated + // by they key. + if ((vkey == VK_DECIMAL) || (vkey == VK_SEPARATOR)) { + UINT ch; + + ch = MapVirtualKey(vkey, MAPVK_VK_TO_CHAR); + switch (ch) { + case ',': + return XK_KP_Separator; + case '.': + return XK_KP_Decimal; + default: + return NoSymbol; + } + } + + // MapVirtualKey() doesn't look at modifiers, so it is + // insufficient for mapping most keys to a symbol. ToUnicode() + // does what we want though. Unfortunately it keeps state, so + // we have to be careful around dead characters. + + GetKeyboardState(state); + + // Pressing Ctrl wreaks havoc with the symbol lookup, so turn + // that off. But AltGr shows up as Ctrl+Alt in Windows, so keep + // Ctrl if Alt is active. + if (!(state[VK_LCONTROL] & 0x80) || !(state[VK_RMENU] & 0x80)) + state[VK_CONTROL] = state[VK_LCONTROL] = state[VK_RCONTROL] = 0; + + // FIXME: Multi character results, like U+0644 U+0627 + // on Arabic layout + ret = ToUnicode(vkey, 0, state, wstr, sizeof(wstr)/sizeof(wstr[0]), 0); + + if (ret == 0) { + // Most Ctrl+Alt combinations will fail to produce a symbol, so + // try it again with Ctrl unconditionally disabled. + state[VK_CONTROL] = state[VK_LCONTROL] = state[VK_RCONTROL] = 0; + ret = ToUnicode(vkey, 0, state, wstr, sizeof(wstr)/sizeof(wstr[0]), 0); + } + + if (ret == 1) + return ucs2keysym(wstr[0]); + + if (ret == -1) { + WCHAR dead_char; + + dead_char = wstr[0]; + + // Need to clear out the state that the dead key has caused. + // This is the recommended method by Microsoft's engineers: + // http://blogs.msdn.com/b/michkap/archive/2007/10/27/5717859.aspx + do { + ret = ToUnicode(vkey, 0, state, wstr, sizeof(wstr)/sizeof(wstr[0]), 0); + } while (ret < 0); + + // Dead keys are represented by their spacing equivalent + // (or something similar depending on the layout) + return ucs2keysym(ucs2combining(dead_char)); + } + + return NoSymbol; +} + +bool KeyboardWin32::hasAltGr() +{ + BYTE origState[256]; + BYTE altGrState[256]; + + if (currentLayout == GetKeyboardLayout(0)) + return cachedHasAltGr; + + // Save current keyboard state so we can get things sane again after + // we're done + if (!GetKeyboardState(origState)) + return 0; + + // We press Ctrl+Alt (Windows fake AltGr) and then test every key + // to see if it produces a printable character. If so then we assume + // AltGr is used in the current layout. + + cachedHasAltGr = false; + + memset(altGrState, 0, sizeof(altGrState)); + altGrState[VK_CONTROL] = 0x80; + altGrState[VK_MENU] = 0x80; + + for (UINT vkey = 0;vkey <= 0xff;vkey++) { + int ret; + WCHAR wstr[10]; + + // Need to skip this one as it is a bit magical and will trigger + // a false positive + if (vkey == VK_PACKET) + continue; + + ret = ToUnicode(vkey, 0, altGrState, wstr, + sizeof(wstr)/sizeof(wstr[0]), 0); + if (ret == 1) { + cachedHasAltGr = true; + break; + } + + if (ret == -1) { + // Dead key, need to clear out state before we proceed + do { + ret = ToUnicode(vkey, 0, altGrState, wstr, + sizeof(wstr)/sizeof(wstr[0]), 0); + } while (ret < 0); + } + } + + SetKeyboardState(origState); + + currentLayout = GetKeyboardLayout(0); + + return cachedHasAltGr; +} + +void KeyboardWin32::handleAltGrTimeout(void *data) +{ + KeyboardWin32 *self = (KeyboardWin32 *)data; + + assert(self); + + self->altGrArmed = false; + self->handler->handleKeyPress(0x1d, 0x1d, XK_Control_L); +} + +void KeyboardWin32::resolveAltGrDetection(bool isAltGrSequence) +{ + altGrArmed = false; + Fl::remove_timeout(handleAltGrTimeout); + // when it's not an AltGr sequence we can't supress the Ctrl anymore + if (!isAltGrSequence) + handler->handleKeyPress(0x1d, 0x1d, XK_Control_L); +} 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 <ossman@cendio.se> 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..0ee1d4f6 --- /dev/null +++ b/vncviewer/KeyboardX11.cxx @@ -0,0 +1,220 @@ +/* Copyright 2011-2021 Pierre Ossman <ossman@cendio.se> 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 <config.h> +#endif + +#include <assert.h> + +#include <stdexcept> + +#include <X11/XKBlib.h> +#include <FL/x.H> + +#include <rfb/LogWriter.h> +#include <rfb/ledStates.h> + +#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 std::runtime_error("XkbGetMap"); + + status = XkbGetNames(fl_display, XkbKeyNamesMask, xkb); + if (status != Success) + throw std::runtime_error("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 <ossman@cendio.se> 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 8f7644c3..8c3b5dc5 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 @@ -27,35 +27,15 @@ #include <rfb/CMsgWriter.h> #include <rfb/LogWriter.h> -#include <rfb/KeysymStr.h> #include <rfb/ledStates.h> #include <rfb/util.h> // FLTK can pull in the X11 headers on some systems #ifndef XK_VoidSymbol -#define XK_LATIN1 #define XK_MISCELLANY -#define XK_XKB_KEYS #include <rfb/keysymdef.h> #endif -#ifndef XF86XK_ModeLock -#include <rfb/XF86keysym.h> -#endif - -#if ! (defined(WIN32) || defined(__APPLE__)) -#include <X11/XKBlib.h> -#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 +44,6 @@ #include "DesktopWindow.h" #include "i18n.h" #include "parameters.h" -#include "keysym2ucs.h" #include "menukey.h" #include "vncviewer.h" @@ -77,28 +56,18 @@ #include <FL/Fl_Menu_Button.H> #include <FL/x.H> -#if !defined(WIN32) && !defined(__APPLE__) -#include <X11/XKBlib.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 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,57 +78,27 @@ 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; +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), -#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 std::runtime_error("XkbGetMap"); - - status = XkbGetNames(fl_display, XkbKeyNamesMask, xkb); - if (status != Success) - throw std::runtime_error("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); @@ -196,9 +135,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); @@ -212,6 +148,8 @@ Viewport::~Viewport() delete cursor; } + delete keyboard; + // FLTK automatically deletes all child widgets, so we shouldn't touch // them ourselves here } @@ -346,90 +284,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() @@ -440,76 +295,29 @@ 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)) { 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); } } @@ -559,7 +367,6 @@ int Viewport::handle(int event) { std::string filtered; int buttonMask, wheelMask; - DownMap::const_iterator iter; switch (event) { case FL_PASTE: @@ -650,9 +457,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; @@ -705,54 +512,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,12 +594,19 @@ void Viewport::handlePointerTimeout(void *data) void Viewport::resetKeyboard() { - while (!downKeySym.empty()) - handleKeyRelease(downKeySym.begin()->first); + try { + cc->releaseAllKeys(); + } catch (std::exception& e) { + vlog.error("%s", e.what()); + abort_connection_with_unexpected_error(e); + } + + keyboard->reset(); } -void Viewport::handleKeyPress(int keyCode, uint32_t keySym) +void Viewport::handleKeyPress(int systemKeyCode, + uint32_t keyCode, uint32_t keySym) { static bool menuRecursion = false; @@ -856,48 +622,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 (std::exception& e) { vlog.error("%s", e.what()); abort_connection_with_unexpected_error(e); @@ -905,346 +631,45 @@ 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 (std::exception& e) { vlog.error("%s", e.what()); abort_connection_with_unexpected_error(e); } - - downKeySym.erase(iter); } 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_XBUTTONDOWN) || - (msg->message == WM_XBUTTONUP) || - (msg->message == WM_MOUSEWHEEL) || - (msg->message == WM_MOUSEHWHEEL)) { - // We can't get a mouse event in the middle of an AltGr sequence, so - // abort that detection - if (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, 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); - } - - 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->downKeySym.count(0x2a)) - self->handleKeyRelease(0x2a); - if (self->downKeySym.count(0x36)) - self->handleKeyRelease(0x36); - } - - 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 keyCode; - - keyCode = cocoa_event_keycode(event); - if ((unsigned)keyCode >= code_map_osx_to_qnum_len) - keyCode = 0; - else - keyCode = code_map_osx_to_qnum[keyCode]; - - if (cocoa_is_key_press(event)) { - uint32_t keySym; - - 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); - - // 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); - } else { - self->handleKeyRelease(keyCode); - } - - 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]; - - // 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); - 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); + 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, 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, XK_Control_L); -} -#endif - // FIXME: gcc confuses ID_DISCONNECT with NULL #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" @@ -1353,30 +778,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 c5222a88..41696d9d 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 <ossman@cendio.se> for Cendio AB + * Copyright 2011-2021 Pierre Ossman <ossman@cendio.se> 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 @@ -20,22 +20,23 @@ #ifndef __VIEWPORT_H__ #define __VIEWPORT_H__ -#include <map> - #include <rfb/Rect.h> #include <FL/Fl_Widget.H> #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_); @@ -75,8 +76,6 @@ protected: private: bool hasFocus(); - unsigned int getModifierMask(unsigned int keysym); - static void handleClipboardChange(int source, void *data); void flushPendingClipboard(); @@ -86,16 +85,12 @@ private: void resetKeyboard(); - void handleKeyPress(int keyCode, uint32_t keySym); - void handleKeyRelease(int keyCode); + void handleKeyPress(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(); @@ -113,13 +108,7 @@ private: rfb::Point lastPointerPos; uint16_t lastButtonMask; - typedef std::map<int, uint32_t> DownMap; - DownMap downKeySym; - -#ifdef WIN32 - bool altGrArmed; - unsigned int altGrCtrlTime; -#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 <FL/x.H> #import <Cocoa/Cocoa.h> -#import <Carbon/Carbon.h> -#include <IOKit/hidsystem/IOHIDLib.h> -#include <IOKit/hidsystem/IOHIDParameter.h> - -#define XK_LATIN1 -#define XK_MISCELLANY -#define XK_XKB_KEYS -#include <rfb/keysymdef.h> -#include <rfb/XF86keysym.h> #include <rfb/Rect.h> -#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 <windows.h> -#include <stdio.h> - -#define XK_MISCELLANY -#define XK_XKB_KEYS -#define XK_KOREAN -#include <rfb/keysymdef.h> -#include <rfb/XF86keysym.h> - -#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 |