diff src/video/cocoa/SDL_cocoakeyboard.m @ 2268:4baee598306d

Date: Thu, 05 Jul 2007 14:02:33 -0700 From: Sam Lantinga Subject: SDL 1.3 keyboard plan After lots of discussion with Christian, this is what we came up with: > So, to sum up... > SDLK_* become the physical keys, starting at > (1<<21) > We create a macro SDLK_INDEX(X) > We have two functions SDL_GetLayoutKey(SDLKey) and SDL_GetKeyName() > SDL_GetLayoutKey maps to UCS4 for printable characters, and SDLK* for non-printable characters > and does so based on the OS's current keyboard layout > SDL_GetKeyName() handles both SDLK_* and UCS4, converting UCS4 to UTF-8 and converting SDLK_* into our names, which are UTF-8 for printable characters. > WASD folks use SDLK_*, and 'I' folks use SDL_GetLayoutKey(SDLK_*) Here is the patch he came up with, and his e-mail about it: Date: Fri, 17 Aug 2007 19:50:28 +0200 From: Christian Walther Subject: Re: SDL 1.3 keyboard plan > Sounds great, go ahead and send me a patch. Here goes! Thanks for having a look. Don't hesitate to comment if anything does not conform to your ideas. One caveat: Committing this now may break compilability of some video drivers - specifically, if they use any of the SDLK_* codes that were obsoleted and moved into SDL_compat.h. I only tried Cocoa (which did break, but is already fixed) and X11 (which didn't, but then its key handling is #iffed out). If that's a problem, it may need to go into a branch. -Christian
author Sam Lantinga <slouken@libsdl.org>
date Sun, 19 Aug 2007 14:52:52 +0000
parents 243bc7ae5a21
children d5a11262f067
line wrap: on
line diff
--- a/src/video/cocoa/SDL_cocoakeyboard.m	Sat Aug 18 05:39:09 2007 +0000
+++ b/src/video/cocoa/SDL_cocoakeyboard.m	Sun Aug 19 14:52:52 2007 +0000
@@ -26,6 +26,8 @@
 
 #include "../../events/SDL_keyboard_c.h"
 
+#include <Carbon/Carbon.h>
+
 
 #ifndef NX_DEVICERCTLKEYMASK
     #define NX_DEVICELCTLKEYMASK    0x00000001
@@ -52,192 +54,6 @@
     #define NX_DEVICERCTLKEYMASK    0x00002000
 #endif
 
-static void
-InitKeymap (SDLKey *keymap)
-{
-    const void *KCHRPtr;
-    UInt32 state;
-    UInt32 value;
-    int i;
-
-    for ( i=0; i<256; ++i )
-        keymap[i] = SDLK_UNKNOWN;
-
-    /* This keymap is almost exactly the same as the OS 9 one */
-    keymap[KEY_ESCAPE] = SDLK_ESCAPE;
-    keymap[KEY_F1] = SDLK_F1;
-    keymap[KEY_F2] = SDLK_F2;
-    keymap[KEY_F3] = SDLK_F3;
-    keymap[KEY_F4] = SDLK_F4;
-    keymap[KEY_F5] = SDLK_F5;
-    keymap[KEY_F6] = SDLK_F6;
-    keymap[KEY_F7] = SDLK_F7;
-    keymap[KEY_F8] = SDLK_F8;
-    keymap[KEY_F9] = SDLK_F9;
-    keymap[KEY_F10] = SDLK_F10;
-    keymap[KEY_F11] = SDLK_F11;
-    keymap[KEY_F12] = SDLK_F12;
-    keymap[KEY_F13] = SDLK_F13;
-    keymap[KEY_F14] = SDLK_F14;
-    keymap[KEY_F15] = SDLK_F15;
-/*
-    keymap[KEY_PRINT] = SDLK_PRINT;
-    keymap[KEY_SCROLLOCK] = SDLK_SCROLLOCK;
-    keymap[KEY_PAUSE] = SDLK_PAUSE;
-*/
-    keymap[KEY_POWER] = SDLK_POWER;
-    keymap[KEY_BACKQUOTE] = SDLK_BACKQUOTE;
-    keymap[KEY_1] = SDLK_1;
-    keymap[KEY_2] = SDLK_2;
-    keymap[KEY_3] = SDLK_3;
-    keymap[KEY_4] = SDLK_4;
-    keymap[KEY_5] = SDLK_5;
-    keymap[KEY_6] = SDLK_6;
-    keymap[KEY_7] = SDLK_7;
-    keymap[KEY_8] = SDLK_8;
-    keymap[KEY_9] = SDLK_9;
-    keymap[KEY_0] = SDLK_0;
-    keymap[KEY_MINUS] = SDLK_MINUS;
-    keymap[KEY_EQUALS] = SDLK_EQUALS;
-    keymap[KEY_BACKSPACE] = SDLK_BACKSPACE;
-    keymap[KEY_INSERT] = SDLK_INSERT;
-    keymap[KEY_HOME] = SDLK_HOME;
-    keymap[KEY_PAGEUP] = SDLK_PAGEUP;
-    keymap[KEY_NUMLOCK] = SDLK_NUMLOCK;
-    keymap[KEY_KP_EQUALS] = SDLK_KP_EQUALS;
-    keymap[KEY_KP_DIVIDE] = SDLK_KP_DIVIDE;
-    keymap[KEY_KP_MULTIPLY] = SDLK_KP_MULTIPLY;
-    keymap[KEY_TAB] = SDLK_TAB;
-    keymap[KEY_q] = SDLK_q;
-    keymap[KEY_w] = SDLK_w;
-    keymap[KEY_e] = SDLK_e;
-    keymap[KEY_r] = SDLK_r;
-    keymap[KEY_t] = SDLK_t;
-    keymap[KEY_y] = SDLK_y;
-    keymap[KEY_u] = SDLK_u;
-    keymap[KEY_i] = SDLK_i;
-    keymap[KEY_o] = SDLK_o;
-    keymap[KEY_p] = SDLK_p;
-    keymap[KEY_LEFTBRACKET] = SDLK_LEFTBRACKET;
-    keymap[KEY_RIGHTBRACKET] = SDLK_RIGHTBRACKET;
-    keymap[KEY_BACKSLASH] = SDLK_BACKSLASH;
-    keymap[KEY_DELETE] = SDLK_DELETE;
-    keymap[KEY_END] = SDLK_END;
-    keymap[KEY_PAGEDOWN] = SDLK_PAGEDOWN;
-    keymap[KEY_KP7] = SDLK_KP7;
-    keymap[KEY_KP8] = SDLK_KP8;
-    keymap[KEY_KP9] = SDLK_KP9;
-    keymap[KEY_KP_MINUS] = SDLK_KP_MINUS;
-    keymap[KEY_CAPSLOCK] = SDLK_CAPSLOCK;
-    keymap[KEY_a] = SDLK_a;
-    keymap[KEY_s] = SDLK_s;
-    keymap[KEY_d] = SDLK_d;
-    keymap[KEY_f] = SDLK_f;
-    keymap[KEY_g] = SDLK_g;
-    keymap[KEY_h] = SDLK_h;
-    keymap[KEY_j] = SDLK_j;
-    keymap[KEY_k] = SDLK_k;
-    keymap[KEY_l] = SDLK_l;
-    keymap[KEY_SEMICOLON] = SDLK_SEMICOLON;
-    keymap[KEY_QUOTE] = SDLK_QUOTE;
-    keymap[KEY_RETURN] = SDLK_RETURN;
-    keymap[KEY_KP4] = SDLK_KP4;
-    keymap[KEY_KP5] = SDLK_KP5;
-    keymap[KEY_KP6] = SDLK_KP6;
-    keymap[KEY_KP_PLUS] = SDLK_KP_PLUS;
-    keymap[KEY_LSHIFT] = SDLK_LSHIFT;
-    keymap[KEY_RSHIFT] = SDLK_RSHIFT;
-    keymap[KEY_z] = SDLK_z;
-    keymap[KEY_x] = SDLK_x;
-    keymap[KEY_c] = SDLK_c;
-    keymap[KEY_v] = SDLK_v;
-    keymap[KEY_b] = SDLK_b;
-    keymap[KEY_n] = SDLK_n;
-    keymap[KEY_m] = SDLK_m;
-    keymap[KEY_COMMA] = SDLK_COMMA;
-    keymap[KEY_PERIOD] = SDLK_PERIOD;
-    keymap[KEY_SLASH] = SDLK_SLASH;
-    keymap[KEY_UP] = SDLK_UP;
-    keymap[KEY_KP1] = SDLK_KP1;
-    keymap[KEY_KP2] = SDLK_KP2;
-    keymap[KEY_KP3] = SDLK_KP3;
-    keymap[KEY_KP_ENTER] = SDLK_KP_ENTER;
-    keymap[KEY_LCTRL] = SDLK_LCTRL;
-    keymap[KEY_LALT] = SDLK_LALT;
-    keymap[KEY_LMETA] = SDLK_LMETA;
-    keymap[KEY_RCTRL] = SDLK_RCTRL;
-    keymap[KEY_RALT] = SDLK_RALT;
-    keymap[KEY_RMETA] = SDLK_RMETA;
-    keymap[KEY_SPACE] = SDLK_SPACE;
-    keymap[KEY_LEFT] = SDLK_LEFT;
-    keymap[KEY_DOWN] = SDLK_DOWN;
-    keymap[KEY_RIGHT] = SDLK_RIGHT;
-    keymap[KEY_KP0] = SDLK_KP0;
-    keymap[KEY_KP_PERIOD] = SDLK_KP_PERIOD;
-    keymap[KEY_IBOOK_ENTER] = SDLK_KP_ENTER;
-    keymap[KEY_IBOOK_RIGHT] = SDLK_RIGHT;
-    keymap[KEY_IBOOK_DOWN] = SDLK_DOWN;
-    keymap[KEY_IBOOK_UP]      = SDLK_UP;
-    keymap[KEY_IBOOK_LEFT] = SDLK_LEFT;
-
-    /* 
-        Up there we setup a static scancode->keysym map. However, it will not
-        work very well on international keyboard. Hence we now query Mac OS X
-        for its own keymap to adjust our own mapping table. However, this is
-        basically only useful for ascii char keys. This is also the reason
-        why we keep the static table, too.
-     */
-
-    /* Get a pointer to the systems cached KCHR */
-    KCHRPtr = (void *)GetScriptManagerVariable(smKCHRCache);
-    if (KCHRPtr) {
-        /* Loop over all 127 possible scan codes */
-        for (i = 0; i < 0x7F; i++) {
-            /* We pretend a clean start to begin with (i.e. no dead keys active */
-            state = 0;
-
-            /* Now translate the key code to a key value */
-            value = KeyTranslate(KCHRPtr, i, &state) & 0xff;
-
-            /* If the state become 0, it was a dead key. We need to translate again,
-                passing in the new state, to get the actual key value */
-            if (state != 0)
-                value = KeyTranslate(KCHRPtr, i, &state) & 0xff;
-
-            /* Now we should have a latin1 value, which are SDL keysyms */
-            if (value >= 32 && value <= 255) {
-                keymap[i] = value;
-            }
-        }
-    }
-
-    /* 
-        The keypad codes are re-setup here, because the loop above cannot
-        distinguish between a key on the keypad and a regular key. We maybe
-        could get around this problem in another fashion: NSEvent's flags
-        include a "NSNumericPadKeyMask" bit; we could check that and modify
-        the symbol we return on the fly. However, this flag seems to exhibit
-        some weird behaviour related to the num lock key
-    */
-    keymap[KEY_KP0] = SDLK_KP0;
-    keymap[KEY_KP1] = SDLK_KP1;
-    keymap[KEY_KP2] = SDLK_KP2;
-    keymap[KEY_KP3] = SDLK_KP3;
-    keymap[KEY_KP4] = SDLK_KP4;
-    keymap[KEY_KP5] = SDLK_KP5;
-    keymap[KEY_KP6] = SDLK_KP6;
-    keymap[KEY_KP7] = SDLK_KP7;
-    keymap[KEY_KP8] = SDLK_KP8;
-    keymap[KEY_KP9] = SDLK_KP9;
-    keymap[KEY_KP_MINUS] = SDLK_KP_MINUS;
-    keymap[KEY_KP_PLUS] = SDLK_KP_PLUS;
-    keymap[KEY_KP_PERIOD] = SDLK_KP_PERIOD;
-    keymap[KEY_KP_EQUALS] = SDLK_KP_EQUALS;
-    keymap[KEY_KP_DIVIDE] = SDLK_KP_DIVIDE;
-    keymap[KEY_KP_MULTIPLY] = SDLK_KP_MULTIPLY;
-    keymap[KEY_KP_ENTER] = SDLK_KP_ENTER;
-}
-
 /* This is the original behavior, before support was added for 
  * differentiating between left and right versions of the keys.
  */
@@ -433,8 +249,8 @@
     newMask = newMods & NSNumericPadKeyMask;
 
     if (oldMask != newMask) {
-        SDL_SendKeyboardKey(keyboard, SDL_PRESSED, (Uint8)scancode, SDLK_NUMLOCK);
-        SDL_SendKeyboardKey(keyboard, SDL_RELEASED, (Uint8)scancode, SDLK_NUMLOCK);
+        SDL_SendKeyboardKey(keyboard, SDL_PRESSED, (Uint8)scancode, SDLK_KP_NUMLOCKCLEAR);
+        SDL_SendKeyboardKey(keyboard, SDL_RELEASED, (Uint8)scancode, SDLK_KP_NUMLOCKCLEAR);
     }
 }
 
