cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

SDL_cocoakeyboard.m (19460B)


      1/*
      2  Simple DirectMedia Layer
      3  Copyright (C) 1997-2014 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 */
    179static void
    180HandleNonDeviceModifier(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 */
    203static void
    204HandleModifierOneSide(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 */
    231static void
    232HandleModifierSide(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 */
    275static void
    276ReleaseModifierSide(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 */
    315static void
    316HandleCapsLock(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 */
    333static void
    334DoSidedModifiers(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
    387static void
    388HandleModifiers(_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
    400static void
    401UpdateKeymap(SDL_VideoData *data)
    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        return;
    458    }
    459
    460cleanup:
    461    CFRelease(key_layout);
    462}
    463
    464void
    465Cocoa_InitKeyboard(_THIS)
    466{
    467    SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
    468
    469    UpdateKeymap(data);
    470
    471    /* Set our own names for the platform-dependent but layout-independent keys */
    472    /* This key is NumLock on the MacBook keyboard. :) */
    473    /*SDL_SetScancodeName(SDL_SCANCODE_NUMLOCKCLEAR, "Clear");*/
    474    SDL_SetScancodeName(SDL_SCANCODE_LALT, "Left Option");
    475    SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Command");
    476    SDL_SetScancodeName(SDL_SCANCODE_RALT, "Right Option");
    477    SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Command");
    478}
    479
    480void
    481Cocoa_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
    511void
    512Cocoa_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
    524void
    525Cocoa_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
    537void
    538Cocoa_HandleKeyEvent(_THIS, NSEvent *event)
    539{
    540    SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
    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 NSKeyDown:
    565        if (![event isARepeat]) {
    566            /* See if we need to rebuild the keyboard layout */
    567            UpdateKeymap(data);
    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 mailing list <sdl@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 NSKeyUp:
    589        SDL_SendKeyboardKey(SDL_RELEASED, code);
    590        break;
    591    case NSFlagsChanged:
    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
    600void
    601Cocoa_QuitKeyboard(_THIS)
    602{
    603}
    604
    605#endif /* SDL_VIDEO_DRIVER_COCOA */
    606
    607/* vi: set ts=4 sw=4 expandtab: */