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: */