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