@@ -513,14 +329,19 @@
     SDL_Keyboard keyboard;
     NSAutoreleasePool *pool;
 
-    InitKeymap(data->keymap);
-
     pool = [[NSAutoreleasePool alloc] init];
     data->fieldEdit = [[NSTextView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 0.0, 0.0)];
     [pool release];
     
     SDL_zero(keyboard);
     data->keyboard = SDL_AddKeyboard(&keyboard, -1);
+    
+    /* Set our own names for the platform-dependent but layout-independent keys */
+    SDL_SetKeyName(SDLK_KP_NUMLOCKCLEAR, "clear");
+    SDL_SetKeyName(SDLK_LALT, "left option");
+    SDL_SetKeyName(SDLK_LMETA, "left command");
+    SDL_SetKeyName(SDLK_RALT, "right option");
+    SDL_SetKeyName(SDLK_RMETA, "right command");
 }
 
 void
@@ -528,20 +349,33 @@
 {
     SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
     unsigned short scancode = [event keyCode];
+    SDLKey physicalKey;
     const char *text;
 
-    if (scancode >= 256) {
+    if ((scancode == 10 || scancode == 50) && KBGetLayoutType(LMGetKbdType()) == kKeyboardISO) {
+        /* see comments in SDL_cocoakeys.h */
+        scancode = 60 - scancode;
+    }
+    if (scancode < SDL_arraysize(macToSDLKey)) {
+        physicalKey = macToSDLKey[scancode];
+    }
+    else {
         /* Hmm, does this ever happen?  If so, need to extend the keymap... */
-        return;
+        physicalKey = SDLK_UNKNOWN;
     }
 
     switch ([event type]) {
     case NSKeyDown:
         if (![event isARepeat]) {
-            SDL_SendKeyboardKey(data->keyboard, SDL_PRESSED, (Uint8)scancode,
-                                data->keymap[scancode]);
+            SDL_SendKeyboardKey(data->keyboard, SDL_PRESSED, (Uint8)scancode, physicalKey);
+#if 1
+            if (physicalKey == SDLK_UNKNOWN) {
+                fprintf(stderr, "The key you just pressed is not recognized by SDL. To help get this fixed, report this to the SDL mailing list <sdl@libsdl.org> or to Christian Walther <cwalther@gmx.ch>. Mac virtual key code is %d.\n", scancode);
+            }
+#endif
         }
         if (SDL_EventState(SDL_TEXTINPUT, SDL_QUERY)) {
+            /* FIXME CW 2007-08-16: only send those events to the field editor for which we actually want text events, not e.g. esc or function keys. Arrow keys in particular seem to produce crashes sometimes. */
             [data->fieldEdit interpretKeyEvents:[NSArray arrayWithObject:event]];
             text = [[event characters] UTF8String];
             if(text && *text) {
@@ -550,12 +384,136 @@
         }
         break;
     case NSKeyUp:
-        SDL_SendKeyboardKey(data->keyboard, SDL_RELEASED, (Uint8)scancode,
-                            data->keymap[scancode]);
+        SDL_SendKeyboardKey(data->keyboard, SDL_RELEASED, (Uint8)scancode, physicalKey);
         break;
     case NSFlagsChanged:
+        /* FIXME CW 2007-08-14: check if this whole mess that takes up half of this file is really necessary */
         HandleModifiers(_this, scancode, [event modifierFlags]);
         break;
+    default: /* just to avoid compiler warnings */
+        break;
+    }
+}
+
+SDLKey
+Cocoa_GetLayoutKey(_THIS, SDLKey physicalKey)
+{
+    switch (physicalKey) {
+        /* Many of these keys would generate a character in the translation by keyboard layout, but an inappropriate one, so we catch them before. */
+        case SDLK_UNKNOWN:
+        case SDLK_RETURN:
+        case SDLK_ESCAPE:
+        case SDLK_BACKSPACE:
+        case SDLK_TAB:
+        case SDLK_SPACE:
+        case SDLK_CAPSLOCK:
+        case SDLK_F1:
+        case SDLK_F2:
+        case SDLK_F3:
+        case SDLK_F4:
+        case SDLK_F5:
+        case SDLK_F6:
+        case SDLK_F7:
+        case SDLK_F8:
+        case SDLK_F9:
+        case SDLK_F10:
+        case SDLK_F11:
+        case SDLK_F12:
+        case SDLK_PRINTSCREEN:
+        case SDLK_SCROLLLOCK:
+        case SDLK_PAUSE:
+        case SDLK_INSERT:
+        case SDLK_HOME:
+        case SDLK_PAGEUP:
+        case SDLK_DELETE:
+        case SDLK_END:
+        case SDLK_PAGEDOWN:
+        case SDLK_RIGHT:
+        case SDLK_LEFT:
+        case SDLK_DOWN:
+        case SDLK_UP:
+        case SDLK_KP_NUMLOCKCLEAR:
+        case SDLK_KP_ENTER:
+        case SDLK_APPLICATION:
+        case SDLK_POWER:
+        case SDLK_F13:
+        case SDLK_F14:
+        case SDLK_F15:
+        case SDLK_F16:
+        case SDLK_LCTRL:
+        case SDLK_LSHIFT:
+        case SDLK_LALT:
+        case SDLK_LMETA:
+        case SDLK_RCTRL:
+        case SDLK_RSHIFT:
+        case SDLK_RALT:
+        case SDLK_RMETA:
+            return physicalKey;
+        
+        /* For the rest, we try the translation first. */
+        default: {
+            UInt16 vkey = 0;
+            KeyboardLayoutRef layout;
+            KeyboardLayoutKind kind;
+            UInt32 keyboardType = LMGetKbdType();
+            
+            /* Look up pkey to get a Mac virtual key code - linear search isn't terribly efficient, this might have to be optimized. */
+            while (vkey < 128 && physicalKey != macToSDLKey[vkey]) vkey++;
+            if (vkey == 128) return physicalKey;
+            if ((vkey == 10 || vkey == 50) && KBGetLayoutType(keyboardType) == kKeyboardISO) vkey = 60 - vkey; /* see comments in SDL_cocoakeys.h */
+            
+            if (KLGetCurrentKeyboardLayout(&layout) != noErr) return physicalKey;
+            if (KLGetKeyboardLayoutProperty(layout, kKLKind, (const void **)&kind) != noErr) return physicalKey;
+            if (kind == kKLKCHRuchrKind || kind == kKLuchrKind) {
+                UniChar utf16String[4];
+                UInt32 deadKeyState = 0;
+                UniCharCount actualStringLength;
+                const UCKeyboardLayout *uchrData;
+                
+                if (KLGetKeyboardLayoutProperty(layout, kKLuchrData, (const void **)&uchrData) != noErr) return physicalKey;
+                if (UCKeyTranslate(uchrData, vkey, kUCKeyActionDisplay, 0, keyboardType, 0, &deadKeyState, 4, &actualStringLength, utf16String) != noErr) return physicalKey;
+                /* kUCKeyActionDisplay (instead of kUCKeyActionDown) seems to take care of dead keys, so no need to check for that case and simulate a second key press */
+                
+                if (actualStringLength == 0) return physicalKey;
+                
+                /* Decode the first character from UTF-16. I'm not sure if this is appropriate for keyboard layouts that generate more than 1 character, or if we would have to use SDL_KEY_LAYOUT_SPECIAL_BIT in that case. */
+                if (utf16String[0] < 0xD800 || utf16String[0] > 0xDFFF) {
+                    return utf16String[0];
+                }
+                else if (utf16String[0] > 0xDBFF || utf16String[1] < 0xDC00 || utf16String[1] > 0xDFFF) {
+                    /* invalid UTF-16 */
+                    return physicalKey;
+                }
+                else {
+                    return (((utf16String[0] & 0x3FF) << 10) | (utf16String[1] & 0x3FF)) + 0x10000;
+                }
+            }
+            else { /* kind == kKLKCHRKind */
+                const void *kchrData;
+                UInt32 state = 0;
+                UInt8 charCode;
+                SInt32 scriptCode;
+                TextEncoding keyboardEncoding;
+                CFStringRef conversionString;
+                UniChar codepoint;
+                
+                if (KLGetKeyboardLayoutProperty(layout, kKLKCHRData, &kchrData) != noErr) return physicalKey;
+                charCode = KeyTranslate(kchrData, vkey, &state) & 0xFF; /* Actually returns a UInt32 containing two character codes (and two 'reserved' bytes), but we're only interested in the second (or only) one */
+                if (charCode == 0) {
+                    /* It's a dead key, so simulate a second key press */
+                    charCode = KeyTranslate(kchrData, vkey, &state) & 0xFF;
+                    /* Still zero? Give up. */
+                    if (charCode == 0) return physicalKey;
+                }
+                if (KLGetKeyboardLayoutProperty(layout, kKLGroupIdentifier, (const void **)&scriptCode) != noErr) return physicalKey; /* That the group identifier is actually a script code is not documented, but confirmed here: <http://lists.apple.com/archives/carbon-dev/2005/Jan/msg00533.html> */
+                if (UpgradeScriptInfoToTextEncoding(scriptCode, kTextLanguageDontCare, kTextRegionDontCare, NULL, &keyboardEncoding) != noErr) return physicalKey;
+                
+                conversionString = CFStringCreateWithBytes(kCFAllocatorDefault, &charCode, 1, keyboardEncoding, FALSE);
+                codepoint = CFStringGetCharacterAtIndex(conversionString, 0);
+                CFRelease(conversionString);
+                return codepoint;
+            }
+        }
     }
 }