/*
 * tkMacOSXKeyEvent.c --
 *
 *	This file implements functions that decode & handle keyboard events
 *	on MacOS X.
 *
 * Copyright 2001, Apple Computer, Inc.
 * Copyright (c) 2006-2007 Daniel A. Steffen <das@users.sourceforge.net>
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 *	The following terms apply to all files originating from Apple
 *	Computer, Inc. ("Apple") and associated with the software
 *	unless explicitly disclaimed in individual files.
 *
 *
 *	Apple hereby grants permission to use, copy, modify,
 *	distribute, and license this software and its documentation
 *	for any purpose, provided that existing copyright notices are
 *	retained in all copies and that this notice is included
 *	verbatim in any distributions. No written agreement, license,
 *	or royalty fee is required for any of the authorized
 *	uses. Modifications to this software may be copyrighted by
 *	their authors and need not follow the licensing terms
 *	described here, provided that the new terms are clearly
 *	indicated on the first page of each file where they apply.
 *
 *
 *	IN NO EVENT SHALL APPLE, THE AUTHORS OR DISTRIBUTORS OF THE
 *	SOFTWARE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
 *	INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
 *	THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF,
 *	EVEN IF APPLE OR THE AUTHORS HAVE BEEN ADVISED OF THE
 *	POSSIBILITY OF SUCH DAMAGE.  APPLE, THE AUTHORS AND
 *	DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING,
 *	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.	 THIS
 *	SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND APPLE,THE
 *	AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE
 *	MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 *	GOVERNMENT USE: If you are acquiring this software on behalf
 *	of the U.S. government, the Government shall have only
 *	"Restricted Rights" in the software and related documentation
 *	as defined in the Federal Acquisition Regulations (FARs) in
 *	Clause 52.227.19 (c) (2).  If you are acquiring the software
 *	on behalf of the Department of Defense, the software shall be
 *	classified as "Commercial Computer Software" and the
 *	Government shall have only "Restricted Rights" as defined in
 *	Clause 252.227-7013 (c) (1) of DFARs.  Notwithstanding the
 *	foregoing, the authors grant the U.S. Government and others
 *	acting in its behalf permission to use and distribute the
 *	software in accordance with the terms specified in this
 *	license.
 *
 * RCS: @(#) $Id: tkMacOSXKeyEvent.c,v 1.24.2.2 2010/01/02 10:43:26 dkf Exp $
 */

#include "tkMacOSXPrivate.h"
#include "tkMacOSXEvent.h"

/*
#ifdef TK_MAC_DEBUG
#define TK_MAC_DEBUG_KEYBOARD
#endif
*/

typedef struct {
    WindowRef whichWindow;
    int global_x, global_y;
    int local_x, local_y;
    unsigned int state;
    UInt32 keyCode;
    UInt32 keyModifiers;
    UInt32 message;
    unsigned char ch;
} KeyEventData;

static Tk_Window grabWinPtr = NULL;
				/* Current grab window, NULL if no grab. */
static Tk_Window keyboardGrabWinPtr = NULL;
				/* Current keyboard grab window. */
static UInt32 deadKeyStateUp = 0;
				/* The deadkey state for the current sequence
				 * of keyup events or 0 if not in a deadkey
				 * sequence */
static UInt32 deadKeyStateDown = 0;
				/* Ditto for keydown */

/*
 * Declarations for functions used only in this file.
 */

static int InitKeyData(KeyEventData *keyEventDataPtr);
static int InitKeyEvent(XEvent *eventPtr, KeyEventData *e, UInt32 savedKeyCode,
    UInt32 savedModifiers);
static int GenerateKeyEvent(UInt32 eKind, KeyEventData *e, UInt32 savedKeyCode,
    UInt32 savedModifiers, const UniChar *chars, int numChars);
static int GetKeyboardLayout(Ptr *resourcePtr, TextEncoding *encodingPtr);
static TextEncoding GetKCHREncoding(ScriptCode script, SInt32 layoutid);
static int KeycodeToUnicodeViaUnicodeResource(UniChar *uniChars, int maxChars,
    Ptr uchr, EventKind eKind, UInt32 keycode, UInt32 modifiers,
    UInt32 *deadKeyStatePtr);
