1/* 2 * tkMacOSXKeyEvent.c -- 3 * 4 * This file implements functions that decode & handle keyboard events 5 * on MacOS X. 6 * 7 * Copyright 2001, Apple Computer, Inc. 8 * Copyright (c) 2006-2007 Daniel A. Steffen <das@users.sourceforge.net> 9 * 10 * See the file "license.terms" for information on usage and redistribution of 11 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 12 * 13 * The following terms apply to all files originating from Apple 14 * Computer, Inc. ("Apple") and associated with the software 15 * unless explicitly disclaimed in individual files. 16 * 17 * 18 * Apple hereby grants permission to use, copy, modify, 19 * distribute, and license this software and its documentation 20 * for any purpose, provided that existing copyright notices are 21 * retained in all copies and that this notice is included 22 * verbatim in any distributions. No written agreement, license, 23 * or royalty fee is required for any of the authorized 24 * uses. Modifications to this software may be copyrighted by 25 * their authors and need not follow the licensing terms 26 * described here, provided that the new terms are clearly 27 * indicated on the first page of each file where they apply. 28 * 29 * 30 * IN NO EVENT SHALL APPLE, THE AUTHORS OR DISTRIBUTORS OF THE 31 * SOFTWARE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, 32 * INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF 33 * THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, 34 * EVEN IF APPLE OR THE AUTHORS HAVE BEEN ADVISED OF THE 35 * POSSIBILITY OF SUCH DAMAGE. APPLE, THE AUTHORS AND 36 * DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, 37 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, 38 * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS 39 * SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND APPLE,THE 40 * AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE 41 * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 42 * 43 * GOVERNMENT USE: If you are acquiring this software on behalf 44 * of the U.S. government, the Government shall have only 45 * "Restricted Rights" in the software and related documentation 46 * as defined in the Federal Acquisition Regulations (FARs) in 47 * Clause 52.227.19 (c) (2). If you are acquiring the software 48 * on behalf of the Department of Defense, the software shall be 49 * classified as "Commercial Computer Software" and the 50 * Government shall have only "Restricted Rights" as defined in 51 * Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the 52 * foregoing, the authors grant the U.S. Government and others 53 * acting in its behalf permission to use and distribute the 54 * software in accordance with the terms specified in this 55 * license. 56 * 57 * RCS: @(#) $Id: tkMacOSXKeyEvent.c,v 1.6.2.15 2007/06/29 03:22:02 das Exp $ 58 */ 59 60#include "tkMacOSXPrivate.h" 61#include "tkMacOSXEvent.h" 62 63/* 64#ifdef TK_MAC_DEBUG 65#define TK_MAC_DEBUG_KEYBOARD 66#endif 67*/ 68 69typedef struct { 70 WindowRef whichWindow; 71 int global_x, global_y; 72 int local_x, local_y; 73 unsigned int state; 74 UInt32 keyCode; 75 UInt32 keyModifiers; 76 UInt32 message; 77 unsigned char ch; 78} KeyEventData; 79 80static Tk_Window grabWinPtr = NULL; 81 /* Current grab window, NULL if no grab. */ 82static Tk_Window keyboardGrabWinPtr = NULL; 83 /* Current keyboard grab window. */ 84static UInt32 deadKeyStateUp = 0; 85 /* The deadkey state for the current sequence 86 * of keyup events or 0 if not in a deadkey 87 * sequence */ 88static UInt32 deadKeyStateDown = 0; 89 /* Ditto for keydown */ 90 91/* 92 * Declarations for functions used only in this file. 93 */ 94 95static int InitKeyData(KeyEventData *keyEventDataPtr); 96static int InitKeyEvent(XEvent *eventPtr, KeyEventData *e, UInt32 savedKeyCode, 97 UInt32 savedModifiers); 98static int GenerateKeyEvent(UInt32 eKind, KeyEventData *e, UInt32 savedKeyCode, 99 UInt32 savedModifiers, const UniChar *chars, int numChars); 100static int GetKeyboardLayout(Ptr *resourcePtr, TextEncoding *encodingPtr); 101static TextEncoding GetKCHREncoding(ScriptCode script, SInt32 layoutid); 102static int KeycodeToUnicodeViaUnicodeResource(UniChar *uniChars, int maxChars, 103 Ptr uchr, EventKind eKind, UInt32 keycode, UInt32 modifiers, 104 UInt32 *deadKeyStatePtr); 105static int KeycodeToUnicodeViaKCHRResource(UniChar *uniChars, int maxChars, 106 Ptr kchr, TextEncoding encoding, EventKind eKind, UInt32 keycode, 107 UInt32 modifiers, UInt32 *deadKeyStatePtr); 108 109 110/* 111 *---------------------------------------------------------------------- 112 * 113 * TkMacOSXProcessKeyboardEvent -- 114 * 115 * This routine processes the event in eventPtr, and 116 * generates the appropriate Tk events from it. 117 * 118 * Results: 119 * True if event(s) are generated - false otherwise. 120 * 121 * Side effects: 122 * Additional events may be place on the Tk event queue. 123 * 124 *---------------------------------------------------------------------- 125 */ 126 127MODULE_SCOPE int 128TkMacOSXProcessKeyboardEvent( 129 TkMacOSXEvent *eventPtr, 130 MacEventStatus *statusPtr) 131{ 132 static UInt32 savedKeyCode = 0; 133 static UInt32 savedModifiers = 0; 134 static UniChar savedChar = 0; 135 OSStatus err; 136 KeyEventData keyEventData; 137 MenuRef menuRef; 138 MenuItemIndex menuItemIndex; 139 int eventGenerated; 140 UniChar uniChars[5]; /* make this larger, if needed */ 141 UInt32 uniCharsLen = 0; 142 143 if (!InitKeyData(&keyEventData)) { 144 statusPtr->err = 1; 145 return false; 146 } 147 148 /* 149 * Because of the way that Tk operates, we can't in general funnel menu 150 * accelerators through IsMenuKeyEvent. Tk treats accelerators as mere 151 * decoration, and the user has to install bindings to get them to fire. 152 * 153 * However, the only way to trigger the Hide & Hide Others functions 154 * is by invoking the Menu command for Hide. So there is no nice way to 155 * provide a Tk command to hide the app which would be available for a 156 * binding. So I am going to hijack Command-H and Command-Shift-H 157 * here, and run the menu commands. Since the HI Guidelines explicitly 158 * reserve these for Hide, this isn't such a bad thing. Also, if you do 159 * rebind Command-H to another menu item, Hide will lose its binding. 160 * 161 * Note that I don't really do anything at this point, 162 * I just mark stopProcessing as 0 and return, and then the 163 * RecieveAndProcessEvent code will dispatch the event to the default 164 * handler. 165 */ 166 167 if ((eventPtr->eKind == kEventRawKeyDown 168 || eventPtr->eKind == kEventRawKeyRepeat) 169 && IsMenuKeyEvent(tkCurrentAppleMenu, eventPtr->eventRef, 170 kMenuEventQueryOnly, &menuRef, &menuItemIndex)) { 171 MenuCommand menuCmd; 172 173 GetMenuItemCommandID (menuRef, menuItemIndex, &menuCmd); 174 switch (menuCmd) { 175 case kHICommandHide: 176 case kHICommandHideOthers: 177 case kHICommandShowAll: 178 case kHICommandPreferences: 179 case kHICommandQuit: 180 statusPtr->stopProcessing = 0; 181 182 /* 183 * TODO: may not be on event on queue. 184 */ 185 186 return 0; 187 break; 188 default: 189 break; 190 } 191 } 192 193 err = ChkErr(GetEventParameter, eventPtr->eventRef, 194 kEventParamKeyMacCharCodes, typeChar, NULL, 195 sizeof(keyEventData.ch), NULL, &keyEventData.ch); 196 if (err != noErr) { 197 statusPtr->err = 1; 198 return false; 199 } 200 err = ChkErr(GetEventParameter, eventPtr->eventRef, kEventParamKeyCode, 201 typeUInt32, NULL, sizeof(keyEventData.keyCode), NULL, 202 &keyEventData.keyCode); 203 if (err != noErr) { 204 statusPtr->err = 1; 205 return false; 206 } 207 err = ChkErr(GetEventParameter, eventPtr->eventRef, 208 kEventParamKeyModifiers, typeUInt32, NULL, 209 sizeof(keyEventData.keyModifiers), NULL, 210 &keyEventData.keyModifiers); 211 if (err != noErr) { 212 statusPtr->err = 1; 213 return false; 214 } 215 216 switch (eventPtr->eKind) { 217 case kEventRawKeyUp: 218 case kEventRawKeyDown: 219 case kEventRawKeyRepeat: { 220 UInt32 *deadKeyStatePtr; 221 222 if (kEventRawKeyDown == eventPtr->eKind) { 223 deadKeyStatePtr = &deadKeyStateDown; 224 } else { 225 deadKeyStatePtr = &deadKeyStateUp; 226 } 227 228 uniCharsLen = TkMacOSXKeycodeToUnicode(uniChars, 229 sizeof(uniChars)/sizeof(*uniChars), eventPtr->eKind, 230 keyEventData.keyCode, keyEventData.keyModifiers, 231 deadKeyStatePtr); 232 break; 233 } 234 } 235 236 if (kEventRawKeyUp == eventPtr->eKind) { 237 /* 238 * For some reason the deadkey processing for KeyUp doesn't work 239 * sometimes, so we fudge and use the last detected KeyDown. 240 */ 241 242 if ((0 == uniCharsLen) && (0 != savedChar)) { 243 uniChars[0] = savedChar; 244 uniCharsLen = 1; 245 } 246 247 /* 248 * Suppress keyup events while we have a deadkey sequence on keydown. 249 * We still *do* want to collect deadkey state in this situation if 250 * the system provides it, that's why we do this only after 251 * TkMacOSXKeycodeToUnicode(). 252 */ 253 254 if (0 != deadKeyStateDown) { 255 uniCharsLen = 0; 256 } 257 } 258 259 keyEventData.message = keyEventData.ch|(keyEventData.keyCode << 8); 260 261 eventGenerated = GenerateKeyEvent(eventPtr->eKind, &keyEventData, 262 savedKeyCode, savedModifiers, uniChars, uniCharsLen); 263 264 savedModifiers = keyEventData.keyModifiers; 265 266 if ((kEventRawKeyDown == eventPtr->eKind) && (uniCharsLen > 0)) { 267 savedChar = uniChars[0]; 268 } else { 269 savedChar = 0; 270 } 271 272 statusPtr->stopProcessing = 1; 273 274 if (eventGenerated == 0) { 275 savedKeyCode = keyEventData.message; 276 return false; 277 } else if (eventGenerated == -1) { 278 savedKeyCode = 0; 279 statusPtr->stopProcessing = 0; 280 return false; 281 } else { 282 savedKeyCode = 0; 283 return true; 284 } 285} 286 287/* 288 *---------------------------------------------------------------------- 289 * 290 * GenerateKeyEvent -- 291 * 292 * Given Macintosh keyUp, keyDown & autoKey events (in their "raw" 293 * form) and a list of unicode characters this function generates the 294 * appropriate X key events. 295 * 296 * Parameter eKind is a raw keyboard event. e contains the data sent 297 * with the event. savedKeyCode and savedModifiers contain the values 298 * from the last event that came before (see 299 * TkMacOSXProcessKeyboardEvent()). chars/numChars has the Unicode 300 * characters for which we want to create events. 301 * 302 * Results: 303 * 1 if an event was generated, -1 for any error. 304 * 305 * Side effects: 306 * Additional events may be place on the Tk event queue. 307 * 308 *---------------------------------------------------------------------- 309 */ 310 311static int 312GenerateKeyEvent( 313 UInt32 eKind, 314 KeyEventData * e, 315 UInt32 savedKeyCode, 316 UInt32 savedModifiers, 317 const UniChar * chars, 318 int numChars) 319{ 320 XEvent event; 321 int i; 322 323 if (-1 == InitKeyEvent(&event, e, savedKeyCode, savedModifiers)) { 324 return -1; 325 } 326 327 if (kEventRawKeyModifiersChanged == eKind) { 328 if (savedModifiers > e->keyModifiers) { 329 event.xany.type = KeyRelease; 330 } else { 331 event.xany.type = KeyPress; 332 } 333 334 /* 335 * Use special '-1' to signify a special keycode to our 336 * platform specific code in tkMacOSXKeyboard.c. This is 337 * rather like what happens on Windows. 338 */ 339 340 event.xany.send_event = -1; 341 342 /* 343 * Set keycode (which was zero) to the changed modifier 344 */ 345 346 event.xkey.keycode = (e->keyModifiers ^ savedModifiers); 347 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); 348 349 } else { 350 for (i = 0; i < numChars; ++i) { 351 /* 352 * Encode one char in the trans_chars array that was already 353 * introduced for MS Windows. Don't encode the string, if it is 354 * a control character but was not generated with a real control 355 * modifier. Such control characters get generated by KeyTrans() 356 * for special keys, but we rather want to identify those by 357 * their KeySyms. 358 */ 359 360 event.xkey.trans_chars[0] = 0; 361 if ((controlKey & e->keyModifiers) || (chars[i] >= ' ')) { 362 int done; 363 done = Tcl_UniCharToUtf(chars[i],event.xkey.trans_chars); 364 event.xkey.trans_chars[done] = 0; 365 } 366 367 switch(eKind) { 368 case kEventRawKeyDown: 369 event.xany.type = KeyPress; 370 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); 371 break; 372 case kEventRawKeyUp: 373 event.xany.type = KeyRelease; 374 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); 375 break; 376 case kEventRawKeyRepeat: 377 event.xany.type = KeyRelease; 378 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); 379 event.xany.type = KeyPress; 380 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); 381 break; 382 default: 383 TkMacOSXDbgMsg("Invalid parameter eKind %ld", eKind); 384 return -1; 385 } 386 } 387 } 388 389 return 1; 390} 391 392/* 393 *---------------------------------------------------------------------- 394 * 395 * InitKeyData -- 396 * 397 * This routine initializes a KeyEventData structure by asking the OS 398 * and Tk for all the global information needed here. 399 * 400 * Results: 401 * True if the current front window can be found in Tk data structures 402 * - false otherwise. 403 * 404 * Side Effects: 405 * None 406 * 407 *---------------------------------------------------------------------- 408 */ 409 410static int 411InitKeyData( 412 KeyEventData *keyEventDataPtr) 413{ 414 memset(keyEventDataPtr, 0, sizeof(*keyEventDataPtr)); 415 416 keyEventDataPtr->whichWindow = ActiveNonFloatingWindow(); 417 if (keyEventDataPtr->whichWindow == NULL) { 418 return false; 419 } 420 XQueryPointer(NULL, None, NULL, NULL, &keyEventDataPtr->global_x, 421 &keyEventDataPtr->global_y, &keyEventDataPtr->local_x, 422 &keyEventDataPtr->local_y, &keyEventDataPtr->state); 423 424 return true; 425} 426 427/* 428 *---------------------------------------------------------------------- 429 * 430 * InitKeyEvent -- 431 * 432 * Initialize an XEvent structure by asking Tk for global information. 433 * Also uses a KeyEventData structure and other current state. 434 * 435 * Results: 436 * 1 on success, -1 for any error. 437 * 438 * Side effects: 439 * Additional events may be place on the Tk event queue. 440 * 441 *---------------------------------------------------------------------- 442 */ 443 444/* 445 * We have a general problem here. How do we handle 'Option-char' 446 * keypresses? The problem is that we might want to bind to some of these 447 * (e.g. Cmd-Opt-d is 'uncomment' in Alpha). OTOH Option-d actually produces 448 * a real character on MacOS, namely a mathematical delta. 449 * 450 * The current behaviour is that a binding goes by the combinations of 451 * modifiers and base keysym, that is Option-d. The string value of the 452 * event is the mathematical delta character, so if no binding calls 453 * [break], the text widget will insert that character. 454 * 455 * Note that this is similar to control combinations on all platforms. They 456 * also generate events that have the base character as keysym and a real 457 * control character as character value. So Ctrl+C gets us the keysym XK_C, 458 * the modifier Control (so you can bind <Control-C>) and a string value as 459 * "\u0003". 460 * 461 * For a different solutions we may want for the event to contain keysyms for 462 * *both* the 'Opt-d' side of things and the mathematical delta. Then a 463 * binding on Opt-d will trigger, but a binding on mathematical delta would 464 * also trigger. This would require changes in the core, though. 465 */ 466 467static int 468InitKeyEvent( 469 XEvent * eventPtr, 470 KeyEventData * e, 471 UInt32 savedKeyCode, 472 UInt32 savedModifiers) 473{ 474 Window window; 475 Tk_Window tkwin; 476 TkDisplay *dispPtr; 477 478 /* 479 * The focus must be in the FrontWindow on the Macintosh. 480 * We then query Tk to determine the exact Tk window 481 * that owns the focus. 482 */ 483 484 window = TkMacOSXGetXWindow(e->whichWindow); 485 dispPtr = TkGetDisplayList(); 486 tkwin = Tk_IdToWindow(dispPtr->display, window); 487 488 if (!tkwin) { 489 TkMacOSXDbgMsg("tkwin == NULL"); 490 return -1; 491 } 492 493 tkwin = (Tk_Window) ((TkWindow *) tkwin)->dispPtr->focusPtr; 494 if (!tkwin) { 495 TkMacOSXDbgMsg("tkwin == NULL"); 496 return -1; 497 } 498 499 eventPtr->xany.send_event = false; 500 eventPtr->xany.serial = Tk_Display(tkwin)->request; 501 502 eventPtr->xkey.same_screen = true; 503 eventPtr->xkey.subwindow = None; 504 eventPtr->xkey.time = TkpGetMS(); 505 eventPtr->xkey.x_root = e->global_x; 506 eventPtr->xkey.y_root = e->global_y; 507 eventPtr->xkey.window = Tk_WindowId(tkwin); 508 eventPtr->xkey.display = Tk_Display(tkwin); 509 eventPtr->xkey.root = XRootWindow(Tk_Display(tkwin), 0); 510 eventPtr->xkey.state = e->state; 511 eventPtr->xkey.trans_chars[0] = 0; 512 513 Tk_TopCoordsToWindow(tkwin, e->local_x, e->local_y, &eventPtr->xkey.x, 514 &eventPtr->xkey.y); 515 516 eventPtr->xkey.keycode = e->ch | ((savedKeyCode & charCodeMask) << 8) | 517 ((e->message&keyCodeMask) << 8); 518 519 return 1; 520} 521 522/* 523 *---------------------------------------------------------------------- 524 * 525 * GetKeyboardLayout -- 526 * 527 * Queries the OS for a pointer to a keyboard resource. 528 * 529 * This function works with the keyboard layout switch menu. It uses 530 * Keyboard Layout Services, where available. 531 * 532 * Results: 533 * 1 if there is returned a Unicode 'uchr' resource in *resourcePtr, 0 534 * if it is a classic 'KCHR' resource. A pointer to the actual resource 535 * data goes into *resourcePtr. If the resource is a 'KCHR' resource, 536 * the corresponding Mac encoding goes into *encodingPtr. 537 * 538 * Side effects: 539 * Sets some internal static variables. 540 * 541 *---------------------------------------------------------------------- 542 */ 543 544static int 545GetKeyboardLayout( 546 Ptr *resourcePtr, 547 TextEncoding *encodingPtr) 548{ 549 static KeyboardLayoutRef lastLayout = NULL; 550 static SInt32 lastLayoutId; 551 static TextEncoding lastEncoding = kTextEncodingMacRoman; 552 static Ptr uchr = NULL; 553 static Ptr KCHR = NULL; 554 int hasLayoutChanged = false; 555 KeyboardLayoutRef currentLayout = NULL; 556 SInt32 currentLayoutId = 0; 557 ScriptCode currentKeyScript; 558 559 currentKeyScript = GetScriptManagerVariable(smKeyScript); 560 561 /* 562 * Use the Keyboard Layout Services. 563 */ 564 565 KLGetCurrentKeyboardLayout(¤tLayout); 566 567 if (currentLayout != NULL) { 568 569 /* 570 * The layout pointer could in theory be the same for different 571 * layouts, only the id gives us the information that the 572 * keyboard has actually changed. OTOH the layout object can 573 * also change and it could still be the same layoutid. 574 */ 575 576 KLGetKeyboardLayoutProperty(currentLayout, kKLIdentifier, 577 (const void**)¤tLayoutId); 578 579 if ((lastLayout != currentLayout) 580 || (lastLayoutId != currentLayoutId)) { 581 582#ifdef TK_MAC_DEBUG_KEYBOARD 583 TkMacOSXDbgMsg("Use KLS"); 584#endif 585 586 hasLayoutChanged = true; 587 588 /* 589 * Reinitialize all relevant variables. 590 */ 591 592 lastLayout = currentLayout; 593 lastLayoutId = currentLayoutId; 594 uchr = NULL; 595 KCHR = NULL; 596 597 if ((KLGetKeyboardLayoutProperty(currentLayout, 598 kKLuchrData, (const void**)&uchr) 599 == noErr) 600 && (uchr != NULL)) { 601 /* done */ 602 } else if ((KLGetKeyboardLayoutProperty(currentLayout, 603 kKLKCHRData, (const void**)&KCHR) 604 == noErr) 605 && (KCHR != NULL)) { 606 /* done */ 607 } 608 } 609 } 610 611 if (hasLayoutChanged) { 612#ifdef TK_MAC_DEBUG_KEYBOARD 613 if (KCHR) { 614 TkMacOSXDbgMsg("New 'KCHR' layout %ld", currentLayoutId); 615 } else if (uchr) { 616 TkMacOSXDbgMsg("New 'uchr' layout %ld", currentLayoutId); 617 } else { 618 TkMacOSXDbgMsg("Use cached layout (should have been %ld)", 619 currentLayoutId); 620 } 621#endif 622 623 deadKeyStateUp = deadKeyStateDown = 0; 624 625 /* 626 * If we did get a new 'KCHR', compute its encoding and put it into 627 * lastEncoding. 628 * 629 * If we didn't get a new 'KCHR' and if we have no 'uchr' either, get 630 * some 'KCHR' from the OS cache and leave lastEncoding at its 631 * current value. This should better not happen, it doesn't really 632 * work. 633 */ 634 635 if (KCHR) { 636 lastEncoding = GetKCHREncoding(currentKeyScript, currentLayoutId); 637#ifdef TK_MAC_DEBUG_KEYBOARD 638 TkMacOSXDbgMsg("New 'KCHR' encoding %lu (%lu + 0x%lX)", 639 lastEncoding, lastEncoding & 0xFFFFL, 640 lastEncoding & ~0xFFFFL); 641#endif 642 } else if (!uchr) { 643 KCHR = (Ptr)(intptr_t)GetScriptManagerVariable(smKCHRCache); 644 } 645 } 646 647 if (uchr) { 648 *resourcePtr = uchr; 649 return 1; 650 } else { 651 *resourcePtr = KCHR; 652 *encodingPtr = lastEncoding; 653 return 0; 654 } 655} 656 657/* 658 *---------------------------------------------------------------------- 659 * 660 * GetKCHREncoding -- 661 * 662 * Upgrade a WorldScript code to a TEC encoding based on the keyboard 663 * layout id. 664 * 665 * Results: 666 * The TEC code that corresponds best to the combination of WorldScript 667 * code and 'KCHR' id. 668 * 669 * Side effects: 670 * None. 671 * 672 * Rationale and Notes: 673 * WorldScript codes are sometimes not unique encodings. E.g. Icelandic 674 * uses script smRoman (0), but the actual encoding is 675 * kTextEncodingMacIcelandic (37). ftp://ftp.unicode.org/Public 676 * /MAPPINGS/VENDORS/APPLE/README.TXT has a good summary of these 677 * variants. So we need to upgrade the script to an encoding with 678 * GetTextEncodingFromScriptInfo(). 679 * 680 * 'KCHR' ids are usually region codes (see the comments in Script.h). 681 * Where they are not, we get a paramErr from the OS function and have 682 * appropriate fallbacks. 683 * 684 *---------------------------------------------------------------------- 685 */ 686 687static TextEncoding 688GetKCHREncoding( 689 ScriptCode script, 690 SInt32 layoutid) 691{ 692 RegionCode region = layoutid; 693 TextEncoding encoding = script; 694 695 if (GetTextEncodingFromScriptInfo(script, kTextLanguageDontCare, region, 696 &encoding) == noErr) { 697 return encoding; 698 } 699 700 /* 701 * GetTextEncodingFromScriptInfo() doesn't know about more exotic 702 * layouts. This provides a fallback for good measure. In an ideal 703 * world, exotic layouts would always provide a 'uchr' resource anyway, 704 * so we wouldn't need this. 705 * 706 * We can add more keyboard layouts, if we get actual complaints. Farsi 707 * or other Celtic/Gaelic layouts would be candidates. 708 */ 709 710 switch (layoutid) { 711 /* 712 * Icelandic and Faroese (planned). These layouts are sold by Apple 713 * Iceland for legacy applications. 714 */ 715 716 case 1800: case 1821: 717 return kTextEncodingMacIcelandic; 718 719 /* 720 * Irish and Welsh. These layouts are mentioned in <Script.h>. 721 * 722 * FIXME: This may have to be kTextEncodingMacGaelic instead, but I 723 * can't locate layouts of this type for testing. 724 */ 725 726 case 581: case 779: 727 return kTextEncodingMacCeltic; 728 } 729 730 /* 731 * The valid script codes are also the valid default encoding codes, so 732 * if nothing else helps, fall back on those. 733 */ 734 735 return script; 736} 737 738/* 739 *---------------------------------------------------------------------- 740 * 741 * KeycodeToUnicodeViaUnicodeResource -- 742 * 743 * Given MacOS key event data this function generates the Unicode 744 * characters. It does this using a 'uchr' and the UCKeyTranslate 745 * API. 746 * 747 * The parameter deadKeyStatePtr can be NULL, if no deadkey handling 748 * is needed. 749 * 750 * Tested and known to work with US, Hebrew, Greek and Russian layouts 751 * as well as "Unicode Hex Input". 752 * 753 * Results: 754 * The number of characters generated if any, 0 if we are waiting for 755 * another byte of a dead-key sequence. Fills in the uniChars array 756 * with a Unicode string. 757 * 758 * Side Effects: 759 * None 760 * 761 *---------------------------------------------------------------------- 762 */ 763 764static int 765KeycodeToUnicodeViaUnicodeResource( 766 UniChar *uniChars, 767 int maxChars, 768 Ptr uchr, 769 EventKind eKind, 770 UInt32 keycode, 771 UInt32 modifiers, 772 UInt32 *deadKeyStatePtr) 773{ 774 int action; 775 unsigned long keyboardType; 776 OptionBits options = 0; 777 UInt32 dummy_state; 778 UniCharCount actuallength; 779 OSStatus err; 780 781 keycode &= 0xFF; 782 modifiers = (modifiers >> 8) & 0xFF; 783 keyboardType = LMGetKbdType(); 784 785 if (NULL==deadKeyStatePtr) { 786 options = kUCKeyTranslateNoDeadKeysMask; 787 dummy_state = 0; 788 deadKeyStatePtr = &dummy_state; 789 } 790 791 switch(eKind) { 792 case kEventRawKeyDown: 793 action = kUCKeyActionDown; 794 break; 795 case kEventRawKeyUp: 796 action = kUCKeyActionUp; 797 break; 798 case kEventRawKeyRepeat: 799 action = kUCKeyActionAutoKey; 800 break; 801 default: 802 TkMacOSXDbgMsg("Invalid parameter eKind %d", eKind); 803 return 0; 804 } 805 806 err = ChkErr(UCKeyTranslate, (const UCKeyboardLayout *) uchr, keycode, 807 action, modifiers, keyboardType, options, deadKeyStatePtr, 808 maxChars, &actuallength, uniChars); 809 810 if ((0 == actuallength) && (0 != *deadKeyStatePtr)) { 811 /* 812 * More data later 813 */ 814 815 return 0; 816 } 817 818 /* 819 * some IMEs leave residue :-( 820 */ 821 822 *deadKeyStatePtr = 0; 823 824 if (err != noErr) { 825 actuallength = 0; 826 } 827 828 return actuallength; 829} 830 831/* 832 *---------------------------------------------------------------------- 833 * 834 * KeycodeToUnicodeViaKCHRResource -- 835 * 836 * Given MacOS key event data this function generates the Unicode 837 * characters. It does this using a 'KCHR' and the KeyTranslate API. 838 * 839 * The parameter deadKeyStatePtr can be NULL, if no deadkey handling 840 * is needed. 841 * 842 * Results: 843 * The number of characters generated if any, 0 if we are waiting for 844 * another byte of a dead-key sequence. Fills in the uniChars array 845 * with a Unicode string. 846 * 847 * Side Effects: 848 * None 849 * 850 *---------------------------------------------------------------------- 851 */ 852 853static int 854KeycodeToUnicodeViaKCHRResource( 855 UniChar *uniChars, 856 int maxChars, 857 Ptr kchr, 858 TextEncoding encoding, 859 EventKind eKind, 860 UInt32 keycode, 861 UInt32 modifiers, 862 UInt32 *deadKeyStatePtr) 863{ 864 UInt32 result; 865 char macBuff[3]; 866 char *macStr; 867 int macStrLen; 868 UInt32 dummy_state = 0; 869 870 if (NULL == deadKeyStatePtr) { 871 deadKeyStatePtr = &dummy_state; 872 } 873 874 keycode |= modifiers; 875 result = KeyTranslate(kchr, keycode, deadKeyStatePtr); 876 877 if ((0 == result) && (0 != dummy_state)) { 878 /* 879 * 'dummy_state' gets only filled if the caller did not want deadkey 880 * processing (deadKeyStatePtr was NULL originally), but we still 881 * have a deadkey. We just push the keycode for the space bar to get 882 * the real key value. 883 */ 884 885 result = KeyTranslate(kchr, 0x31, deadKeyStatePtr); 886 *deadKeyStatePtr = 0; 887 } 888 889 if ((0 == result) && (0 != *deadKeyStatePtr)) { 890 /* 891 * More data later 892 */ 893 894 return 0; 895 } 896 897 macBuff[0] = (char) (result >> 16); 898 macBuff[1] = (char) result; 899 macBuff[2] = 0; 900 901 if (0 != macBuff[0]) { 902 /* 903 * If the first byte is valid, the second is too 904 */ 905 906 macStr = macBuff; 907 macStrLen = 2; 908 } else if (0 != macBuff[1]) { 909 /* 910 * Only the second is valid 911 */ 912 913 macStr = macBuff+1; 914 macStrLen = 1; 915 } else { 916 /* 917 * No valid bytes at all -- shouldn't happen 918 */ 919 920 macStr = NULL; 921 macStrLen = 0; 922 } 923 924 if (macStrLen <= 0) { 925 return 0; 926 } else { 927 928 /* 929 * Use the CFString conversion routines. This is the easiest and 930 * most compatible way to get from an 8-bit string and a MacOS script 931 * code to a Unicode string. 932 * 933 * FIXME: The system ships with an Irish 'KCHR' but without the 934 * corresponding macCeltic encoding, which triggers the error below. 935 * Tcl doesn't have the macCeltic encoding either right now, so until 936 * we get that, we can just as well stick to this code. The right 937 * fix would be to use the Tcl encodings and add macCeltic and 938 * probably others there. Suitable Unicode data files for the 939 * missing encodings are available from www.evertype.com. 940 */ 941 942 CFStringRef cfString; 943 int uniStrLen; 944 945 cfString = CFStringCreateWithCStringNoCopy(NULL, macStr, encoding, 946 kCFAllocatorNull); 947 if (cfString == NULL) { 948 TkMacOSXDbgMsg("CFString: Can't convert with encoding %ld", 949 encoding); 950 return 0; 951 } 952 953 uniStrLen = CFStringGetLength(cfString); 954 if (uniStrLen > maxChars) { 955 uniStrLen = maxChars; 956 } 957 CFStringGetCharacters(cfString, CFRangeMake(0,uniStrLen), uniChars); 958 CFRelease(cfString); 959 960 return uniStrLen; 961 } 962} 963 964/* 965 *---------------------------------------------------------------------- 966 * 967 * TkMacOSXKeycodeToUnicode -- 968 * 969 * Given MacOS key event data this function generates the Unicode 970 * characters. It does this using OS resources and APIs. 971 * 972 * The parameter deadKeyStatePtr can be NULL, if no deadkey handling 973 * is needed. 974 * 975 * This function is called from XKeycodeToKeysym() in 976 * tkMacOSKeyboard.c. 977 * 978 * Results: 979 * The number of characters generated if any, 0 if we are waiting for 980 * another byte of a dead-key sequence. Fills in the uniChars array 981 * with a Unicode string. 982 * 983 * Side Effects: 984 * None 985 * 986 *---------------------------------------------------------------------- 987 */ 988 989MODULE_SCOPE int 990TkMacOSXKeycodeToUnicode( 991 UniChar *uniChars, 992 int maxChars, 993 EventKind eKind, 994 UInt32 keycode, 995 UInt32 modifiers, 996 UInt32 *deadKeyStatePtr) 997{ 998 Ptr resource = NULL; 999 TextEncoding encoding; 1000 int len; 1001 1002 1003 if (GetKeyboardLayout(&resource,&encoding)) { 1004 len = KeycodeToUnicodeViaUnicodeResource( 1005 uniChars, maxChars, resource, eKind, 1006 keycode, modifiers, deadKeyStatePtr); 1007 } else { 1008 len = KeycodeToUnicodeViaKCHRResource( 1009 uniChars, maxChars, resource, encoding, eKind, 1010 keycode, modifiers, deadKeyStatePtr); 1011 } 1012 1013 return len; 1014} 1015 1016/* 1017 *---------------------------------------------------------------------- 1018 * 1019 * XGrabKeyboard -- 1020 * 1021 * Simulates a keyboard grab by setting the focus. 1022 * 1023 * Results: 1024 * Always returns GrabSuccess. 1025 * 1026 * Side effects: 1027 * Sets the keyboard focus to the specified window. 1028 * 1029 *---------------------------------------------------------------------- 1030 */ 1031 1032int 1033XGrabKeyboard( 1034 Display* display, 1035 Window grab_window, 1036 Bool owner_events, 1037 int pointer_mode, 1038 int keyboard_mode, 1039 Time time) 1040{ 1041 keyboardGrabWinPtr = Tk_IdToWindow(display, grab_window); 1042 return GrabSuccess; 1043} 1044 1045/* 1046 *---------------------------------------------------------------------- 1047 * 1048 * XUngrabKeyboard -- 1049 * 1050 * Releases the simulated keyboard grab. 1051 * 1052 * Results: 1053 * None. 1054 * 1055 * Side effects: 1056 * Sets the keyboard focus back to the value before the grab. 1057 * 1058 *---------------------------------------------------------------------- 1059 */ 1060 1061void 1062XUngrabKeyboard( 1063 Display* display, 1064 Time time) 1065{ 1066 keyboardGrabWinPtr = NULL; 1067} 1068 1069/* 1070 *---------------------------------------------------------------------- 1071 * 1072 * TkMacOSXGetCapture -- 1073 * 1074 * Results: 1075 * Returns the current grab window 1076 * Side effects: 1077 * None. 1078 * 1079 */ 1080 1081Tk_Window 1082TkMacOSXGetCapture(void) 1083{ 1084 return grabWinPtr; 1085} 1086 1087/* 1088 *---------------------------------------------------------------------- 1089 * 1090 * TkpSetCapture -- 1091 * 1092 * This function captures the mouse so that all future events 1093 * will be reported to this window, even if the mouse is outside 1094 * the window. If the specified window is NULL, then the mouse 1095 * is released. 1096 * 1097 * Results: 1098 * None. 1099 * 1100 * Side effects: 1101 * Sets the capture flag and captures the mouse. 1102 * 1103 *---------------------------------------------------------------------- 1104 */ 1105 1106void 1107TkpSetCapture( 1108 TkWindow *winPtr) /* Capture window, or NULL. */ 1109{ 1110 while (winPtr && !Tk_IsTopLevel(winPtr)) { 1111 winPtr = winPtr->parentPtr; 1112 } 1113#if 0 1114 { 1115 TkWindow *w = NULL; 1116 WindowModality m; 1117 1118 if (winPtr) { 1119 w = winPtr; 1120 m = kWindowModalityAppModal; 1121 } else if (grabWinPtr) { 1122 w = (TkWindow*)grabWinPtr; 1123 m = kWindowModalityNone; 1124 } 1125 if (w && w->window != None && TkMacOSXHostToplevelExists(w)) { 1126 ChkErr(SetWindowModality, TkMacOSXDrawableWindow(w->window), m, 1127 NULL); 1128 } 1129 } 1130#endif 1131 grabWinPtr = (Tk_Window) winPtr; 1132} 1133 1134/* 1135 *---------------------------------------------------------------------- 1136 * 1137 * Tk_SetCaretPos -- 1138 * 1139 * This enables correct placement of the XIM caret. This is called 1140 * by widgets to indicate their cursor placement, and the caret 1141 * location is used by TkpGetString to place the XIM caret. 1142 * 1143 * Results: 1144 * None 1145 * 1146 * Side effects: 1147 * None 1148 * 1149 *---------------------------------------------------------------------- 1150 */ 1151 1152void 1153Tk_SetCaretPos( 1154 Tk_Window tkwin, 1155 int x, 1156 int y, 1157 int height) 1158{ 1159} 1160 1161/* 1162 *---------------------------------------------------------------------- 1163 * 1164 * TkMacOSXInitKeyboard -- 1165 * 1166 * This procedure initializes the keyboard layout. 1167 * 1168 * Results: 1169 * None. 1170 * 1171 * Side effects: 1172 * None. 1173 * 1174 *---------------------------------------------------------------------- 1175 */ 1176 1177MODULE_SCOPE void 1178TkMacOSXInitKeyboard( 1179 Tcl_Interp *interp) 1180{ 1181 Ptr resource; 1182 TextEncoding encoding; 1183 1184 GetKeyboardLayout(&resource, &encoding); 1185} 1186