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