sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_cocoakeyboard.m (19700B)


      1 /*
      2   Simple DirectMedia Layer
      3   Copyright (C) 1997-2020 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 = (unsigned int)[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, (unsigned int)[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: */