From 6b743d0c1bf1fe9e11b0ac2cb51ede39fda87362 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 21 Jul 2014 16:48:43 +0200 Subject: [PATCH] Add OS X keyboard handler --- vncviewer/Viewport.cxx | 35 +++- vncviewer/cocoa.h | 7 + vncviewer/cocoa.mm | 382 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 423 insertions(+), 1 deletion(-) diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index e183939d..027ca54c 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -77,10 +77,15 @@ #include #include +#ifdef __APPLE__ +#include "cocoa.h" +#endif + #ifdef WIN32 #include "win32.h" #endif + using namespace rfb; using namespace rdr; @@ -708,7 +713,35 @@ bool Viewport::handleXEvent(void *event, void *data) return true; } -#elif !defined(__APPLE__) +#elif defined(__APPLE__) + if (cocoa_is_keyboard_event(event)) { + int keyCode; + + keyCode = cocoa_event_keycode(event); + + if (cocoa_is_key_press(event)) { + rdr::U32 keySym; + + keySym = cocoa_event_keysym(event); + if (keySym == NoSymbol) { + vlog.error(_("No symbol for key code 0x%02x (in the current state)"), + (int)keyCode); + return true; + } + + 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 true; + } +#else XEvent *xevent = (XEvent*)event; if (xevent->type == KeyPress) { diff --git a/vncviewer/cocoa.h b/vncviewer/cocoa.h index 87980823..e9101f34 100644 --- a/vncviewer/cocoa.h +++ b/vncviewer/cocoa.h @@ -22,4 +22,11 @@ int cocoa_capture_display(Fl_Window *win, bool all_displays); void cocoa_release_display(Fl_Window *win); +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); + #endif diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm index 2b50ecfe..e9e09683 100644 --- a/vncviewer/cocoa.mm +++ b/vncviewer/cocoa.mm @@ -25,6 +25,17 @@ #include #import +#import + +#define XK_LATIN1 +#define XK_MISCELLANY +#define XK_XKB_KEYS +#include +#include + +#include "keysym2ucs.h" + +#define NoSymbol 0 static bool captured = false; @@ -100,3 +111,374 @@ void cocoa_release_display(Fl_Window *win) if ([nsw level] != newlevel) [nsw setLevel:newlevel]; } + +int cocoa_is_keyboard_event(const void *event) +{ + NSEvent *nsevent; + + nsevent = (NSEvent*)event; + + switch ([nsevent type]) { + case NSKeyDown: + case NSKeyUp: + case NSFlagsChanged: + 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] == 0x39) + return 1; + + // These are entirely undocumented, but I cannot find any other way + // of differentiating between left and right keys + switch ([nsevent keyCode]) { + case 0x36: + mask = 0x0010; + break; + case 0x37: + mask = 0x0008; + break; + case 0x38: + mask = 0x0002; + break; + case 0x39: + // We don't see any event on release of CapsLock + return 1; + case 0x3A: + mask = 0x0020; + break; + case 0x3B: + mask = 0x0001; + break; + case 0x3C: + mask = 0x0004; + break; + case 0x3D: + mask = 0x0040; + break; + case 0x3E: + 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; + + nsevent = (NSEvent*)event; + + return [nsevent keyCode]; +} + +static NSString *key_translate(UInt16 keyCode, UInt32 modifierFlags) +{ + const UCKeyboardLayout *layout; + OSStatus err; + + layout = NULL; + +#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) + TISInputSourceRef keyboard; + CFDataRef uchr; + + keyboard = TISCopyCurrentKeyboardInputSource(); + uchr = (CFDataRef)TISGetInputSourceProperty(keyboard, + kTISPropertyUnicodeKeyLayoutData); + if (uchr == NULL) + return nil; + + layout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr); +#else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 + KeyboardLayoutRef old_layout; + int kind; + + err = KLGetCurrentKeyboardLayout(&old_layout); + if (err != noErr) + return nil; + + err = KLGetKeyboardLayoutProperty(old_layout, kKLKind, + (const void**)&kind); + if (err != noErr) + return nil; + + // Old, crufty layout format? + if (kind == kKLKCHRKind) { + void *kchr_layout; + + UInt32 chars, state; + char buf[3]; + + unichar result[16]; + ByteCount in_len, out_len; + + err = KLGetKeyboardLayoutProperty(old_layout, kKLKCHRData, + (const void**)&kchr_layout); + if (err != noErr) + return nil; + + state = 0; + + keyCode &= 0x7f; + modifierFlags &= 0xff00; + + chars = KeyTranslate(kchr_layout, keyCode | modifierFlags, &state); + + // Dead key? + if (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. + chars = KeyTranslate(kchr_layout, keyCode | modifierFlags, &state); + } + + buf[0] = (chars >> 16) & 0xff; + buf[1] = chars & 0xff; + buf[2] = '\0'; + + if (buf[0] == '\0') { + buf[0] = buf[1]; + buf[1] = '\0'; + } + + // The data is now in some layout specific encoding. Need to convert + // this to unicode. + + ScriptCode script; + TextEncoding encoding; + TECObjectRef converter; + + script = (ScriptCode)GetScriptManagerVariable(smKeyScript); + + err = UpgradeScriptInfoToTextEncoding(script, kTextLanguageDontCare, + kTextRegionDontCare, NULL, + &encoding); + if (err != noErr) + return nil; + + err = TECCreateConverter(&converter, encoding, kTextEncodingUnicodeV4_0); + if (err != noErr) + return nil; + + in_len = strlen(buf); + out_len = sizeof(result); + + err = TECConvertText(converter, (ConstTextPtr)buf, in_len, &in_len, + (TextPtr)result, out_len, &out_len); + + TECDisposeConverter(converter); + + if (err != noErr) + return nil; + + return [NSString stringWithCharacters:result + length:(out_len / sizeof(unichar))]; + } + + if ((kind != kKLKCHRuchrKind) && (kind != kKLuchrKind)) + return nil; + + err = KLGetKeyboardLayoutProperty(old_layout, kKLuchrData, + (const void**)&layout); + if (err != noErr) + return nil; +#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 + + if (layout == NULL) + 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]; +} + +// FIXME: We use hard coded values here as the constants didn't appear +// in the OS X headers until 10.5. +static const int kvk_map[][2] = { + { 0x24, XK_Return }, + { 0x30, XK_Tab }, + { 0x31, XK_space }, + { 0x33, XK_BackSpace }, + { 0x35, XK_Escape }, + // This one is undocumented for unknown reasons + { 0x36, XK_Super_R }, + { 0x37, XK_Super_L }, + { 0x38, XK_Shift_L }, + { 0x39, XK_Caps_Lock }, + { 0x3A, XK_Alt_L }, + { 0x3B, XK_Control_L }, + { 0x3C, XK_Shift_R }, + { 0x3D, XK_Alt_R }, + { 0x3E, XK_Control_R }, + { 0x40, XK_F17 }, + { 0x48, XF86XK_AudioRaiseVolume }, + { 0x49, XF86XK_AudioLowerVolume }, + { 0x4A, XF86XK_AudioMute }, + { 0x4F, XK_F18 }, + { 0x50, XK_F19 }, + { 0x5A, XK_F20 }, + { 0x60, XK_F5 }, + { 0x61, XK_F6 }, + { 0x62, XK_F7 }, + { 0x63, XK_F3 }, + { 0x64, XK_F8 }, + { 0x65, XK_F9 }, + { 0x67, XK_F11 }, + { 0x69, XK_F13 }, + { 0x6A, XK_F16 }, + { 0x6B, XK_F14 }, + { 0x6D, XK_F10 }, + // Also undocumented + { 0x6E, XK_Menu }, + { 0x6F, XK_F12 }, + { 0x71, XK_F15 }, + // Should we send Insert here? + { 0x72, XK_Help }, + { 0x73, XK_Home }, + { 0x74, XK_Page_Up }, + { 0x75, XK_Delete }, + { 0x76, XK_F4 }, + { 0x77, XK_End }, + { 0x78, XK_F2 }, + { 0x79, XK_Page_Down }, + { 0x7A, XK_F1 }, + { 0x7B, XK_Left }, + { 0x7C, XK_Right }, + { 0x7D, XK_Down }, + { 0x7E, XK_Up }, + // The OS X headers claim these keys are not layout independent. + // Could it be because of the state of the decimal key? + /* { 0x41, XK_KP_Decimal }, */ // see below + { 0x43, XK_KP_Multiply }, + { 0x45, XK_KP_Add }, + // OS X doesn't have NumLock, so is this really correct? + { 0x47, XK_Num_Lock }, + { 0x4B, XK_KP_Divide }, + { 0x4C, XK_KP_Enter }, + { 0x4E, XK_KP_Subtract }, + { 0x51, XK_KP_Equal }, + { 0x52, XK_KP_0 }, + { 0x53, XK_KP_1 }, + { 0x54, XK_KP_2 }, + { 0x55, XK_KP_3 }, + { 0x56, XK_KP_4 }, + { 0x57, XK_KP_5 }, + { 0x58, XK_KP_6 }, + { 0x59, XK_KP_7 }, + { 0x5B, XK_KP_8 }, + { 0x5C, XK_KP_9 }, +}; + +int cocoa_event_keysym(const void *event) +{ + NSEvent *nsevent; + + UInt16 key_code; + int 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]); +} -- 2.39.5