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 */
194static void
195HandleNonDeviceModifier(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 */
218static void
219HandleModifierOneSide(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 */
246static void
247HandleModifierSide(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 */
290static void
291ReleaseModifierSide(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 */
330static void
331DoSidedModifiers(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
381static void
382HandleModifiers(_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
394static void
395UpdateKeymap(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
457cleanup:
458    CFRelease(key_layout);
459}
460
461void
462Cocoa_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
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 = _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
600void
601Cocoa_QuitKeyboard(_THIS)
602{
603}
604
605#endif /* SDL_VIDEO_DRIVER_COCOA */
606
607/* vi: set ts=4 sw=4 expandtab: */
608