static int KeycodeToUnicodeViaKCHRResource(UniChar *uniChars, int maxChars,
    Ptr kchr, TextEncoding encoding, EventKind eKind, UInt32 keycode,
    UInt32 modifiers, UInt32 *deadKeyStatePtr);


/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXProcessKeyboardEvent --
 *
 *	This routine processes the event in eventPtr, and
 *	generates the appropriate Tk events from it.
 *
 * Results:
 *	True if event(s) are generated - false otherwise.
 *
 * Side effects:
 *	Additional events may be place on the Tk event queue.
 *
 *----------------------------------------------------------------------
 */

MODULE_SCOPE int
TkMacOSXProcessKeyboardEvent(
    TkMacOSXEvent *eventPtr,
    MacEventStatus *statusPtr)
{
    static UInt32 savedKeyCode = 0;
    static UInt32 savedModifiers = 0;
    static UniChar savedChar = 0;
    OSStatus err;
    KeyEventData keyEventData;
    MenuRef menuRef;
    MenuItemIndex menuItemIndex;
    int eventGenerated;
    UniChar uniChars[5]; /* make this larger, if needed */
    UInt32 uniCharsLen = 0;

    if (!InitKeyData(&keyEventData)) {
	statusPtr->err = 1;
	return false;
    }

    /*
     * Because of the way that Tk operates, we can't in general funnel menu
     * accelerators through IsMenuKeyEvent. Tk treats accelerators as mere
     * decoration, and the user has to install bindings to get them to fire.
     *
     * However, the only way to trigger the Hide & Hide Others functions
     * is by invoking the Menu command for Hide. So there is no nice way to
     * provide a Tk command to hide the app which would be available for a
     * binding. So I am going to hijack Command-H and Command-Shift-H
     * here, and run the menu commands. Since the HI Guidelines explicitly
     * reserve these for Hide, this isn't such a bad thing. Also, if you do
     * rebind Command-H to another menu item, Hide will lose its binding.
     *
     * Note that I don't really do anything at this point,
     * I just mark stopProcessing as 0 and return, and then the
     * RecieveAndProcessEvent code will dispatch the event to the default
     * handler.
     */

    if ((eventPtr->eKind == kEventRawKeyDown
	    || eventPtr->eKind == kEventRawKeyRepeat)
	    && IsMenuKeyEvent(tkCurrentAppleMenu, eventPtr->eventRef,
		    kMenuEventQueryOnly, &menuRef, &menuItemIndex)) {
	MenuCommand menuCmd;

	GetMenuItemCommandID (menuRef, menuItemIndex, &menuCmd);
	switch (menuCmd) {
	    case kHICommandHide:
	    case kHICommandHideOthers:
	    case kHICommandShowAll:
	    case kHICommandPreferences:
	    case kHICommandQuit:
		statusPtr->stopProcessing = 0;

		/*
		 * TODO: may not be on event on queue.
		 */

		return 0;
		break;
	    default:
		break;
	}
    }

    err = ChkErr(GetEventParameter, eventPtr->eventRef,
	    kEventParamKeyMacCharCodes, typeChar, NULL,
	    sizeof(keyEventData.ch), NULL, &keyEventData.ch);
    if (err != noErr) {
	statusPtr->err = 1;
	return false;
    }
    err = ChkErr(GetEventParameter, eventPtr->eventRef, kEventParamKeyCode,
	    typeUInt32, NULL, sizeof(keyEventData.keyCode), NULL,
	    &keyEventData.keyCode);
    if (err != noErr) {
	statusPtr->err = 1;
	return false;
    }
    err = ChkErr(GetEventParameter, eventPtr->eventRef,
	    kEventParamKeyModifiers, typeUInt32, NULL,
	    sizeof(keyEventData.keyModifiers), NULL,
	    &keyEventData.keyModifiers);
    if (err != noErr) {
	statusPtr->err = 1;
	return false;
    }

    switch (eventPtr->eKind) {
	case kEventRawKeyUp:
	case kEventRawKeyDown:
	case kEventRawKeyRepeat: {
	    UInt32 *deadKeyStatePtr;

	    if (kEventRawKeyDown == eventPtr->eKind) {
		deadKeyStatePtr = &deadKeyStateDown;
	    } else {
		deadKeyStatePtr = &deadKeyStateUp;
	    }

	    uniCharsLen = TkMacOSXKeycodeToUnicode(uniChars,
		    sizeof(uniChars)/sizeof(*uniChars), eventPtr->eKind,
		    keyEventData.keyCode, keyEventData.keyModifiers,
		    deadKeyStatePtr);
	    break;
	}
    }

    if (kEventRawKeyUp == eventPtr->eKind) {
	/*
	 * For some reason the deadkey processing for KeyUp doesn't work
	 * sometimes, so we fudge and use the last detected KeyDown.
	 */

	if ((0 == uniCharsLen) && (0 != savedChar)) {
	    uniChars[0] = savedChar;
	    uniCharsLen = 1;
	}

	/*
	 * Suppress keyup events while we have a deadkey sequence on keydown.
	 * We still *do* want to collect deadkey state in this situation if
	 * the system provides it, that's why we do this only after
	 * TkMacOSXKeycodeToUnicode().
	 */

	if (0 != deadKeyStateDown) {
	    uniCharsLen = 0;
	}
    }

    keyEventData.message = keyEventData.ch|(keyEventData.keyCode << 8);

    eventGenerated = GenerateKeyEvent(eventPtr->eKind, &keyEventData,
	    savedKeyCode, savedModifiers, uniChars, uniCharsLen);

    savedModifiers = keyEventData.keyModifiers;

    if ((kEventRawKeyDown == eventPtr->eKind) && (uniCharsLen > 0)) {
	savedChar = uniChars[0];
    } else {
	savedChar = 0;
    }

    statusPtr->stopProcessing = 1;

    if (eventGenerated == 0) {
	savedKeyCode = keyEventData.message;
	return false;
    } else if (eventGenerated == -1) {
	savedKeyCode = 0;
	statusPtr->stopProcessing = 0;
	return false;
    } else {
	savedKeyCode = 0;
	return true;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GenerateKeyEvent --
 *
 *	Given Macintosh keyUp, keyDown & autoKey events (in their "raw"
 *	form) and a list of unicode characters this function generates the
 *	appropriate X key events.
 *
 *	Parameter eKind is a raw keyboard event. e contains the data sent
 *	with the event. savedKeyCode and savedModifiers contain the values
 *	from the last event that came before (see
 *	TkMacOSXProcessKeyboardEvent()). chars/numChars has the Unicode
 *	characters for which we want to create events.
 *
 * Results:
 *	1 if an event was generated, -1 for any error.
 *
 * Side effects:
 *	Additional events may be place on the Tk event queue.
 *
 *----------------------------------------------------------------------
 */

static int
GenerateKeyEvent(
    UInt32 eKind,
    KeyEventData * e,
    UInt32 savedKeyCode,
    UInt32 savedModifiers,
    const UniChar * chars,
    int numChars)
{
    XEvent event;
    int i;

    if (-1 == InitKeyEvent(&event, e, savedKeyCode, savedModifiers)) {
	return -1;
    }

    if (kEventRawKeyModifiersChanged == eKind) {
	if (savedModifiers > e->keyModifiers) {
	    event.xany.type = KeyRelease;
	} else {
	    event.xany.type = KeyPress;
	}

	/*
	 * Use special '-1' to signify a special keycode to our
	 * platform specific code in tkMacOSXKeyboard.c. This is
	 * rather like what happens on Windows.
	 */

	event.xany.send_event = -1;

	/*
	 * Set keycode (which was zero) to the changed modifier
	 */

	event.xkey.keycode = (e->keyModifiers ^ savedModifiers);
	Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);

    } else {
	for (i = 0; i < numChars; ++i) {
	    /*
	     * Encode one char in the trans_chars array that was already
	     * introduced for MS Windows. Don't encode the string, if it is
	     * a control character but was not generated with a real control
	     * modifier. Such control characters get generated by KeyTrans()
	     * for special keys, but we rather want to identify those by
	     * their KeySyms.
	     */

	    event.xkey.trans_chars[0] = 0;
	    if ((controlKey & e->keyModifiers) || (chars[i] >= ' ')) {
		int done;
		done = Tcl_UniCharToUtf(chars[i],event.xkey.trans_chars);
		event.xkey.trans_chars[done] = 0;
	    }

	    switch(eKind) {
		case kEventRawKeyDown:
		    event.xany.type = KeyPress;
		    Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
		    break;
		case kEventRawKeyUp:
		    event.xany.type = KeyRelease;
		    Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
		    break;
		case kEventRawKeyRepeat:
		    event.xany.type = KeyRelease;
		    Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
		    event.xany.type = KeyPress;
		    Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
		    break;
		default:
		    TkMacOSXDbgMsg("Invalid parameter eKind %ld", eKind);
		    return -1;
	    }
	}
    }

    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * InitKeyData --
 *
 *	This routine initializes a KeyEventData structure by asking the OS
 *	and Tk for all the global information needed here.
 *
 * Results:
 *	True if the current front window can be found in Tk data structures
 *	- false otherwise.
 *
 * Side Effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
InitKeyData(
    KeyEventData *keyEventDataPtr)
{
    memset(keyEventDataPtr, 0, sizeof(*keyEventDataPtr));

    keyEventDataPtr->whichWindow = ActiveNonFloatingWindow();
    if (keyEventDataPtr->whichWindow == NULL) {
	return false;
    }
    XQueryPointer(NULL, None, NULL, NULL, &keyEventDataPtr->global_x,
	    &keyEventDataPtr->global_y, &keyEventDataPtr->local_x,
	    &keyEventDataPtr->local_y, &keyEventDataPtr->state);

    return true;
}

/*
 *----------------------------------------------------------------------
 *
 * InitKeyEvent --
 *
 *	Initialize an XEvent structure by asking Tk for global information.
 *	Also uses a KeyEventData structure and other current state.
 *
 * Results:
 *	1 on success, -1 for any error.
 *
 * Side effects:
 *	Additional events may be place on the Tk event queue.
 *
 *----------------------------------------------------------------------
 */

/*
 * We have a general problem here. How do we handle 'Option-char'
 * keypresses?	The problem is that we might want to bind to some of these
 * (e.g. Cmd-Opt-d is 'uncomment' in Alpha). OTOH Option-d actually produces
 * a real character on MacOS, namely a mathematical delta.
 *
 * The current behaviour is that a binding goes by the combinations of
 * modifiers and base keysym, that is Option-d. The string value of the
 * event is the mathematical delta character, so if no binding calls
 * [break], the text widget will insert that character.
 *
 * Note that this is similar to control combinations on all platforms. They
 * also generate events that have the base character as keysym and a real
 * control character as character value. So Ctrl+C gets us the keysym XK_C,
 * the modifier Control (so you can bind <Control-C>) and a string value as
 * "\u0003".
 *
 * For a different solutions we may want for the event to contain keysyms for
 * *both* the 'Opt-d' side of things and the mathematical delta. Then a
 * binding on Opt-d will trigger, but a binding on mathematical delta would
 * also trigger. This would require changes in the core, though.
 */

static int
InitKeyEvent(
    XEvent * eventPtr,
    KeyEventData * e,
    UInt32 savedKeyCode,
    UInt32 savedModifiers)
{
    Window window;
    Tk_Window tkwin;
    TkDisplay *dispPtr;

    /*
     * The focus must be in the FrontWindow on the Macintosh.
     * We then query Tk to determine the exact Tk window
     * that owns the focus.
     */

    window = TkMacOSXGetXWindow(e->whichWindow);
    dispPtr = TkGetDisplayList();
    tkwin = Tk_IdToWindow(dispPtr->display, window);

    if (!tkwin) {
	TkMacOSXDbgMsg("tkwin == NULL");
	return -1;
    }

    tkwin = (Tk_Window) ((TkWindow *) tkwin)->dispPtr->focusPtr;
    if (!tkwin) {
	TkMacOSXDbgMsg("tkwin == NULL");
	return -1;
    }

    memset(eventPtr, 0, sizeof(XEvent));
    eventPtr->xany.send_event = false;
    eventPtr->xany.serial = Tk_Display(tkwin)->request;

    eventPtr->xkey.same_screen = true;
    eventPtr->xkey.subwindow = None;
    eventPtr->xkey.time = TkpGetMS();
    eventPtr->xkey.x_root = e->global_x;
    eventPtr->xkey.y_root = e->global_y;
    eventPtr->xkey.window = Tk_WindowId(tkwin);
    eventPtr->xkey.display = Tk_Display(tkwin);
    eventPtr->xkey.root = XRootWindow(Tk_Display(tkwin), 0);
    eventPtr->xkey.state =  e->state;
    eventPtr->xkey.trans_chars[0] = 0;

    Tk_TopCoordsToWindow(tkwin, e->local_x, e->local_y, &eventPtr->xkey.x,
	    &eventPtr->xkey.y);

    eventPtr->xkey.keycode = e->ch | ((savedKeyCode & charCodeMask) << 8) |
	    ((e->message&keyCodeMask) << 8);

    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * GetKeyboardLayout --
 *
 *	Queries the OS for a pointer to a keyboard resource.
 *
 *	This function works with the keyboard layout switch menu. It uses
 *	Keyboard Layout Services, where available.
 *
 * Results:
 *	1 if there is returned a Unicode 'uchr' resource in *resourcePtr, 0
 *	if it is a classic 'KCHR' resource. A pointer to the actual resource
 *	data goes into *resourcePtr. If the resource is a 'KCHR' resource,
 *	the corresponding Mac encoding goes into *encodingPtr.
 *
 * Side effects:
 *	Sets some internal static variables.
 *
 *----------------------------------------------------------------------
 */

static int
GetKeyboardLayout(
    Ptr *resourcePtr,
    TextEncoding *encodingPtr)
{
    static KeyboardLayoutRef lastLayout = NULL;
    static SInt32 lastLayoutId;
    static TextEncoding lastEncoding = kTextEncodingMacRoman;
    static Ptr uchr = NULL;
    static Ptr KCHR = NULL;
    int hasLayoutChanged = false;
    KeyboardLayoutRef currentLayout = NULL;
    SInt32 currentLayoutId = 0;
    ScriptCode currentKeyScript;

    currentKeyScript = GetScriptManagerVariable(smKeyScript);

    /*
     * Use the Keyboard Layout Services.
     */

    KLGetCurrentKeyboardLayout(&currentLayout);

    if (currentLayout != NULL) {

	/*
	 * The layout pointer could in theory be the same for different
	 * layouts, only the id gives us the information that the
	 * keyboard has actually changed. OTOH the layout object can
	 * also change and it could still be the same layoutid.
	 */

	KLGetKeyboardLayoutProperty(currentLayout, kKLIdentifier,
		(const void**)&currentLayoutId);

	if ((lastLayout != currentLayout)
		|| (lastLayoutId != currentLayoutId)) {

#ifdef TK_MAC_DEBUG_KEYBOARD
	    TkMacOSXDbgMsg("Use KLS");
#endif

	    hasLayoutChanged = true;

	    /*
	     * Reinitialize all relevant variables.
	     */

	    lastLayout = currentLayout;
	    lastLayoutId = currentLayoutId;
	    uchr = NULL;
	    KCHR = NULL;

	    if ((KLGetKeyboardLayoutProperty(currentLayout,
				    kKLuchrData, (const void**)&uchr)
			    == noErr)
		    && (uchr != NULL)) {
		/* done */
	    } else if ((KLGetKeyboardLayoutProperty(currentLayout,
				    kKLKCHRData, (const void**)&KCHR)
			    == noErr)
		    && (KCHR != NULL)) {
		/* done */
	    }
	}
    }

    if (hasLayoutChanged) {
#ifdef TK_MAC_DEBUG_KEYBOARD
	if (KCHR) {
	    TkMacOSXDbgMsg("New 'KCHR' layout %ld", currentLayoutId);
	} else if (uchr) {
	    TkMacOSXDbgMsg("New 'uchr' layout %ld", currentLayoutId);
	} else {
	    TkMacOSXDbgMsg("Use cached layout (should have been %ld)",
		    currentLayoutId);
	}
#endif

	deadKeyStateUp = deadKeyStateDown = 0;

	/*
	 * If we did get a new 'KCHR', compute its encoding and put it into
	 * lastEncoding.
	 *
	 * If we didn't get a new 'KCHR' and if we have no 'uchr' either, get
	 * some 'KCHR' from the OS cache and leave lastEncoding at its
	 * current value. This should better not happen, it doesn't really
	 * work.
	 */

	if (KCHR) {
	    lastEncoding = GetKCHREncoding(currentKeyScript, currentLayoutId);
#ifdef TK_MAC_DEBUG_KEYBOARD
	    TkMacOSXDbgMsg("New 'KCHR' encoding %lu (%lu + 0x%lX)",
		    lastEncoding, lastEncoding & 0xFFFFL,
		    lastEncoding & ~0xFFFFL);
#endif
	} else if (!uchr) {
	    KCHR = (Ptr)(intptr_t)GetScriptManagerVariable(smKCHRCache);
	}
    }

    if (uchr) {
	*resourcePtr = uchr;
	return 1;
    } else {
	*resourcePtr = KCHR;
	*encodingPtr = lastEncoding;
	return 0;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GetKCHREncoding --
 *
 *	Upgrade a WorldScript code to a TEC encoding based on the keyboard
 *	layout id.
 *
 * Results:
 *	The TEC code that corresponds best to the combination of WorldScript
 *	code and 'KCHR' id.
 *
 * Side effects:
 *	None.
 *
 * Rationale and Notes:
 *	WorldScript codes are sometimes not unique encodings. E.g. Icelandic
 *	uses script smRoman (0), but the actual encoding is
 *	kTextEncodingMacIcelandic (37). ftp://ftp.unicode.org/Public
 *	/MAPPINGS/VENDORS/APPLE/README.TXT has a good summary of these
 *	variants. So we need to upgrade the script to an encoding with
 *	GetTextEncodingFromScriptInfo().
 *
 *	'KCHR' ids are usually region codes (see the comments in Script.h).
 *	Where they are not, we get a paramErr from the OS function and have
 *	appropriate fallbacks.
 *
 *----------------------------------------------------------------------
 */

static TextEncoding
GetKCHREncoding(
    ScriptCode script,
    SInt32 layoutid)
{
    RegionCode region = layoutid;
    TextEncoding encoding = script;

    if (GetTextEncodingFromScriptInfo(script, kTextLanguageDontCare, region,
	    &encoding) == noErr) {
	return encoding;
    }

    /*
     * GetTextEncodingFromScriptInfo() doesn't know about more exotic
     * layouts. This provides a fallback for good measure. In an ideal
     * world, exotic layouts would always provide a 'uchr' resource anyway,
     * so we wouldn't need this.
     *
     * We can add more keyboard layouts, if we get actual complaints. Farsi
     * or other Celtic/Gaelic layouts would be candidates.
     */

    switch (layoutid) {
	/*
	 * Icelandic and Faroese (planned). These layouts are sold by Apple
	 * Iceland for legacy applications.
	 */

	case 1800: case 1821:
	    return kTextEncodingMacIcelandic;

	/*
	 * Irish and Welsh. These layouts are mentioned in <Script.h>.
	 *
	 * FIXME: This may have to be kTextEncodingMacGaelic instead, but I
	 * can't locate layouts of this type for testing.
	 */

	case 581: case 779:
	    return kTextEncodingMacCeltic;
    }

    /*
     * The valid script codes are also the valid default encoding codes, so
     * if nothing else helps, fall back on those.
     */

    return script;
}

/*
 *----------------------------------------------------------------------
 *
 * KeycodeToUnicodeViaUnicodeResource --
 *
 *	Given MacOS key event data this function generates the Unicode
 *	characters. It does this using a 'uchr' and the UCKeyTranslate
 *	API.
 *
 *	The parameter deadKeyStatePtr can be NULL, if no deadkey handling
 *	is needed.
 *
 *	Tested and known to work with US, Hebrew, Greek and Russian layouts
 *	as well as "Unicode Hex Input".
 *
 * Results:
 *	The number of characters generated if any, 0 if we are waiting for
 *	another byte of a dead-key sequence. Fills in the uniChars array
 *	with a Unicode string.
 *
 * Side Effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
KeycodeToUnicodeViaUnicodeResource(
    UniChar *uniChars,
    int maxChars,
    Ptr uchr,
    EventKind eKind,
    UInt32 keycode,
    UInt32 modifiers,
    UInt32 *deadKeyStatePtr)
{
    int action;
    unsigned long keyboardType;
    OptionBits options = 0;
    UInt32 dummy_state;
    UniCharCount actuallength;
    OSStatus err;

    keycode &= 0xFF;
    modifiers = (modifiers >> 8) & 0xFF;
    keyboardType = LMGetKbdType();

    if (NULL==deadKeyStatePtr) {
	options = kUCKeyTranslateNoDeadKeysMask;
	dummy_state = 0;
	deadKeyStatePtr = &dummy_state;
    }

    switch(eKind) {
	case kEventRawKeyDown:
	    action = kUCKeyActionDown;
	    break;
	case kEventRawKeyUp:
	    action = kUCKeyActionUp;
	    break;
	case kEventRawKeyRepeat:
	    action = kUCKeyActionAutoKey;
	    break;
	default:
	    TkMacOSXDbgMsg("Invalid parameter eKind %d", eKind);
	    return 0;
    }

    err = ChkErr(UCKeyTranslate, (const UCKeyboardLayout *) uchr, keycode,
	    action, modifiers, keyboardType, options, deadKeyStatePtr,
	    maxChars, &actuallength, uniChars);

    if ((0 == actuallength) && (0 != *deadKeyStatePtr)) {
	/*
	 * More data later
	 */

	return 0;
    }

    /*
     * some IMEs leave residue :-(
     */

    *deadKeyStatePtr = 0;

    if (err != noErr) {
	actuallength = 0;
    }

    return actuallength;
}

/*
 *----------------------------------------------------------------------
 *
 * KeycodeToUnicodeViaKCHRResource --
 *
 *	Given MacOS key event data this function generates the Unicode
 *	characters. It does this using a 'KCHR' and the KeyTranslate API.
 *
 *	The parameter deadKeyStatePtr can be NULL, if no deadkey handling
 *	is needed.
 *
 * Results:
 *	The number of characters generated if any, 0 if we are waiting for
 *	another byte of a dead-key sequence. Fills in the uniChars array
 *	with a Unicode string.
 *
 * Side Effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
KeycodeToUnicodeViaKCHRResource(
    UniChar *uniChars,
    int maxChars,
    Ptr kchr,
    TextEncoding encoding,
    EventKind eKind,
    UInt32 keycode,
    UInt32 modifiers,
    UInt32 *deadKeyStatePtr)
{
    UInt32 result;
    char macBuff[3];
    char *macStr;
    int macStrLen;
    UInt32 dummy_state = 0;

    if (NULL == deadKeyStatePtr) {
	deadKeyStatePtr = &dummy_state;
    }

    keycode |= modifiers;
    result = KeyTranslate(kchr, keycode, deadKeyStatePtr);

    if ((0 == result) && (0 != dummy_state)) {
	/*
	 * 'dummy_state' gets only filled if the caller did not want deadkey
	 * processing (deadKeyStatePtr was NULL originally), but we still
	 * have a deadkey. We just push the keycode for the space bar to get
	 * the real key value.
	 */

	result = KeyTranslate(kchr, 0x31, deadKeyStatePtr);
	*deadKeyStatePtr = 0;
    }

    if ((0 == result) && (0 != *deadKeyStatePtr)) {
	/*
	 * More data later
	 */

	return 0;
    }

    macBuff[0] = (char) (result >> 16);
    macBuff[1] = (char)	 result;
    macBuff[2] = 0;

    if (0 != macBuff[0]) {
	/*
	 * If the first byte is valid, the second is too
	 */

	macStr = macBuff;
	macStrLen = 2;
    } else if (0 != macBuff[1]) {
	/*
	 * Only the second is valid
	 */

	macStr = macBuff+1;
	macStrLen = 1;
    } else {
	/*
	 * No valid bytes at all -- shouldn't happen
	 */

	macStr = NULL;
	macStrLen = 0;
    }

    if (macStrLen <= 0) {
	return 0;
    } else {

	/*
	 * Use the CFString conversion routines. This is the easiest and
	 * most compatible way to get from an 8-bit string and a MacOS script
	 * code to a Unicode string.
	 *
	 * FIXME: The system ships with an Irish 'KCHR' but without the
	 * corresponding macCeltic encoding, which triggers the error below.
	 * Tcl doesn't have the macCeltic encoding either right now, so until
	 * we get that, we can just as well stick to this code. The right
	 * fix would be to use the Tcl encodings and add macCeltic and
	 * probably others there. Suitable Unicode data files for the
	 * missing encodings are available from www.evertype.com.
	 */

	CFStringRef cfString;
	int uniStrLen;

	cfString = CFStringCreateWithCStringNoCopy(NULL, macStr, encoding,
		kCFAllocatorNull);
	if (cfString == NULL) {
	    TkMacOSXDbgMsg("CFString: Can't convert with encoding %ld",
		    encoding);
	    return 0;
	}

	uniStrLen = CFStringGetLength(cfString);
	if (uniStrLen > maxChars) {
	    uniStrLen = maxChars;
	}
	CFStringGetCharacters(cfString, CFRangeMake(0,uniStrLen), uniChars);
	CFRelease(cfString);

	return uniStrLen;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXKeycodeToUnicode --
 *
 *	Given MacOS key event data this function generates the Unicode
 *	characters. It does this using OS resources and APIs.
 *
 *	The parameter deadKeyStatePtr can be NULL, if no deadkey handling
 *	is needed.
 *
 *	This function is called from XKeycodeToKeysym() in
 *	tkMacOSKeyboard.c.
 *
 * Results:
 *	The number of characters generated if any, 0 if we are waiting for
 *	another byte of a dead-key sequence. Fills in the uniChars array
 *	with a Unicode string.
 *
 * Side Effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

MODULE_SCOPE int
TkMacOSXKeycodeToUnicode(
    UniChar *uniChars,
    int maxChars,
    EventKind eKind,
    UInt32 keycode,
    UInt32 modifiers,
    UInt32 *deadKeyStatePtr)
{
    Ptr resource = NULL;
    TextEncoding encoding;
    int len;


    if (GetKeyboardLayout(&resource,&encoding)) {
	len = KeycodeToUnicodeViaUnicodeResource(
	    uniChars, maxChars, resource, eKind,
	    keycode, modifiers, deadKeyStatePtr);
    } else {
	len = KeycodeToUnicodeViaKCHRResource(
	    uniChars, maxChars, resource, encoding, eKind,
	    keycode, modifiers, deadKeyStatePtr);
    }

    return len;
}

/*
 *----------------------------------------------------------------------
 *
 * XGrabKeyboard --
 *
 *	Simulates a keyboard grab by setting the focus.
 *
 * Results:
 *	Always returns GrabSuccess.
 *
 * Side effects:
 *	Sets the keyboard focus to the specified window.
 *
 *----------------------------------------------------------------------
 */

int
XGrabKeyboard(
    Display* display,
    Window grab_window,
    Bool owner_events,
    int pointer_mode,
    int keyboard_mode,
    Time time)
{
    keyboardGrabWinPtr = Tk_IdToWindow(display, grab_window);
    return GrabSuccess;
}

/*
 *----------------------------------------------------------------------
 *
 * XUngrabKeyboard --
 *
 *	Releases the simulated keyboard grab.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Sets the keyboard focus back to the value before the grab.
 *
 *----------------------------------------------------------------------
 */

void
XUngrabKeyboard(
    Display* display,
    Time time)
{
    keyboardGrabWinPtr = NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXGetCapture --
 *
 * Results:
 *	Returns the current grab window
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

Tk_Window
TkMacOSXGetCapture(void)
{
    return grabWinPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TkpSetCapture --
 *
 *	This function captures the mouse so that all future events
 *	will be reported to this window, even if the mouse is outside
 *	the window. If the specified window is NULL, then the mouse
 *	is released.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Sets the capture flag and captures the mouse.
 *
 *----------------------------------------------------------------------
 */

void
TkpSetCapture(
    TkWindow *winPtr)		/* Capture window, or NULL. */
{
    while (winPtr && !Tk_IsTopLevel(winPtr)) {
	winPtr = winPtr->parentPtr;
    }
#if 0
    {
	TkWindow *w = NULL;
	WindowModality m;

	if (winPtr) {
	    w = winPtr;
	    m = kWindowModalityAppModal;
	} else if (grabWinPtr) {
	    w = (TkWindow*)grabWinPtr;
	    m = kWindowModalityNone;
	}
	if (w && w->window != None && TkMacOSXHostToplevelExists(w)) {
	    ChkErr(SetWindowModality, TkMacOSXDrawableWindow(w->window), m,
		    NULL);
	}
    }
#endif
    grabWinPtr = (Tk_Window) winPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_SetCaretPos --
 *
 *	This enables correct placement of the XIM caret. This is called
 *	by widgets to indicate their cursor placement, and the caret
 *	location is used by TkpGetString to place the XIM caret.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

void
Tk_SetCaretPos(
    Tk_Window tkwin,
    int x,
    int y,
    int height)
{
}

/*
 *----------------------------------------------------------------------
 *
 * TkMacOSXInitKeyboard --
 *
 *	This procedure initializes the keyboard layout.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

MODULE_SCOPE void
TkMacOSXInitKeyboard(
    Tcl_Interp *interp)
{
    Ptr resource;
    TextEncoding encoding;

    GetKeyboardLayout(&resource, &encoding);
}
