diff options
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoakeyboard.m')
| -rw-r--r-- | contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoakeyboard.m | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoakeyboard.m b/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoakeyboard.m new file mode 100644 index 0000000..e458be9 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoakeyboard.m | |||
| @@ -0,0 +1,604 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 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 | #ifdef 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 | #if 0 | ||
| 34 | #define DEBUG_IME NSLog | ||
| 35 | #else | ||
| 36 | #define DEBUG_IME(...) | ||
| 37 | #endif | ||
| 38 | |||
| 39 | @interface SDL3TranslatorResponder : NSView <NSTextInputClient> | ||
| 40 | { | ||
| 41 | NSString *_markedText; | ||
| 42 | NSRange _markedRange; | ||
| 43 | NSRange _selectedRange; | ||
| 44 | SDL_Rect _inputRect; | ||
| 45 | int _pendingRawCode; | ||
| 46 | SDL_Scancode _pendingScancode; | ||
| 47 | Uint64 _pendingTimestamp; | ||
| 48 | } | ||
| 49 | - (void)doCommandBySelector:(SEL)myselector; | ||
| 50 | - (void)setInputRect:(const SDL_Rect *)rect; | ||
| 51 | - (void)setPendingKey:(int)rawcode scancode:(SDL_Scancode)scancode timestamp:(Uint64)timestamp; | ||
| 52 | - (void)sendPendingKey; | ||
| 53 | - (void)clearPendingKey; | ||
| 54 | @end | ||
| 55 | |||
| 56 | @implementation SDL3TranslatorResponder | ||
| 57 | |||
| 58 | - (void)setInputRect:(const SDL_Rect *)rect | ||
| 59 | { | ||
| 60 | SDL_copyp(&_inputRect, rect); | ||
| 61 | } | ||
| 62 | |||
| 63 | - (void)insertText:(id)aString replacementRange:(NSRange)replacementRange | ||
| 64 | { | ||
| 65 | const char *str; | ||
| 66 | |||
| 67 | DEBUG_IME(@"insertText: %@ replacementRange: (%d, %d)", aString, | ||
| 68 | (int)replacementRange.location, (int)replacementRange.length); | ||
| 69 | |||
| 70 | /* Could be NSString or NSAttributedString, so we have | ||
| 71 | * to test and convert it before return as SDL event */ | ||
| 72 | if ([aString isKindOfClass:[NSAttributedString class]]) { | ||
| 73 | str = [[aString string] UTF8String]; | ||
| 74 | } else { | ||
| 75 | str = [aString UTF8String]; | ||
| 76 | } | ||
| 77 | |||
| 78 | // We're likely sending the composed text, so we reset the IME status. | ||
| 79 | if ([self hasMarkedText]) { | ||
| 80 | [self unmarkText]; | ||
| 81 | } | ||
| 82 | |||
| 83 | // Deliver the raw key event that generated this text | ||
| 84 | [self sendPendingKey]; | ||
| 85 | |||
| 86 | if ((int)replacementRange.location != -1) { | ||
| 87 | // We're replacing the last character | ||
| 88 | SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_BACKSPACE, true); | ||
| 89 | SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_BACKSPACE, false); | ||
| 90 | } | ||
| 91 | |||
| 92 | SDL_SendKeyboardText(str); | ||
| 93 | } | ||
| 94 | |||
| 95 | - (void)doCommandBySelector:(SEL)myselector | ||
| 96 | { | ||
| 97 | /* No need to do anything since we are not using Cocoa | ||
| 98 | selectors to handle special keys, instead we use SDL | ||
| 99 | key events to do the same job. | ||
| 100 | */ | ||
| 101 | } | ||
| 102 | |||
| 103 | - (BOOL)hasMarkedText | ||
| 104 | { | ||
| 105 | return _markedText != nil; | ||
| 106 | } | ||
| 107 | |||
| 108 | - (NSRange)markedRange | ||
| 109 | { | ||
| 110 | return _markedRange; | ||
| 111 | } | ||
| 112 | |||
| 113 | - (NSRange)selectedRange | ||
| 114 | { | ||
| 115 | return _selectedRange; | ||
| 116 | } | ||
| 117 | |||
| 118 | - (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange | ||
| 119 | { | ||
| 120 | if ([aString isKindOfClass:[NSAttributedString class]]) { | ||
| 121 | aString = [aString string]; | ||
| 122 | } | ||
| 123 | |||
| 124 | if ([aString length] == 0) { | ||
| 125 | [self unmarkText]; | ||
| 126 | return; | ||
| 127 | } | ||
| 128 | |||
| 129 | if (_markedText != aString) { | ||
| 130 | _markedText = aString; | ||
| 131 | } | ||
| 132 | |||
| 133 | _selectedRange = selectedRange; | ||
| 134 | _markedRange = NSMakeRange(0, [aString length]); | ||
| 135 | |||
| 136 | // This key event was consumed by the IME | ||
| 137 | [self clearPendingKey]; | ||
| 138 | |||
| 139 | NSUInteger utf32SelectedRangeLocation = [[aString substringToIndex:selectedRange.location] lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4; | ||
| 140 | NSUInteger utf32SelectionRangeEnd = [[aString substringToIndex:(selectedRange.location + selectedRange.length)] lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4; | ||
| 141 | NSUInteger utf32SelectionRangeLength = utf32SelectionRangeEnd - utf32SelectedRangeLocation; | ||
| 142 | |||
| 143 | SDL_SendEditingText([aString UTF8String], | ||
| 144 | (int)utf32SelectedRangeLocation, (int)utf32SelectionRangeLength); | ||
| 145 | |||
| 146 | DEBUG_IME(@"setMarkedText: %@, (%d, %d) replacement range (%d, %d)", _markedText, | ||
| 147 | (int)selectedRange.location, (int)selectedRange.length, | ||
| 148 | (int)replacementRange.location, (int)replacementRange.length); | ||
| 149 | } | ||
| 150 | |||
| 151 | - (void)unmarkText | ||
| 152 | { | ||
| 153 | _markedText = nil; | ||
| 154 | |||
| 155 | // This key event was consumed by the IME | ||
| 156 | [self clearPendingKey]; | ||
| 157 | |||
| 158 | SDL_SendEditingText("", 0, 0); | ||
| 159 | } | ||
| 160 | |||
| 161 | - (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange | ||
| 162 | { | ||
| 163 | NSWindow *window = [self window]; | ||
| 164 | NSRect contentRect = [window contentRectForFrameRect:[window frame]]; | ||
| 165 | float windowHeight = contentRect.size.height; | ||
| 166 | NSRect rect = NSMakeRect(_inputRect.x, windowHeight - _inputRect.y - _inputRect.h, | ||
| 167 | _inputRect.w, _inputRect.h); | ||
| 168 | |||
| 169 | if (actualRange) { | ||
| 170 | *actualRange = aRange; | ||
| 171 | } | ||
| 172 | |||
| 173 | DEBUG_IME(@"firstRectForCharacterRange: (%d, %d): windowHeight = %g, rect = %@", | ||
| 174 | (int)aRange.location, (int)aRange.length, windowHeight, | ||
| 175 | NSStringFromRect(rect)); | ||
| 176 | |||
| 177 | rect = [window convertRectToScreen:rect]; | ||
| 178 | |||
| 179 | return rect; | ||
| 180 | } | ||
| 181 | |||
| 182 | - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange | ||
| 183 | { | ||
| 184 | DEBUG_IME(@"attributedSubstringFromRange: (%d, %d)", (int)aRange.location, (int)aRange.length); | ||
| 185 | return nil; | ||
| 186 | } | ||
| 187 | |||
| 188 | - (NSInteger)conversationIdentifier | ||
| 189 | { | ||
| 190 | return (NSInteger)self; | ||
| 191 | } | ||
| 192 | |||
| 193 | /* This method returns the index for character that is | ||
| 194 | * nearest to thePoint. thPoint is in screen coordinate system. | ||
| 195 | */ | ||
| 196 | - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint | ||
| 197 | { | ||
| 198 | DEBUG_IME(@"characterIndexForPoint: (%g, %g)", thePoint.x, thePoint.y); | ||
| 199 | return 0; | ||
| 200 | } | ||
| 201 | |||
| 202 | /* This method is the key to attribute extension. | ||
| 203 | * We could add new attributes through this method. | ||
| 204 | * NSInputServer examines the return value of this | ||
| 205 | * method & constructs appropriate attributed string. | ||
| 206 | */ | ||
| 207 | - (NSArray *)validAttributesForMarkedText | ||
| 208 | { | ||
| 209 | return [NSArray array]; | ||
| 210 | } | ||
| 211 | |||
| 212 | - (void)setPendingKey:(int)rawcode scancode:(SDL_Scancode)scancode timestamp:(Uint64)timestamp | ||
| 213 | { | ||
| 214 | _pendingRawCode = rawcode; | ||
| 215 | _pendingScancode = scancode; | ||
| 216 | _pendingTimestamp = timestamp; | ||
| 217 | } | ||
| 218 | |||
| 219 | - (void)sendPendingKey | ||
| 220 | { | ||
| 221 | if (_pendingRawCode < 0) { | ||
| 222 | return; | ||
| 223 | } | ||
| 224 | |||
| 225 | SDL_SendKeyboardKey(_pendingTimestamp, SDL_DEFAULT_KEYBOARD_ID, _pendingRawCode, _pendingScancode, true); | ||
| 226 | [self clearPendingKey]; | ||
| 227 | } | ||
| 228 | |||
| 229 | - (void)clearPendingKey | ||
| 230 | { | ||
| 231 | _pendingRawCode = -1; | ||
| 232 | } | ||
| 233 | |||
| 234 | @end | ||
| 235 | |||
| 236 | static bool IsModifierKeyPressed(unsigned int flags, | ||
| 237 | unsigned int target_mask, | ||
| 238 | unsigned int other_mask, | ||
| 239 | unsigned int either_mask) | ||
| 240 | { | ||
| 241 | bool target_pressed = (flags & target_mask) != 0; | ||
| 242 | bool other_pressed = (flags & other_mask) != 0; | ||
| 243 | bool either_pressed = (flags & either_mask) != 0; | ||
| 244 | |||
| 245 | if (either_pressed != (target_pressed || other_pressed)) | ||
| 246 | return either_pressed; | ||
| 247 | |||
| 248 | return target_pressed; | ||
| 249 | } | ||
| 250 | |||
| 251 | static void HandleModifiers(SDL_VideoDevice *_this, SDL_Scancode code, unsigned int modifierFlags) | ||
| 252 | { | ||
| 253 | bool pressed = false; | ||
| 254 | |||
| 255 | if (code == SDL_SCANCODE_LSHIFT) { | ||
| 256 | pressed = IsModifierKeyPressed(modifierFlags, NX_DEVICELSHIFTKEYMASK, | ||
| 257 | NX_DEVICERSHIFTKEYMASK, NX_SHIFTMASK); | ||
| 258 | } else if (code == SDL_SCANCODE_LCTRL) { | ||
| 259 | pressed = IsModifierKeyPressed(modifierFlags, NX_DEVICELCTLKEYMASK, | ||
| 260 | NX_DEVICERCTLKEYMASK, NX_CONTROLMASK); | ||
| 261 | } else if (code == SDL_SCANCODE_LALT) { | ||
| 262 | pressed = IsModifierKeyPressed(modifierFlags, NX_DEVICELALTKEYMASK, | ||
| 263 | NX_DEVICERALTKEYMASK, NX_ALTERNATEMASK); | ||
| 264 | } else if (code == SDL_SCANCODE_LGUI) { | ||
| 265 | pressed = IsModifierKeyPressed(modifierFlags, NX_DEVICELCMDKEYMASK, | ||
| 266 | NX_DEVICERCMDKEYMASK, NX_COMMANDMASK); | ||
| 267 | } else if (code == SDL_SCANCODE_RSHIFT) { | ||
| 268 | pressed = IsModifierKeyPressed(modifierFlags, NX_DEVICERSHIFTKEYMASK, | ||
| 269 | NX_DEVICELSHIFTKEYMASK, NX_SHIFTMASK); | ||
| 270 | } else if (code == SDL_SCANCODE_RCTRL) { | ||
| 271 | pressed = IsModifierKeyPressed(modifierFlags, NX_DEVICERCTLKEYMASK, | ||
| 272 | NX_DEVICELCTLKEYMASK, NX_CONTROLMASK); | ||
| 273 | } else if (code == SDL_SCANCODE_RALT) { | ||
| 274 | pressed = IsModifierKeyPressed(modifierFlags, NX_DEVICERALTKEYMASK, | ||
| 275 | NX_DEVICELALTKEYMASK, NX_ALTERNATEMASK); | ||
| 276 | } else if (code == SDL_SCANCODE_RGUI) { | ||
| 277 | pressed = IsModifierKeyPressed(modifierFlags, NX_DEVICERCMDKEYMASK, | ||
| 278 | NX_DEVICELCMDKEYMASK, NX_COMMANDMASK); | ||
| 279 | } else { | ||
| 280 | return; | ||
| 281 | } | ||
| 282 | |||
| 283 | if (pressed) { | ||
| 284 | SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, 0, code, true); | ||
| 285 | } else { | ||
| 286 | SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, 0, code, false); | ||
| 287 | } | ||
| 288 | } | ||
| 289 | |||
| 290 | static void UpdateKeymap(SDL_CocoaVideoData *data, bool send_event) | ||
| 291 | { | ||
| 292 | TISInputSourceRef key_layout; | ||
| 293 | UCKeyboardLayout *keyLayoutPtr = NULL; | ||
| 294 | CFDataRef uchrDataRef; | ||
| 295 | |||
| 296 | // See if the keymap needs to be updated | ||
| 297 | key_layout = TISCopyCurrentKeyboardLayoutInputSource(); | ||
| 298 | if (key_layout == data.key_layout) { | ||
| 299 | return; | ||
| 300 | } | ||
| 301 | data.key_layout = key_layout; | ||
| 302 | |||
| 303 | // Try Unicode data first | ||
| 304 | uchrDataRef = TISGetInputSourceProperty(key_layout, kTISPropertyUnicodeKeyLayoutData); | ||
| 305 | if (uchrDataRef) { | ||
| 306 | keyLayoutPtr = (UCKeyboardLayout *)CFDataGetBytePtr(uchrDataRef); | ||
| 307 | } | ||
| 308 | |||
| 309 | if (!keyLayoutPtr) { | ||
| 310 | CFRelease(key_layout); | ||
| 311 | return; | ||
| 312 | } | ||
| 313 | |||
| 314 | static struct { | ||
| 315 | int flags; | ||
| 316 | SDL_Keymod modstate; | ||
| 317 | } mods[] = { | ||
| 318 | { 0, SDL_KMOD_NONE }, | ||
| 319 | { shiftKey, SDL_KMOD_SHIFT }, | ||
| 320 | { alphaLock, SDL_KMOD_CAPS }, | ||
| 321 | { (shiftKey | alphaLock), (SDL_KMOD_SHIFT | SDL_KMOD_CAPS) }, | ||
| 322 | { optionKey, SDL_KMOD_ALT }, | ||
| 323 | { (optionKey | shiftKey), (SDL_KMOD_ALT | SDL_KMOD_SHIFT) }, | ||
| 324 | { (optionKey | alphaLock), (SDL_KMOD_ALT | SDL_KMOD_CAPS) }, | ||
| 325 | { (optionKey | shiftKey | alphaLock), (SDL_KMOD_ALT | SDL_KMOD_SHIFT | SDL_KMOD_CAPS) } | ||
| 326 | }; | ||
| 327 | |||
| 328 | UInt32 keyboard_type = LMGetKbdType(); | ||
| 329 | |||
| 330 | SDL_Keymap *keymap = SDL_CreateKeymap(); | ||
| 331 | for (int m = 0; m < SDL_arraysize(mods); ++m) { | ||
| 332 | for (int i = 0; i < SDL_arraysize(darwin_scancode_table); i++) { | ||
| 333 | OSStatus err; | ||
| 334 | UniChar s[8]; | ||
| 335 | UniCharCount len; | ||
| 336 | UInt32 dead_key_state; | ||
| 337 | |||
| 338 | // Make sure this scancode is a valid character scancode | ||
| 339 | SDL_Scancode scancode = darwin_scancode_table[i]; | ||
| 340 | if (scancode == SDL_SCANCODE_UNKNOWN || | ||
| 341 | scancode == SDL_SCANCODE_DELETE || | ||
| 342 | (SDL_GetKeymapKeycode(NULL, scancode, SDL_KMOD_NONE) & SDLK_SCANCODE_MASK)) { | ||
| 343 | continue; | ||
| 344 | } | ||
| 345 | |||
| 346 | /* | ||
| 347 | * Swap the scancode for these two wrongly translated keys | ||
| 348 | * UCKeyTranslate() function does not do its job properly for ISO layout keyboards, where the key '@', | ||
| 349 | * which is located in the top left corner of the keyboard right under the Escape key, and the additional | ||
| 350 | * key '<', which is on the right of the Shift key, are inverted | ||
| 351 | */ | ||
| 352 | if ((scancode == SDL_SCANCODE_NONUSBACKSLASH || scancode == SDL_SCANCODE_GRAVE) && KBGetLayoutType(LMGetKbdType()) == kKeyboardISO) { | ||
| 353 | // see comments in scancodes_darwin.h | ||
| 354 | scancode = (SDL_Scancode)((SDL_SCANCODE_NONUSBACKSLASH + SDL_SCANCODE_GRAVE) - scancode); | ||
| 355 | } | ||
| 356 | |||
| 357 | dead_key_state = 0; | ||
| 358 | err = UCKeyTranslate(keyLayoutPtr, i, kUCKeyActionDown, | ||
| 359 | ((mods[m].flags >> 8) & 0xFF), keyboard_type, | ||
| 360 | kUCKeyTranslateNoDeadKeysMask, | ||
| 361 | &dead_key_state, 8, &len, s); | ||
| 362 | if (err != noErr) { | ||
| 363 | continue; | ||
| 364 | } | ||
| 365 | |||
| 366 | if (len > 0 && s[0] != 0x10) { | ||
| 367 | SDL_SetKeymapEntry(keymap, scancode, mods[m].modstate, s[0]); | ||
| 368 | } else { | ||
| 369 | // The default keymap doesn't have any SDL_KMOD_ALT entries, so we don't need to override them | ||
| 370 | if (!(mods[m].modstate & SDL_KMOD_ALT)) { | ||
| 371 | SDL_SetKeymapEntry(keymap, scancode, mods[m].modstate, SDLK_UNKNOWN); | ||
| 372 | } | ||
| 373 | } | ||
| 374 | } | ||
| 375 | } | ||
| 376 | SDL_SetKeymap(keymap, send_event); | ||
| 377 | } | ||
| 378 | |||
| 379 | static void SDLCALL SDL_MacOptionAsAltChanged(void *userdata, const char *name, const char *oldValue, const char *hint) | ||
| 380 | { | ||
| 381 | SDL_VideoDevice *_this = (SDL_VideoDevice *)userdata; | ||
| 382 | SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->internal; | ||
| 383 | |||
| 384 | if (hint && *hint) { | ||
| 385 | if (SDL_strcmp(hint, "none") == 0) { | ||
| 386 | data.option_as_alt = OptionAsAltNone; | ||
| 387 | } else if (SDL_strcmp(hint, "only_left") == 0) { | ||
| 388 | data.option_as_alt = OptionAsAltOnlyLeft; | ||
| 389 | } else if (SDL_strcmp(hint, "only_right") == 0) { | ||
| 390 | data.option_as_alt = OptionAsAltOnlyRight; | ||
| 391 | } else if (SDL_strcmp(hint, "both") == 0) { | ||
| 392 | data.option_as_alt = OptionAsAltBoth; | ||
| 393 | } | ||
| 394 | } else { | ||
| 395 | data.option_as_alt = OptionAsAltNone; | ||
| 396 | } | ||
| 397 | } | ||
| 398 | |||
| 399 | void Cocoa_InitKeyboard(SDL_VideoDevice *_this) | ||
| 400 | { | ||
| 401 | SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->internal; | ||
| 402 | |||
| 403 | UpdateKeymap(data, false); | ||
| 404 | |||
| 405 | // Set our own names for the platform-dependent but layout-independent keys | ||
| 406 | // This key is NumLock on the MacBook keyboard. :) | ||
| 407 | // SDL_SetScancodeName(SDL_SCANCODE_NUMLOCKCLEAR, "Clear"); | ||
| 408 | SDL_SetScancodeName(SDL_SCANCODE_LALT, "Left Option"); | ||
| 409 | SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Command"); | ||
| 410 | SDL_SetScancodeName(SDL_SCANCODE_RALT, "Right Option"); | ||
| 411 | SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Command"); | ||
| 412 | |||
| 413 | data.modifierFlags = (unsigned int)[NSEvent modifierFlags]; | ||
| 414 | SDL_ToggleModState(SDL_KMOD_CAPS, (data.modifierFlags & NSEventModifierFlagCapsLock) ? true : false); | ||
| 415 | |||
| 416 | SDL_AddHintCallback(SDL_HINT_MAC_OPTION_AS_ALT, SDL_MacOptionAsAltChanged, _this); | ||
| 417 | } | ||
| 418 | |||
| 419 | bool Cocoa_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) | ||
| 420 | { | ||
| 421 | @autoreleasepool { | ||
| 422 | NSView *parentView; | ||
| 423 | SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->internal; | ||
| 424 | NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow; | ||
| 425 | |||
| 426 | parentView = [nswindow contentView]; | ||
| 427 | |||
| 428 | /* We only keep one field editor per process, since only the front most | ||
| 429 | * window can receive text input events, so it make no sense to keep more | ||
| 430 | * than one copy. When we switched to another window and requesting for | ||
| 431 | * text input, simply remove the field editor from its superview then add | ||
| 432 | * it to the front most window's content view */ | ||
| 433 | if (!data.fieldEdit) { | ||
| 434 | data.fieldEdit = [[SDL3TranslatorResponder alloc] initWithFrame:NSMakeRect(0.0, 0.0, 0.0, 0.0)]; | ||
| 435 | } | ||
| 436 | |||
| 437 | if (![[data.fieldEdit superview] isEqual:parentView]) { | ||
| 438 | // DEBUG_IME(@"add fieldEdit to window contentView"); | ||
| 439 | [data.fieldEdit removeFromSuperview]; | ||
| 440 | [parentView addSubview:data.fieldEdit]; | ||
| 441 | [nswindow makeFirstResponder:data.fieldEdit]; | ||
| 442 | } | ||
| 443 | } | ||
| 444 | return Cocoa_UpdateTextInputArea(_this, window); | ||
| 445 | } | ||
| 446 | |||
| 447 | bool Cocoa_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 448 | { | ||
| 449 | @autoreleasepool { | ||
| 450 | SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->internal; | ||
| 451 | |||
| 452 | if (data && data.fieldEdit) { | ||
| 453 | [data.fieldEdit removeFromSuperview]; | ||
| 454 | data.fieldEdit = nil; | ||
| 455 | } | ||
| 456 | } | ||
| 457 | return true; | ||
| 458 | } | ||
| 459 | |||
| 460 | bool Cocoa_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window) | ||
| 461 | { | ||
| 462 | SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->internal; | ||
| 463 | if (data.fieldEdit) { | ||
| 464 | [data.fieldEdit setInputRect:&window->text_input_rect]; | ||
| 465 | } | ||
| 466 | return true; | ||
| 467 | } | ||
| 468 | |||
| 469 | static NSEvent *ReplaceEvent(NSEvent *event, OptionAsAlt option_as_alt) | ||
| 470 | { | ||
| 471 | if (option_as_alt == OptionAsAltNone) { | ||
| 472 | return event; | ||
| 473 | } | ||
| 474 | |||
| 475 | const unsigned int modflags = (unsigned int)[event modifierFlags]; | ||
| 476 | |||
| 477 | bool ignore_alt_characters = false; | ||
| 478 | |||
| 479 | bool lalt_pressed = IsModifierKeyPressed(modflags, NX_DEVICELALTKEYMASK, | ||
| 480 | NX_DEVICERALTKEYMASK, NX_ALTERNATEMASK); | ||
| 481 | bool ralt_pressed = IsModifierKeyPressed(modflags, NX_DEVICERALTKEYMASK, | ||
| 482 | NX_DEVICELALTKEYMASK, NX_ALTERNATEMASK); | ||
| 483 | |||
| 484 | if (option_as_alt == OptionAsAltOnlyLeft && lalt_pressed) { | ||
| 485 | ignore_alt_characters = true; | ||
| 486 | } else if (option_as_alt == OptionAsAltOnlyRight && ralt_pressed) { | ||
| 487 | ignore_alt_characters = true; | ||
| 488 | } else if (option_as_alt == OptionAsAltBoth && (lalt_pressed || ralt_pressed)) { | ||
| 489 | ignore_alt_characters = true; | ||
| 490 | } | ||
| 491 | |||
| 492 | bool cmd_pressed = modflags & NX_COMMANDMASK; | ||
| 493 | bool ctrl_pressed = modflags & NX_CONTROLMASK; | ||
| 494 | |||
| 495 | ignore_alt_characters = ignore_alt_characters && !cmd_pressed && !ctrl_pressed; | ||
| 496 | |||
| 497 | if (ignore_alt_characters) { | ||
| 498 | NSString *charactersIgnoringModifiers = [event charactersIgnoringModifiers]; | ||
| 499 | return [NSEvent keyEventWithType:[event type] | ||
| 500 | location:[event locationInWindow] | ||
| 501 | modifierFlags:modflags | ||
| 502 | timestamp:[event timestamp] | ||
| 503 | windowNumber:[event windowNumber] | ||
| 504 | context:nil | ||
| 505 | characters:charactersIgnoringModifiers | ||
| 506 | charactersIgnoringModifiers:charactersIgnoringModifiers | ||
| 507 | isARepeat:[event isARepeat] | ||
| 508 | keyCode:[event keyCode]]; | ||
| 509 | } | ||
| 510 | |||
| 511 | return event; | ||
| 512 | } | ||
| 513 | |||
| 514 | void Cocoa_HandleKeyEvent(SDL_VideoDevice *_this, NSEvent *event) | ||
| 515 | { | ||
| 516 | unsigned short scancode; | ||
| 517 | SDL_Scancode code; | ||
| 518 | SDL_CocoaVideoData *data = _this ? ((__bridge SDL_CocoaVideoData *)_this->internal) : nil; | ||
| 519 | if (!data) { | ||
| 520 | return; // can happen when returning from fullscreen Space on shutdown | ||
| 521 | } | ||
| 522 | |||
| 523 | if ([event type] == NSEventTypeKeyDown || [event type] == NSEventTypeKeyUp) { | ||
| 524 | event = ReplaceEvent(event, data.option_as_alt); | ||
| 525 | } | ||
| 526 | |||
| 527 | scancode = [event keyCode]; | ||
| 528 | |||
| 529 | if ((scancode == 10 || scancode == 50) && KBGetLayoutType(LMGetKbdType()) == kKeyboardISO) { | ||
| 530 | // see comments in scancodes_darwin.h | ||
| 531 | scancode = 60 - scancode; | ||
| 532 | } | ||
| 533 | |||
| 534 | if (scancode < SDL_arraysize(darwin_scancode_table)) { | ||
| 535 | code = darwin_scancode_table[scancode]; | ||
| 536 | } else { | ||
| 537 | // Hmm, does this ever happen? If so, need to extend the keymap... | ||
| 538 | code = SDL_SCANCODE_UNKNOWN; | ||
| 539 | } | ||
| 540 | |||
| 541 | switch ([event type]) { | ||
| 542 | case NSEventTypeKeyDown: | ||
| 543 | if (![event isARepeat]) { | ||
| 544 | // See if we need to rebuild the keyboard layout | ||
| 545 | UpdateKeymap(data, true); | ||
| 546 | } | ||
| 547 | |||
| 548 | #ifdef DEBUG_SCANCODES | ||
| 549 | if (code == SDL_SCANCODE_UNKNOWN) { | ||
| 550 | 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.", scancode); | ||
| 551 | } | ||
| 552 | #endif | ||
| 553 | if (SDL_TextInputActive(SDL_GetKeyboardFocus())) { | ||
| 554 | [data.fieldEdit setPendingKey:scancode scancode:code timestamp:Cocoa_GetEventTimestamp([event timestamp])]; | ||
| 555 | [data.fieldEdit interpretKeyEvents:[NSArray arrayWithObject:event]]; | ||
| 556 | [data.fieldEdit sendPendingKey]; | ||
| 557 | } else if (SDL_GetKeyboardFocus()) { | ||
| 558 | SDL_SendKeyboardKey(Cocoa_GetEventTimestamp([event timestamp]), SDL_DEFAULT_KEYBOARD_ID, scancode, code, true); | ||
| 559 | } | ||
| 560 | break; | ||
| 561 | case NSEventTypeKeyUp: | ||
| 562 | SDL_SendKeyboardKey(Cocoa_GetEventTimestamp([event timestamp]), SDL_DEFAULT_KEYBOARD_ID, scancode, code, false); | ||
| 563 | break; | ||
| 564 | case NSEventTypeFlagsChanged: { | ||
| 565 | // see if the new modifierFlags mean any existing keys should be pressed/released... | ||
| 566 | const unsigned int modflags = (unsigned int)[event modifierFlags]; | ||
| 567 | HandleModifiers(_this, SDL_SCANCODE_LSHIFT, modflags); | ||
| 568 | HandleModifiers(_this, SDL_SCANCODE_LCTRL, modflags); | ||
| 569 | HandleModifiers(_this, SDL_SCANCODE_LALT, modflags); | ||
| 570 | HandleModifiers(_this, SDL_SCANCODE_LGUI, modflags); | ||
| 571 | HandleModifiers(_this, SDL_SCANCODE_RSHIFT, modflags); | ||
| 572 | HandleModifiers(_this, SDL_SCANCODE_RCTRL, modflags); | ||
| 573 | HandleModifiers(_this, SDL_SCANCODE_RALT, modflags); | ||
| 574 | HandleModifiers(_this, SDL_SCANCODE_RGUI, modflags); | ||
| 575 | break; | ||
| 576 | } | ||
| 577 | default: // just to avoid compiler warnings | ||
| 578 | break; | ||
| 579 | } | ||
| 580 | } | ||
| 581 | |||
| 582 | void Cocoa_QuitKeyboard(SDL_VideoDevice *_this) | ||
| 583 | { | ||
| 584 | } | ||
| 585 | |||
| 586 | typedef int CGSConnection; | ||
| 587 | typedef enum | ||
| 588 | { | ||
| 589 | CGSGlobalHotKeyEnable = 0, | ||
| 590 | CGSGlobalHotKeyDisable = 1, | ||
| 591 | } CGSGlobalHotKeyOperatingMode; | ||
| 592 | |||
| 593 | extern CGSConnection _CGSDefaultConnection(void); | ||
| 594 | extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode); | ||
| 595 | |||
| 596 | bool Cocoa_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed) | ||
| 597 | { | ||
| 598 | #ifdef SDL_MAC_NO_SANDBOX | ||
| 599 | CGSSetGlobalHotKeyOperatingMode(_CGSDefaultConnection(), grabbed ? CGSGlobalHotKeyDisable : CGSGlobalHotKeyEnable); | ||
| 600 | #endif | ||
| 601 | return true; | ||
| 602 | } | ||
| 603 | |||
| 604 | #endif // SDL_VIDEO_DRIVER_COCOA | ||
