| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| #include "../../SDL_internal.h" |
| |
| #if SDL_VIDEO_DRIVER_COCOA |
| |
| #include "SDL_cocoavideo.h" |
| |
| #include "../../events/SDL_events_c.h" |
| #include "../../events/SDL_keyboard_c.h" |
| #include "../../events/scancodes_darwin.h" |
| |
| #include <Carbon/Carbon.h> |
| |
| /*#define DEBUG_IME NSLog */ |
| #define DEBUG_IME(...) |
| |
| @interface SDLTranslatorResponder : NSView <NSTextInputClient> { |
| NSString *_markedText; |
| NSRange _markedRange; |
| NSRange _selectedRange; |
| SDL_Rect _inputRect; |
| } |
| - (void)doCommandBySelector:(SEL)myselector; |
| - (void)setInputRect:(const SDL_Rect *)rect; |
| @end |
| |
| @implementation SDLTranslatorResponder |
| |
| - (void)setInputRect:(const SDL_Rect *)rect |
| { |
| _inputRect = *rect; |
| } |
| |
| - (void)insertText:(id)aString replacementRange:(NSRange)replacementRange |
| { |
| /* TODO: Make use of replacementRange? */ |
| |
| const char *str; |
| |
| DEBUG_IME(@"insertText: %@", aString); |
| |
| /* Could be NSString or NSAttributedString, so we have |
| * to test and convert it before return as SDL event */ |
| if ([aString isKindOfClass: [NSAttributedString class]]) { |
| str = [[aString string] UTF8String]; |
| } else { |
| str = [aString UTF8String]; |
| } |
| |
| /* We're likely sending the composed text, so we reset the IME status. */ |
| if ([self hasMarkedText]) { |
| [self unmarkText]; |
| } |
| |
| SDL_SendKeyboardText(str); |
| } |
| |
| - (void)doCommandBySelector:(SEL)myselector |
| { |
| /* No need to do anything since we are not using Cocoa |
| selectors to handle special keys, instead we use SDL |
| key events to do the same job. |
| */ |
| } |
| |
| - (BOOL)hasMarkedText |
| { |
| return _markedText != nil; |
| } |
| |
| - (NSRange)markedRange |
| { |
| return _markedRange; |
| } |
| |
| - (NSRange)selectedRange |
| { |
| return _selectedRange; |
| } |
| |
| - (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange |
| { |
| if ([aString isKindOfClass:[NSAttributedString class]]) { |
| aString = [aString string]; |
| } |
| |
| if ([aString length] == 0) { |
| [self unmarkText]; |
| return; |
| } |
| |
| if (_markedText != aString) { |
| _markedText = aString; |
| } |
| |
| _selectedRange = selectedRange; |
| _markedRange = NSMakeRange(0, [aString length]); |
| |
| SDL_SendEditingText([aString UTF8String], |
| (int) selectedRange.location, (int) selectedRange.length); |
| |
| DEBUG_IME(@"setMarkedText: %@, (%d, %d)", _markedText, |
| selectedRange.location, selectedRange.length); |
| } |
| |
| - (void)unmarkText |
| { |
| _markedText = nil; |
| |
| SDL_SendEditingText("", 0, 0); |
| } |
| |
| - (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange |
| { |
| NSWindow *window = [self window]; |
| NSRect contentRect = [window contentRectForFrameRect:[window frame]]; |
| float windowHeight = contentRect.size.height; |
| NSRect rect = NSMakeRect(_inputRect.x, windowHeight - _inputRect.y - _inputRect.h, |
| _inputRect.w, _inputRect.h); |
| |
| if (actualRange) { |
| *actualRange = aRange; |
| } |
| |
| DEBUG_IME(@"firstRectForCharacterRange: (%d, %d): windowHeight = %g, rect = %@", |
| aRange.location, aRange.length, windowHeight, |
| NSStringFromRect(rect)); |
| |
| rect = [window convertRectToScreen:rect]; |
| |
| return rect; |
| } |
| |
| - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange |
| { |
| DEBUG_IME(@"attributedSubstringFromRange: (%d, %d)", aRange.location, aRange.length); |
| return nil; |
| } |
| |
| - (NSInteger)conversationIdentifier |
| { |
| return (NSInteger) self; |
| } |
| |
| /* This method returns the index for character that is |
| * nearest to thePoint. thPoint is in screen coordinate system. |
| */ |
| - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint |
| { |
| DEBUG_IME(@"characterIndexForPoint: (%g, %g)", thePoint.x, thePoint.y); |
| return 0; |
| } |
| |
| /* This method is the key to attribute extension. |
| * We could add new attributes through this method. |
| * NSInputServer examines the return value of this |
| * method & constructs appropriate attributed string. |
| */ |
| - (NSArray *)validAttributesForMarkedText |
| { |
| return [NSArray array]; |
| } |
| |
| @end |
| |
| static void |
| HandleModifiers(_THIS, unsigned short scancode, unsigned int modifierFlags) |
| { |
| SDL_Scancode code = darwin_scancode_table[scancode]; |
| |
| const SDL_Scancode codes[] = { |
| SDL_SCANCODE_LSHIFT, |
| SDL_SCANCODE_LCTRL, |
| SDL_SCANCODE_LALT, |
| SDL_SCANCODE_LGUI, |
| SDL_SCANCODE_RSHIFT, |
| SDL_SCANCODE_RCTRL, |
| SDL_SCANCODE_RALT, |
| SDL_SCANCODE_RGUI, |
| SDL_SCANCODE_LSHIFT, |
| SDL_SCANCODE_LCTRL, |
| SDL_SCANCODE_LALT, |
| SDL_SCANCODE_LGUI, }; |
| |
| const unsigned int modifiers[] = { |
| NX_DEVICELSHIFTKEYMASK, |
| NX_DEVICELCTLKEYMASK, |
| NX_DEVICELALTKEYMASK, |
| NX_DEVICELCMDKEYMASK, |
| NX_DEVICERSHIFTKEYMASK, |
| NX_DEVICERCTLKEYMASK, |
| NX_DEVICERALTKEYMASK, |
| NX_DEVICERCMDKEYMASK, |
| NX_SHIFTMASK, |
| NX_CONTROLMASK, |
| NX_ALTERNATEMASK, |
| NX_COMMANDMASK }; |
| |
| for (int i = 0; i < 12; i++) { |
| if (code == codes[i]) { |
| if (modifierFlags & modifiers[i]) { |
| SDL_SendKeyboardKey(SDL_PRESSED, code); |
| } else { |
| SDL_SendKeyboardKey(SDL_RELEASED, code); |
| } |
| } |
| } |
| } |
| |
| static void |
| UpdateKeymap(SDL_VideoData *data, SDL_bool send_event) |
| { |
| TISInputSourceRef key_layout; |
| const void *chr_data; |
| int i; |
| SDL_Scancode scancode; |
| SDL_Keycode keymap[SDL_NUM_SCANCODES]; |
| CFDataRef uchrDataRef; |
| |
| /* See if the keymap needs to be updated */ |
| key_layout = TISCopyCurrentKeyboardLayoutInputSource(); |
| if (key_layout == data.key_layout) { |
| return; |
| } |
| data.key_layout = key_layout; |
| |
| SDL_GetDefaultKeymap(keymap); |
| |
| /* Try Unicode data first */ |
| uchrDataRef = TISGetInputSourceProperty(key_layout, kTISPropertyUnicodeKeyLayoutData); |
| if (uchrDataRef) { |
| chr_data = CFDataGetBytePtr(uchrDataRef); |
| } else { |
| goto cleanup; |
| } |
| |
| if (chr_data) { |
| UInt32 keyboard_type = LMGetKbdType(); |
| OSStatus err; |
| |
| for (i = 0; i < SDL_arraysize(darwin_scancode_table); i++) { |
| UniChar s[8]; |
| UniCharCount len; |
| UInt32 dead_key_state; |
| |
| /* Make sure this scancode is a valid character scancode */ |
| scancode = darwin_scancode_table[i]; |
| if (scancode == SDL_SCANCODE_UNKNOWN || |
| (keymap[scancode] & SDLK_SCANCODE_MASK)) { |
| continue; |
| } |
| |
| dead_key_state = 0; |
| err = UCKeyTranslate ((UCKeyboardLayout *) chr_data, |
| i, kUCKeyActionDown, |
| 0, keyboard_type, |
| kUCKeyTranslateNoDeadKeysMask, |
| &dead_key_state, 8, &len, s); |
| if (err != noErr) { |
| continue; |
| } |
| |
| if (len > 0 && s[0] != 0x10) { |
| keymap[scancode] = s[0]; |
| } |
| } |
| SDL_SetKeymap(0, keymap, SDL_NUM_SCANCODES, send_event); |
| return; |
| } |
| |
| cleanup: |
| CFRelease(key_layout); |
| } |
| |
| void |
| Cocoa_InitKeyboard(_THIS) |
| { |
| SDL_VideoData *data = (__bridge SDL_VideoData *) _this->driverdata; |
| |
| UpdateKeymap(data, SDL_FALSE); |
| |
| /* Set our own names for the platform-dependent but layout-independent keys */ |
| /* This key is NumLock on the MacBook keyboard. :) */ |
| /*SDL_SetScancodeName(SDL_SCANCODE_NUMLOCKCLEAR, "Clear");*/ |
| SDL_SetScancodeName(SDL_SCANCODE_LALT, "Left Option"); |
| SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Command"); |
| SDL_SetScancodeName(SDL_SCANCODE_RALT, "Right Option"); |
| SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Command"); |
| |
| data.modifierFlags = (unsigned int)[NSEvent modifierFlags]; |
| SDL_ToggleModState(KMOD_CAPS, (data.modifierFlags & NSEventModifierFlagCapsLock) != 0); |
| } |
| |
| void |
| Cocoa_StartTextInput(_THIS) |
| { @autoreleasepool |
| { |
| NSView *parentView; |
| SDL_VideoData *data = (__bridge SDL_VideoData *) _this->driverdata; |
| SDL_Window *window = SDL_GetKeyboardFocus(); |
| NSWindow *nswindow = nil; |
| if (window) { |
| nswindow = ((__bridge SDL_WindowData*)window->driverdata).nswindow; |
| } |
| |
| parentView = [nswindow contentView]; |
| |
| /* We only keep one field editor per process, since only the front most |
| * window can receive text input events, so it make no sense to keep more |
| * than one copy. When we switched to another window and requesting for |
| * text input, simply remove the field editor from its superview then add |
| * it to the front most window's content view */ |
| if (!data.fieldEdit) { |
| data.fieldEdit = |
| [[SDLTranslatorResponder alloc] initWithFrame: NSMakeRect(0.0, 0.0, 0.0, 0.0)]; |
| } |
| |
| if (![[data.fieldEdit superview] isEqual:parentView]) { |
| /* DEBUG_IME(@"add fieldEdit to window contentView"); */ |
| [data.fieldEdit removeFromSuperview]; |
| [parentView addSubview: data.fieldEdit]; |
| [nswindow makeFirstResponder: data.fieldEdit]; |
| } |
| }} |
| |
| void |
| Cocoa_StopTextInput(_THIS) |
| { @autoreleasepool |
| { |
| SDL_VideoData *data = (__bridge SDL_VideoData *) _this->driverdata; |
| |
| if (data && data.fieldEdit) { |
| [data.fieldEdit removeFromSuperview]; |
| data.fieldEdit = nil; |
| } |
| }} |
| |
| void |
| Cocoa_SetTextInputRect(_THIS, const SDL_Rect *rect) |
| { |
| SDL_VideoData *data = (__bridge SDL_VideoData *) _this->driverdata; |
| |
| if (!rect) { |
| SDL_InvalidParamError("rect"); |
| return; |
| } |
| |
| [data.fieldEdit setInputRect:rect]; |
| } |
| |
| void |
| Cocoa_HandleKeyEvent(_THIS, NSEvent *event) |
| { |
| unsigned short scancode; |
| SDL_Scancode code; |
| SDL_VideoData *data = _this ? ((__bridge SDL_VideoData *) _this->driverdata) : nil; |
| if (!data) { |
| return; /* can happen when returning from fullscreen Space on shutdown */ |
| } |
| |
| scancode = [event keyCode]; |
| #if 0 |
| const char *text; |
| #endif |
| |
| if ((scancode == 10 || scancode == 50) && KBGetLayoutType(LMGetKbdType()) == kKeyboardISO) { |
| /* see comments in SDL_cocoakeys.h */ |
| scancode = 60 - scancode; |
| } |
| |
| if (scancode < SDL_arraysize(darwin_scancode_table)) { |
| code = darwin_scancode_table[scancode]; |
| } else { |
| /* Hmm, does this ever happen? If so, need to extend the keymap... */ |
| code = SDL_SCANCODE_UNKNOWN; |
| } |
| |
| switch ([event type]) { |
| case NSEventTypeKeyDown: |
| if (![event isARepeat]) { |
| /* See if we need to rebuild the keyboard layout */ |
| UpdateKeymap(data, SDL_TRUE); |
| } |
| |
| SDL_SendKeyboardKey(SDL_PRESSED, code); |
| #ifdef DEBUG_SCANCODES |
| if (code == SDL_SCANCODE_UNKNOWN) { |
| SDL_Log("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); |
| } |
| #endif |
| if (SDL_EventState(SDL_TEXTINPUT, SDL_QUERY)) { |
| /* 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. */ |
| [data.fieldEdit interpretKeyEvents:[NSArray arrayWithObject:event]]; |
| #if 0 |
| text = [[event characters] UTF8String]; |
| if(text && *text) { |
| SDL_SendKeyboardText(text); |
| [data->fieldEdit setString:@""]; |
| } |
| #endif |
| } |
| break; |
| case NSEventTypeKeyUp: |
| SDL_SendKeyboardKey(SDL_RELEASED, code); |
| break; |
| case NSEventTypeFlagsChanged: |
| HandleModifiers(_this, scancode, (unsigned int)[event modifierFlags]); |
| break; |
| default: /* just to avoid compiler warnings */ |
| break; |
| } |
| } |
| |
| void |
| Cocoa_QuitKeyboard(_THIS) |
| { |
| } |
| |
| typedef int CGSConnection; |
| typedef enum { |
| CGSGlobalHotKeyEnable = 0, |
| CGSGlobalHotKeyDisable = 1, |
| } CGSGlobalHotKeyOperatingMode; |
| |
| extern CGSConnection _CGSDefaultConnection(void); |
| extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode); |
| |
| void |
| Cocoa_SetWindowKeyboardGrab(_THIS, SDL_Window * window, SDL_bool grabbed) |
| { |
| #if SDL_MAC_NO_SANDBOX |
| CGSSetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), grabbed ? CGSGlobalHotKeyDisable : CGSGlobalHotKeyEnable); |
| #endif |
| } |
| |
| #endif /* SDL_VIDEO_DRIVER_COCOA */ |
| |
| /* vi: set ts=4 sw=4 expandtab: */ |