]> source.dussan.org Git - tigervnc.git/commitdiff
Add OS X keyboard handler
authorPierre Ossman <ossman@cendio.se>
Mon, 21 Jul 2014 14:48:43 +0000 (16:48 +0200)
committerPierre Ossman <ossman@cendio.se>
Fri, 22 Aug 2014 13:10:28 +0000 (15:10 +0200)
vncviewer/Viewport.cxx
vncviewer/cocoa.h
vncviewer/cocoa.mm

index e183939dfbdb02780778924770a26a356410b566..027ca54ccecb887af8220f26f824b2a6cadde2dd 100644 (file)
 #include <FL/Fl_Menu.H>
 #include <FL/Fl_Menu_Button.H>
 
+#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) {
index 8798082339011e585b5da46b348e23c7ac522f5c..e9101f34ca30e0f87e3a2535b0c2f14f4a3a7a05 100644 (file)
 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
index 2b50ecfea16eef172426bf99ac86cb5e578b8b3c..e9e0968368318c6f16e77676481a08af987417b5 100644 (file)
 #include <FL/x.H>
 
 #import <Cocoa/Cocoa.h>
+#import <Carbon/Carbon.h>
+
+#define XK_LATIN1
+#define XK_MISCELLANY
+#define XK_XKB_KEYS
+#include <rfb/keysymdef.h>
+#include <rfb/XF86keysym.h>
+
+#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]);
+}