src/video/cocoa/SDL_cocoakeyboard.m
author Sam Lantinga <slouken@libsdl.org>
Tue, 27 Oct 2015 11:17:32 -0700
changeset 9898 0da384bef562
parent 9619 b94b6d0bff0f
child 9915 77cf2d1b7215
permissions -rw-r--r--
Add a new SDL_KEYMAPCHANGED SDL event to abstract notification of keyboard layout or input language changes.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2015 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "../../SDL_internal.h"
    22 
    23 #if SDL_VIDEO_DRIVER_COCOA
    24 
    25 #include "SDL_cocoavideo.h"
    26 
    27 #include "../../events/SDL_keyboard_c.h"
    28 #include "../../events/scancodes_darwin.h"
    29 
    30 #include <Carbon/Carbon.h>
    31 
    32 /*#define DEBUG_IME NSLog */
    33 #define DEBUG_IME(...)
    34 
    35 @interface SDLTranslatorResponder : NSView <NSTextInput> {
    36     NSString *_markedText;
    37     NSRange   _markedRange;
    38     NSRange   _selectedRange;
    39     SDL_Rect  _inputRect;
    40 }
    41 - (void) doCommandBySelector:(SEL)myselector;
    42 - (void) setInputRect:(SDL_Rect *) rect;
    43 @end
    44 
    45 @implementation SDLTranslatorResponder
    46 
    47 - (void) setInputRect:(SDL_Rect *) rect
    48 {
    49     _inputRect = *rect;
    50 }
    51 
    52 - (void) insertText:(id) aString
    53 {
    54     const char *str;
    55 
    56     DEBUG_IME(@"insertText: %@", aString);
    57 
    58     /* Could be NSString or NSAttributedString, so we have
    59      * to test and convert it before return as SDL event */
    60     if ([aString isKindOfClass: [NSAttributedString class]]) {
    61         str = [[aString string] UTF8String];
    62     } else {
    63         str = [aString UTF8String];
    64     }
    65 
    66     SDL_SendKeyboardText(str);
    67 }
    68 
    69 - (void) doCommandBySelector:(SEL) myselector
    70 {
    71     /* No need to do anything since we are not using Cocoa
    72        selectors to handle special keys, instead we use SDL
    73        key events to do the same job.
    74     */
    75 }
    76 
    77 - (BOOL) hasMarkedText
    78 {
    79     return _markedText != nil;
    80 }
    81 
    82 - (NSRange) markedRange
    83 {
    84     return _markedRange;
    85 }
    86 
    87 - (NSRange) selectedRange
    88 {
    89     return _selectedRange;
    90 }
    91 
    92 - (void) setMarkedText:(id) aString
    93          selectedRange:(NSRange) selRange
    94 {
    95     if ([aString isKindOfClass: [NSAttributedString class]]) {
    96         aString = [aString string];
    97     }
    98 
    99     if ([aString length] == 0) {
   100         [self unmarkText];
   101         return;
   102     }
   103 
   104     if (_markedText != aString) {
   105         [_markedText release];
   106         _markedText = [aString retain];
   107     }
   108 
   109     _selectedRange = selRange;
   110     _markedRange = NSMakeRange(0, [aString length]);
   111 
   112     SDL_SendEditingText([aString UTF8String],
   113                         selRange.location, selRange.length);
   114 
   115     DEBUG_IME(@"setMarkedText: %@, (%d, %d)", _markedText,
   116           selRange.location, selRange.length);
   117 }
   118 
   119 - (void) unmarkText
   120 {
   121     [_markedText release];
   122     _markedText = nil;
   123 
   124     SDL_SendEditingText("", 0, 0);
   125 }
   126 
   127 - (NSRect) firstRectForCharacterRange: (NSRange) theRange
   128 {
   129     NSWindow *window = [self window];
   130     NSRect contentRect = [window contentRectForFrameRect: [window frame]];
   131     float windowHeight = contentRect.size.height;
   132     NSRect rect = NSMakeRect(_inputRect.x, windowHeight - _inputRect.y - _inputRect.h,
   133                              _inputRect.w, _inputRect.h);
   134 
   135     DEBUG_IME(@"firstRectForCharacterRange: (%d, %d): windowHeight = %g, rect = %@",
   136             theRange.location, theRange.length, windowHeight,
   137             NSStringFromRect(rect));
   138     rect.origin = [[self window] convertBaseToScreen: rect.origin];
   139 
   140     return rect;
   141 }
   142 
   143 - (NSAttributedString *) attributedSubstringFromRange: (NSRange) theRange
   144 {
   145     DEBUG_IME(@"attributedSubstringFromRange: (%d, %d)", theRange.location, theRange.length);
   146     return nil;
   147 }
   148 
   149 - (NSInteger) conversationIdentifier
   150 {
   151     return (NSInteger) self;
   152 }
   153 
   154 /* This method returns the index for character that is
   155  * nearest to thePoint.  thPoint is in screen coordinate system.
   156  */
   157 - (NSUInteger) characterIndexForPoint:(NSPoint) thePoint
   158 {
   159     DEBUG_IME(@"characterIndexForPoint: (%g, %g)", thePoint.x, thePoint.y);
   160     return 0;
   161 }
   162 
   163 /* This method is the key to attribute extension.
   164  * We could add new attributes through this method.
   165  * NSInputServer examines the return value of this
   166  * method & constructs appropriate attributed string.
   167  */
   168 - (NSArray *) validAttributesForMarkedText
   169 {
   170     return [NSArray array];
   171 }
   172 
   173 @end
   174 
   175 /* This is a helper function for HandleModifierSide. This
   176  * function reverts back to behavior before the distinction between
   177  * sides was made.
   178  */
   179 static void
   180 HandleNonDeviceModifier(unsigned int device_independent_mask,
   181                         unsigned int oldMods,
   182                         unsigned int newMods,
   183                         SDL_Scancode scancode)
   184 {
   185     unsigned int oldMask, newMask;
   186 
   187     /* Isolate just the bits we care about in the depedent bits so we can
   188      * figure out what changed
   189      */
   190     oldMask = oldMods & device_independent_mask;
   191     newMask = newMods & device_independent_mask;
   192 
   193     if (oldMask && oldMask != newMask) {
   194         SDL_SendKeyboardKey(SDL_RELEASED, scancode);
   195     } else if (newMask && oldMask != newMask) {
   196         SDL_SendKeyboardKey(SDL_PRESSED, scancode);
   197     }
   198 }
   199 
   200 /* This is a helper function for HandleModifierSide.
   201  * This function sets the actual SDL_PrivateKeyboard event.
   202  */
   203 static void
   204 HandleModifierOneSide(unsigned int oldMods, unsigned int newMods,
   205                       SDL_Scancode scancode,
   206                       unsigned int sided_device_dependent_mask)
   207 {
   208     unsigned int old_dep_mask, new_dep_mask;
   209 
   210     /* Isolate just the bits we care about in the depedent bits so we can
   211      * figure out what changed
   212      */
   213     old_dep_mask = oldMods & sided_device_dependent_mask;
   214     new_dep_mask = newMods & sided_device_dependent_mask;
   215 
   216     /* We now know that this side bit flipped. But we don't know if
   217      * it went pressed to released or released to pressed, so we must
   218      * find out which it is.
   219      */
   220     if (new_dep_mask && old_dep_mask != new_dep_mask) {
   221         SDL_SendKeyboardKey(SDL_PRESSED, scancode);
   222     } else {
   223         SDL_SendKeyboardKey(SDL_RELEASED, scancode);
   224     }
   225 }
   226 
   227 /* This is a helper function for DoSidedModifiers.
   228  * This function will figure out if the modifier key is the left or right side,
   229  * e.g. left-shift vs right-shift.
   230  */
   231 static void
   232 HandleModifierSide(int device_independent_mask,
   233                    unsigned int oldMods, unsigned int newMods,
   234                    SDL_Scancode left_scancode,
   235                    SDL_Scancode right_scancode,
   236                    unsigned int left_device_dependent_mask,
   237                    unsigned int right_device_dependent_mask)
   238 {
   239     unsigned int device_dependent_mask = (left_device_dependent_mask |
   240                                          right_device_dependent_mask);
   241     unsigned int diff_mod;
   242 
   243     /* On the basis that the device independent mask is set, but there are
   244      * no device dependent flags set, we'll assume that we can't detect this
   245      * keyboard and revert to the unsided behavior.
   246      */
   247     if ((device_dependent_mask & newMods) == 0) {
   248         /* Revert to the old behavior */
   249         HandleNonDeviceModifier(device_independent_mask, oldMods, newMods, left_scancode);
   250         return;
   251     }
   252 
   253     /* XOR the previous state against the new state to see if there's a change */
   254     diff_mod = (device_dependent_mask & oldMods) ^
   255                (device_dependent_mask & newMods);
   256     if (diff_mod) {
   257         /* A change in state was found. Isolate the left and right bits
   258          * to handle them separately just in case the values can simulataneously
   259          * change or if the bits don't both exist.
   260          */
   261         if (left_device_dependent_mask & diff_mod) {
   262             HandleModifierOneSide(oldMods, newMods, left_scancode, left_device_dependent_mask);
   263         }
   264         if (right_device_dependent_mask & diff_mod) {
   265             HandleModifierOneSide(oldMods, newMods, right_scancode, right_device_dependent_mask);
   266         }
   267     }
   268 }
   269 
   270 /* This is a helper function for DoSidedModifiers.
   271  * This function will release a key press in the case that
   272  * it is clear that the modifier has been released (i.e. one side
   273  * can't still be down).
   274  */
   275 static void
   276 ReleaseModifierSide(unsigned int device_independent_mask,
   277                     unsigned int oldMods, unsigned int newMods,
   278                     SDL_Scancode left_scancode,
   279                     SDL_Scancode right_scancode,
   280                     unsigned int left_device_dependent_mask,
   281                     unsigned int right_device_dependent_mask)
   282 {
   283     unsigned int device_dependent_mask = (left_device_dependent_mask |
   284                                           right_device_dependent_mask);
   285 
   286     /* On the basis that the device independent mask is set, but there are
   287      * no device dependent flags set, we'll assume that we can't detect this
   288      * keyboard and revert to the unsided behavior.
   289      */
   290     if ((device_dependent_mask & oldMods) == 0) {
   291         /* In this case, we can't detect the keyboard, so use the left side
   292          * to represent both, and release it.
   293          */
   294         SDL_SendKeyboardKey(SDL_RELEASED, left_scancode);
   295         return;
   296     }
   297 
   298     /*
   299      * This could have been done in an if-else case because at this point,
   300      * we know that all keys have been released when calling this function.
   301      * But I'm being paranoid so I want to handle each separately,
   302      * so I hope this doesn't cause other problems.
   303      */
   304     if ( left_device_dependent_mask & oldMods ) {
   305         SDL_SendKeyboardKey(SDL_RELEASED, left_scancode);
   306     }
   307     if ( right_device_dependent_mask & oldMods ) {
   308         SDL_SendKeyboardKey(SDL_RELEASED, right_scancode);
   309     }
   310 }
   311 
   312 /* This is a helper function for DoSidedModifiers.
   313  * This function handles the CapsLock case.
   314  */
   315 static void
   316 HandleCapsLock(unsigned short scancode,
   317                unsigned int oldMods, unsigned int newMods)
   318 {
   319     unsigned int oldMask, newMask;
   320 
   321     oldMask = oldMods & NSAlphaShiftKeyMask;
   322     newMask = newMods & NSAlphaShiftKeyMask;
   323 
   324     if (oldMask != newMask) {
   325         SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_CAPSLOCK);
   326         SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_CAPSLOCK);
   327     }
   328 }
   329 
   330 /* This function will handle the modifier keys and also determine the
   331  * correct side of the key.
   332  */
   333 static void
   334 DoSidedModifiers(unsigned short scancode,
   335                  unsigned int oldMods, unsigned int newMods)
   336 {
   337     /* Set up arrays for the key syms for the left and right side. */
   338     const SDL_Scancode left_mapping[]  = {
   339         SDL_SCANCODE_LSHIFT,
   340         SDL_SCANCODE_LCTRL,
   341         SDL_SCANCODE_LALT,
   342         SDL_SCANCODE_LGUI
   343     };
   344     const SDL_Scancode right_mapping[] = {
   345         SDL_SCANCODE_RSHIFT,
   346         SDL_SCANCODE_RCTRL,
   347         SDL_SCANCODE_RALT,
   348         SDL_SCANCODE_RGUI
   349     };
   350     /* Set up arrays for the device dependent masks with indices that
   351      * correspond to the _mapping arrays
   352      */
   353     const unsigned int left_device_mapping[]  = { NX_DEVICELSHIFTKEYMASK, NX_DEVICELCTLKEYMASK, NX_DEVICELALTKEYMASK, NX_DEVICELCMDKEYMASK };
   354     const unsigned int right_device_mapping[] = { NX_DEVICERSHIFTKEYMASK, NX_DEVICERCTLKEYMASK, NX_DEVICERALTKEYMASK, NX_DEVICERCMDKEYMASK };
   355 
   356     unsigned int i, bit;
   357 
   358     /* Handle CAPSLOCK separately because it doesn't have a left/right side */
   359     HandleCapsLock(scancode, oldMods, newMods);
   360 
   361     /* Iterate through the bits, testing each against the old modifiers */
   362     for (i = 0, bit = NSShiftKeyMask; bit <= NSCommandKeyMask; bit <<= 1, ++i) {
   363         unsigned int oldMask, newMask;
   364 
   365         oldMask = oldMods & bit;
   366         newMask = newMods & bit;
   367 
   368         /* If the bit is set, we must always examine it because the left
   369          * and right side keys may alternate or both may be pressed.
   370          */
   371         if (newMask) {
   372             HandleModifierSide(bit, oldMods, newMods,
   373                                left_mapping[i], right_mapping[i],
   374                                left_device_mapping[i], right_device_mapping[i]);
   375         }
   376         /* If the state changed from pressed to unpressed, we must examine
   377             * the device dependent bits to release the correct keys.
   378             */
   379         else if (oldMask && oldMask != newMask) {
   380             ReleaseModifierSide(bit, oldMods, newMods,
   381                               left_mapping[i], right_mapping[i],
   382                               left_device_mapping[i], right_device_mapping[i]);
   383         }
   384     }
   385 }
   386 
   387 static void
   388 HandleModifiers(_THIS, unsigned short scancode, unsigned int modifierFlags)
   389 {
   390     SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
   391 
   392     if (modifierFlags == data->modifierFlags) {
   393         return;
   394     }
   395 
   396     DoSidedModifiers(scancode, data->modifierFlags, modifierFlags);
   397     data->modifierFlags = modifierFlags;
   398 }
   399 
   400 static void
   401 UpdateKeymap(SDL_VideoData *data, SDL_bool send_event)
   402 {
   403     TISInputSourceRef key_layout;
   404     const void *chr_data;
   405     int i;
   406     SDL_Scancode scancode;
   407     SDL_Keycode keymap[SDL_NUM_SCANCODES];
   408 
   409     /* See if the keymap needs to be updated */
   410     key_layout = TISCopyCurrentKeyboardLayoutInputSource();
   411     if (key_layout == data->key_layout) {
   412         return;
   413     }
   414     data->key_layout = key_layout;
   415 
   416     SDL_GetDefaultKeymap(keymap);
   417 
   418     /* Try Unicode data first */
   419     CFDataRef uchrDataRef = TISGetInputSourceProperty(key_layout, kTISPropertyUnicodeKeyLayoutData);
   420     if (uchrDataRef) {
   421         chr_data = CFDataGetBytePtr(uchrDataRef);
   422     } else {
   423         goto cleanup;
   424     }
   425 
   426     if (chr_data) {
   427         UInt32 keyboard_type = LMGetKbdType();
   428         OSStatus err;
   429 
   430         for (i = 0; i < SDL_arraysize(darwin_scancode_table); i++) {
   431             UniChar s[8];
   432             UniCharCount len;
   433             UInt32 dead_key_state;
   434 
   435             /* Make sure this scancode is a valid character scancode */
   436             scancode = darwin_scancode_table[i];
   437             if (scancode == SDL_SCANCODE_UNKNOWN ||
   438                 (keymap[scancode] & SDLK_SCANCODE_MASK)) {
   439                 continue;
   440             }
   441 
   442             dead_key_state = 0;
   443             err = UCKeyTranslate ((UCKeyboardLayout *) chr_data,
   444                                   i, kUCKeyActionDown,
   445                                   0, keyboard_type,
   446                                   kUCKeyTranslateNoDeadKeysMask,
   447                                   &dead_key_state, 8, &len, s);
   448             if (err != noErr) {
   449                 continue;
   450             }
   451 
   452             if (len > 0 && s[0] != 0x10) {
   453                 keymap[scancode] = s[0];
   454             }
   455         }
   456         SDL_SetKeymap(0, keymap, SDL_NUM_SCANCODES);
   457         if (send_event) {
   458             SDL_SendKeymapChangedEvent();
   459         }
   460         return;
   461     }
   462 
   463 cleanup:
   464     CFRelease(key_layout);
   465 }
   466 
   467 void
   468 Cocoa_InitKeyboard(_THIS)
   469 {
   470     SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
   471 
   472     UpdateKeymap(data, SDL_FALSE);
   473 
   474     /* Set our own names for the platform-dependent but layout-independent keys */
   475     /* This key is NumLock on the MacBook keyboard. :) */
   476     /*SDL_SetScancodeName(SDL_SCANCODE_NUMLOCKCLEAR, "Clear");*/
   477     SDL_SetScancodeName(SDL_SCANCODE_LALT, "Left Option");
   478     SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Command");
   479     SDL_SetScancodeName(SDL_SCANCODE_RALT, "Right Option");
   480     SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Command");
   481 }
   482 
   483 void
   484 Cocoa_StartTextInput(_THIS)
   485 { @autoreleasepool
   486 {
   487     SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
   488     SDL_Window *window = SDL_GetKeyboardFocus();
   489     NSWindow *nswindow = nil;
   490     if (window) {
   491         nswindow = ((SDL_WindowData*)window->driverdata)->nswindow;
   492     }
   493 
   494     NSView *parentView = [nswindow contentView];
   495 
   496     /* We only keep one field editor per process, since only the front most
   497      * window can receive text input events, so it make no sense to keep more
   498      * than one copy. When we switched to another window and requesting for
   499      * text input, simply remove the field editor from its superview then add
   500      * it to the front most window's content view */
   501     if (!data->fieldEdit) {
   502         data->fieldEdit =
   503             [[SDLTranslatorResponder alloc] initWithFrame: NSMakeRect(0.0, 0.0, 0.0, 0.0)];
   504     }
   505 
   506     if (![[data->fieldEdit superview] isEqual: parentView]) {
   507         /* DEBUG_IME(@"add fieldEdit to window contentView"); */
   508         [data->fieldEdit removeFromSuperview];
   509         [parentView addSubview: data->fieldEdit];
   510         [nswindow makeFirstResponder: data->fieldEdit];
   511     }
   512 }}
   513 
   514 void
   515 Cocoa_StopTextInput(_THIS)
   516 { @autoreleasepool
   517 {
   518     SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
   519 
   520     if (data && data->fieldEdit) {
   521         [data->fieldEdit removeFromSuperview];
   522         [data->fieldEdit release];
   523         data->fieldEdit = nil;
   524     }
   525 }}
   526 
   527 void
   528 Cocoa_SetTextInputRect(_THIS, SDL_Rect *rect)
   529 {
   530     SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
   531 
   532     if (!rect) {
   533         SDL_InvalidParamError("rect");
   534         return;
   535     }
   536 
   537     [data->fieldEdit setInputRect: rect];
   538 }
   539 
   540 void
   541 Cocoa_HandleKeyEvent(_THIS, NSEvent *event)
   542 {
   543     SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
   544     if (!data) {
   545         return;  /* can happen when returning from fullscreen Space on shutdown */
   546     }
   547 
   548     unsigned short scancode = [event keyCode];
   549     SDL_Scancode code;
   550 #if 0
   551     const char *text;
   552 #endif
   553 
   554     if ((scancode == 10 || scancode == 50) && KBGetLayoutType(LMGetKbdType()) == kKeyboardISO) {
   555         /* see comments in SDL_cocoakeys.h */
   556         scancode = 60 - scancode;
   557     }
   558 
   559     if (scancode < SDL_arraysize(darwin_scancode_table)) {
   560         code = darwin_scancode_table[scancode];
   561     } else {
   562         /* Hmm, does this ever happen?  If so, need to extend the keymap... */
   563         code = SDL_SCANCODE_UNKNOWN;
   564     }
   565 
   566     switch ([event type]) {
   567     case NSKeyDown:
   568         if (![event isARepeat]) {
   569             /* See if we need to rebuild the keyboard layout */
   570             UpdateKeymap(data, SDL_TRUE);
   571         }
   572 
   573         SDL_SendKeyboardKey(SDL_PRESSED, code);
   574 #if 1
   575         if (code == SDL_SCANCODE_UNKNOWN) {
   576             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);
   577         }
   578 #endif
   579         if (SDL_EventState(SDL_TEXTINPUT, SDL_QUERY)) {
   580             /* 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. */
   581             [data->fieldEdit interpretKeyEvents:[NSArray arrayWithObject:event]];
   582 #if 0
   583             text = [[event characters] UTF8String];
   584             if(text && *text) {
   585                 SDL_SendKeyboardText(text);
   586                 [data->fieldEdit setString:@""];
   587             }
   588 #endif
   589         }
   590         break;
   591     case NSKeyUp:
   592         SDL_SendKeyboardKey(SDL_RELEASED, code);
   593         break;
   594     case NSFlagsChanged:
   595         /* FIXME CW 2007-08-14: check if this whole mess that takes up half of this file is really necessary */
   596         HandleModifiers(_this, scancode, [event modifierFlags]);
   597         break;
   598     default: /* just to avoid compiler warnings */
   599         break;
   600     }
   601 }
   602 
   603 void
   604 Cocoa_QuitKeyboard(_THIS)
   605 {
   606 }
   607 
   608 #endif /* SDL_VIDEO_DRIVER_COCOA */
   609 
   610 /* vi: set ts=4 sw=4 expandtab: */