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(&currentLayout);
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**)&currentLayoutId);
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