/*
 * tkWinKey.c --
 *
 *	This file contains X emulation routines for keyboard related
 *	functions.
 *
 * Copyright (c) 1995 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tkWinInt.h"

/*
 * The keymap table holds mappings of Windows keycodes to X keysyms. If
 * Windows ever comes along and changes the value of their keycodes, this will
 * break all kinds of things. However, this table lookup is much faster than
 * the alternative, in which we walked a list of keycodes looking for a match.
 * Since this lookup is performed for every Windows keypress event, it seems
 * like a worthwhile improvement to use the table.
 */

#define MAX_KEYCODE 145 /* VK_SCROLL is the last entry in our table below */

static const KeySym keymap[] = {
    NoSymbol, NoSymbol, NoSymbol, XK_Cancel, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, XK_BackSpace, XK_Tab,
    NoSymbol, NoSymbol, XK_Clear, XK_Return, NoSymbol,
    NoSymbol, XK_Shift_L, XK_Control_L, XK_Alt_L, XK_Pause,
    XK_Caps_Lock, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, XK_Escape, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, XK_space, XK_Prior, XK_Next,
    XK_End, XK_Home, XK_Left, XK_Up, XK_Right,
    XK_Down, XK_Select, XK_Print, XK_Execute, NoSymbol,
    XK_Insert, XK_Delete, XK_Help, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, XK_Win_L, XK_Win_R, XK_App, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, XK_F1, XK_F2, XK_F3,
    XK_F4, XK_F5, XK_F6, XK_F7, XK_F8,
    XK_F9, XK_F10, XK_F11, XK_F12, XK_F13,
    XK_F14, XK_F15, XK_F16, XK_F17, XK_F18,
    XK_F19, XK_F20, XK_F21, XK_F22, XK_F23,
    XK_F24, NoSymbol, NoSymbol, NoSymbol, NoSymbol,
    NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_Num_Lock,
    XK_Scroll_Lock
};

/*
 * Prototypes for local functions defined in this file:
 */

static KeySym		KeycodeToKeysym(unsigned int keycode,
			    int state, int noascii);

/*
 *----------------------------------------------------------------------
 *
 * TkpGetString --
 *
 *	Retrieve the UTF string equivalent for the given keyboard event.
 *
 * Results:
 *	Returns the UTF string.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

const char *
TkpGetString(
    TkWindow *winPtr,		/* Window where event occurred: needed to get
				 * input context. */
    XEvent *eventPtr,		/* X keyboard event. */
    Tcl_DString *dsPtr)		/* Uninitialized or empty string to hold
				 * result. */
{
    XKeyEvent *keyEv = &eventPtr->xkey;

    Tcl_DStringInit(dsPtr);
    if (keyEv->send_event == -1) {
	if (keyEv->nbytes > 0) {
	    Tcl_ExternalToUtfDString(TkWinGetKeyInputEncoding(),
		    keyEv->trans_chars, keyEv->nbytes, dsPtr);
	}
    } else if (keyEv->send_event == -2) {
	/*
	 * Special case for win2000 multi-lingal IME input. xkey.trans_chars[]
	 * already contains a UNICODE char.
	 */

	int unichar;
	char buf[TCL_UTF_MAX];
	int len;

	unichar = keyEv->trans_chars[1] & 0xff;
	unichar <<= 8;
	unichar |= keyEv->trans_chars[0] & 0xff;

	len = Tcl_UniCharToUtf((Tcl_UniChar) unichar, buf);

	Tcl_DStringAppend(dsPtr, buf, len);
    } else if (keyEv->send_event == -3) {
	/*
	 * Special case for WM_UNICHAR. xkey.trans_chars[] already contains a
	 * UTF-8 char.
	 */

	Tcl_DStringAppend(dsPtr, keyEv->trans_chars, keyEv->nbytes);
    } else {
	/*
	 * This is an event generated from generic code. It has no nchars or
	 * trans_chars members.
	 */

	KeySym keysym = KeycodeToKeysym(keyEv->keycode, keyEv->state, 0);

	if (((keysym != NoSymbol) && (keysym > 0) && (keysym < 256))
		|| (keysym == XK_Return) || (keysym == XK_Tab)) {
	    char buf[TCL_UTF_MAX];
	    int len;

	    len = Tcl_UniCharToUtf((Tcl_UniChar) (keysym & 255), buf);
	    Tcl_DStringAppend(dsPtr, buf, len);
	}
    }
    return Tcl_DStringValue(dsPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * XKeycodeToKeysym --
 *
 *	Translate from a system-dependent keycode to a system-independent
 *	keysym.
 *
 * Results:
 *	Returns the translated keysym, or NoSymbol on failure.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

KeySym
XKeycodeToKeysym(
    Display *display,
    unsigned int keycode,
    int index)
{
    int state = 0;

    if (index & 0x01) {
	state |= ShiftMask;
    }
    return KeycodeToKeysym(keycode, state, 0);
}

/*
 *----------------------------------------------------------------------
 *
 * KeycodeToKeysym --
 *
 *	Translate from a system-dependent keycode to a system-independent
 *	keysym.
 *
 * Results:
 *	Returns the translated keysym, or NoSymbol on failure.
 *
 * Side effects:
 *	It may affect the internal state of the keyboard, such as remembered
 *	dead key or lock indicator lamps.
 *
 *----------------------------------------------------------------------
 */

static KeySym
KeycodeToKeysym(
    unsigned int keycode,
    int state,
    int noascii)
{
    BYTE keys[256];
    int result, deadkey, shift;
    char buf[4];
    unsigned int scancode = MapVirtualKey(keycode, 0);

    /*
     * Do not run keycodes of lock keys through ToAscii(). One of ToAscii()'s
     * side effects is to handle the lights on the keyboard, and we don't want
     * to mess that up.
     */

    if (noascii || keycode == VK_CAPITAL || keycode == VK_SCROLL ||
	    keycode == VK_NUMLOCK) {
	goto skipToAscii;
    }

    /*
     * Use MapVirtualKey() to detect some dead keys.
     */

    if (MapVirtualKey(keycode, 2) > 0x7fffUL) {
	return XK_Multi_key;
    }

    /*
     * Set up a keyboard with correct modifiers
     */

    memset(keys, 0, 256);
    if (state & ShiftMask) {
	keys[VK_SHIFT] = 0x80;
    }
    if (state & ControlMask) {
	keys[VK_CONTROL] = 0x80;
    }
    if (state & Mod2Mask) {
	keys[VK_MENU] = 0x80;
    }

    /*
     * Make sure all lock button info is correct so we don't mess up the
     * lights.
     */

    if (state & LockMask) {
	keys[VK_CAPITAL] = 1;
    }
    if (state & Mod3Mask) {
	keys[VK_SCROLL] = 1;
    }
    if (state & Mod1Mask) {
	keys[VK_NUMLOCK] = 1;
    }

    result = ToAscii(keycode, scancode, keys, (LPWORD) buf, 0);

    if (result < 0) {
	/*
	 * Win95/98: This was a dead char, which is now remembered by the
	 * keyboard. Call ToAscii() again to forget it.
	 * WinNT: This was a dead char, overwriting any previously remembered
	 * key. Calling ToAscii() again does not affect anything.
	 */

	ToAscii(keycode, scancode, keys, (LPWORD) buf, 0);
	return XK_Multi_key;
    }

    if (result == 2) {
	/*
	 * This was a dead char, and there were one previously remembered by
	 * the keyboard. Call ToAscii() again with proper parameters to
	 * restore it.
	 *
	 * Get information about the old char
	 */

	deadkey = VkKeyScan(buf[0]);
	shift = deadkey >> 8;
	deadkey &= 255;
	scancode = MapVirtualKey(deadkey, 0);

	/*
	 * Set up a keyboard with proper modifier keys
	 */

	memset(keys, 0, 256);
	if (shift & 1) {
	    keys[VK_SHIFT] = 0x80;
	}
	if (shift & 2) {
	    keys[VK_CONTROL] = 0x80;
	}
	if (shift & 4) {
	    keys[VK_MENU] = 0x80;
	}
	ToAscii(deadkey, scancode, keys, (LPWORD) buf, 0);
	return XK_Multi_key;
    }

    /*
     * Keycode mapped to a valid Latin-1 character. Since the keysyms for
     * alphanumeric characters map onto Latin-1, we just return it.
     *
     * We treat 0x7F as a special case mostly for backwards compatibility. In
     * versions of Tk<=8.2, Control-Backspace returned "XK_BackSpace" as the X
     * Keysym. This was due to the fact that we did not initialize the keys
     * array properly when we passed it to ToAscii, above. We had previously
     * not been setting the state bit for the Control key. When we fixed that,
     * we found that Control-Backspace on Windows is interpreted as ASCII-127
     * (0x7F), which corresponds to the Delete key.
     *
     * Upon discovering this, we realized we had two choices: return XK_Delete
     * or return XK_BackSpace. If we returned XK_Delete, that could be
     * considered "more correct" (although the correctness would be dependant
     * on whether you believe that ToAscii is doing the right thing in that
     * case); however, this would break backwards compatibility, and worse, it
     * would limit application programmers; they would effectively be unable
     * to bind to <Control-Backspace> on Windows. We therefore chose instead
     * to return XK_BackSpace (handled here by letting the code "fall-through"
     * to the return statement below, which works because the keycode for this
     * event is VK_BACKSPACE, and the keymap table maps that keycode to
     * XK_BackSpace).
     */

    if (result == 1 && UCHAR(buf[0]) >= 0x20 && UCHAR(buf[0]) != 0x7F) {
	return (KeySym) UCHAR(buf[0]);
    }

    /*
     * Keycode is a non-alphanumeric key, so we have to do the lookup.
     */

  skipToAscii:
    if (keycode > MAX_KEYCODE) {
	return NoSymbol;
    }
    switch (keycode) {
	/*
	 * Windows only gives us an undifferentiated VK_CONTROL code (for
	 * example) when either Control key is pressed. To distinguish between
	 * left and right, we have to query the state of one of the two to
	 * determine which was actually pressed. So if the keycode indicates
	 * Control, Shift, or Menu (the key that everybody else calls Alt), do
	 * this extra test. If the right-side key was pressed, return the
	 * appropriate keycode. Otherwise, we fall through and rely on the
	 * keymap table to hold the correct keysym value.
	 */

    case VK_CONTROL:
	if (GetKeyState(VK_RCONTROL) & 0x80) {
	    return XK_Control_R;
	}
	break;
    case VK_SHIFT:
	if (GetKeyState(VK_RSHIFT) & 0x80) {
	    return XK_Shift_R;
	}
	break;
    case VK_MENU:
	if (GetKeyState(VK_RMENU) & 0x80) {
	    return XK_Alt_R;
	}
	break;
    }
    return keymap[keycode];
}

/*
 *----------------------------------------------------------------------
 *
 * TkpGetKeySym --
 *
 *	Given an X KeyPress or KeyRelease event, map the keycode in the event
 *	into a KeySym.
 *
 * Results:
 *	The return value is the KeySym corresponding to eventPtr, or NoSymbol
 *	if no matching Keysym could be found.
 *
 * Side effects:
 *	In the first call for a given display, keycode-to-KeySym maps get
 *	loaded.
 *
 *----------------------------------------------------------------------
 */

KeySym
TkpGetKeySym(
    TkDisplay *dispPtr,		/* Display in which to map keycode. */
    XEvent *eventPtr)		/* Description of X event. */
{
    KeySym sym;
    int state = eventPtr->xkey.state;

    /*
     * Refresh the mapping information if it's stale
     */

    if (dispPtr->bindInfoStale) {
	TkpInitKeymapInfo(dispPtr);
    }

    sym = KeycodeToKeysym(eventPtr->xkey.keycode, state, 0);

    /*
     * Special handling: if this is a ctrl-alt or shifted key, and there is no
     * keysym defined, try without the modifiers.
     */

    if ((sym == NoSymbol) && ((state & ControlMask) || (state & Mod2Mask))) {
	state &= ~(ControlMask | Mod2Mask);
	sym = KeycodeToKeysym(eventPtr->xkey.keycode, state, 0);
    }
    if ((sym == NoSymbol) && (state & ShiftMask)) {
	state &= ~ShiftMask;
	sym = KeycodeToKeysym(eventPtr->xkey.keycode, state, 0);
    }
    return sym;
}

/*
 *--------------------------------------------------------------
 *
 * TkpInitKeymapInfo --
 *
 *	This function is invoked to scan keymap information to recompute stuff
 *	that's important for binding, such as the modifier key (if any) that
 *	corresponds to "mode switch".
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Keymap-related information in dispPtr is updated.
 *
 *--------------------------------------------------------------
 */

void
TkpInitKeymapInfo(
    TkDisplay *dispPtr)		/* Display for which to recompute keymap
				 * information. */
{
    XModifierKeymap *modMapPtr;
    KeyCode *codePtr;
    KeySym keysym;
    int count, i, j, max, arraySize;
#define KEYCODE_ARRAY_SIZE 20

    dispPtr->bindInfoStale = 0;
    modMapPtr = XGetModifierMapping(dispPtr->display);

    /*
     * Check the keycodes associated with the Lock modifier. If any of them is
     * associated with the XK_Shift_Lock modifier, then Lock has to be
     * interpreted as Shift Lock, not Caps Lock.
     */

    dispPtr->lockUsage = LU_IGNORE;
    codePtr = modMapPtr->modifiermap + modMapPtr->max_keypermod*LockMapIndex;
    for (count = modMapPtr->max_keypermod; count > 0; count--, codePtr++) {
	if (*codePtr == 0) {
	    continue;
	}
	keysym = KeycodeToKeysym(*codePtr, 0, 1);
	if (keysym == XK_Shift_Lock) {
	    dispPtr->lockUsage = LU_SHIFT;
	    break;
	}
	if (keysym == XK_Caps_Lock) {
	    dispPtr->lockUsage = LU_CAPS;
	    break;
	}
    }

    /*
     * Look through the keycodes associated with modifiers to see if the the
     * "mode switch", "meta", or "alt" keysyms are associated with any
     * modifiers. If so, remember their modifier mask bits.
     */

    dispPtr->modeModMask = 0;
    dispPtr->metaModMask = 0;
    dispPtr->altModMask = 0;
    codePtr = modMapPtr->modifiermap;
    max = 8*modMapPtr->max_keypermod;
    for (i = 0; i < max; i++, codePtr++) {
	if (*codePtr == 0) {
	    continue;
	}
	keysym = KeycodeToKeysym(*codePtr, 0, 1);
	if (keysym == XK_Mode_switch) {
	    dispPtr->modeModMask |= ShiftMask << (i/modMapPtr->max_keypermod);
	}
	if ((keysym == XK_Meta_L) || (keysym == XK_Meta_R)) {
	    dispPtr->metaModMask |= ShiftMask << (i/modMapPtr->max_keypermod);
	}
	if ((keysym == XK_Alt_L) || (keysym == XK_Alt_R)) {
	    dispPtr->altModMask |= ShiftMask << (i/modMapPtr->max_keypermod);
	}
    }

    /*
     * Create an array of the keycodes for all modifier keys.
     */

    if (dispPtr->modKeyCodes != NULL) {
	ckfree(dispPtr->modKeyCodes);
    }
    dispPtr->numModKeyCodes = 0;
    arraySize = KEYCODE_ARRAY_SIZE;
    dispPtr->modKeyCodes = ckalloc(KEYCODE_ARRAY_SIZE * sizeof(KeyCode));
    for (i = 0, codePtr = modMapPtr->modifiermap; i < max; i++, codePtr++) {
	if (*codePtr == 0) {
	    continue;
	}

	/*
	 * Make sure that the keycode isn't already in the array.
	 */

	for (j = 0; j < dispPtr->numModKeyCodes; j++) {
	    if (dispPtr->modKeyCodes[j] == *codePtr) {
		goto nextModCode;
	    }
	}
	if (dispPtr->numModKeyCodes >= arraySize) {
	    KeyCode *new;

	    /*
	     * Ran out of space in the array; grow it.
	     */

	    arraySize *= 2;
	    new = ckalloc(arraySize * sizeof(KeyCode));
	    memcpy(new, dispPtr->modKeyCodes,
		    dispPtr->numModKeyCodes * sizeof(KeyCode));
	    ckfree(dispPtr->modKeyCodes);
	    dispPtr->modKeyCodes = new;
	}
	dispPtr->modKeyCodes[dispPtr->numModKeyCodes] = *codePtr;
	dispPtr->numModKeyCodes++;
	nextModCode: continue;
    }
    XFreeModifiermap(modMapPtr);
}

/*
 * When mapping from a keysym to a keycode, need information about the
 * modifier state that should be used so that when they call XKeycodeToKeysym
 * taking into account the xkey.state, they will get back the original keysym.
 */

void
TkpSetKeycodeAndState(
    Tk_Window tkwin,
    KeySym keySym,
    XEvent *eventPtr)
{
    int i;
    SHORT result;
    int shift;

    eventPtr->xkey.keycode = 0;
    if (keySym == NoSymbol) {
	return;
    }

    /*
     * We check our private map first for a virtual keycode, as VkKeyScan will
     * return values that don't map to X for the "extended" Syms. This may be
     * due to just casting problems below, but this works.
     */

    for (i = 0; i <= MAX_KEYCODE; i++) {
	if (keymap[i] == keySym) {
	    eventPtr->xkey.keycode = i;
	    return;
	}
    }
    if (keySym >= 0x20) {
	result = VkKeyScan((char) keySym);
	if (result != -1) {
	    shift = result >> 8;
	    if (shift & 1)
		eventPtr->xkey.state |= ShiftMask;
	    if (shift & 2)
		eventPtr->xkey.state |= ControlMask;
	    if (shift & 4)
		eventPtr->xkey.state |= Mod2Mask;
	    eventPtr->xkey.keycode = (KeyCode) (result & 0xff);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * XKeysymToKeycode --
 *
 *	Translate a keysym back into a keycode.
 *
 * Results:
 *	Returns the keycode that would generate the specified keysym.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

KeyCode
XKeysymToKeycode(
    Display *display,
    KeySym keysym)
{
    int i;
    SHORT result;

    /*
     * We check our private map first for a virtual keycode, as VkKeyScan will
     * return values that don't map to X for the "extended" Syms. This may be
     * due to just casting problems below, but this works.
     */

    if (keysym == NoSymbol) {
	return 0;
    }
    for (i = 0; i <= MAX_KEYCODE; i++) {
	if (keymap[i] == keysym) {
	    return ((KeyCode) i);
	}
    }
    if (keysym >= 0x20) {
	result = VkKeyScan((char) keysym);
	if (result != -1) {
	    return (KeyCode) (result & 0xff);
	}
    }

    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * XGetModifierMapping --
 *
 *	Fetch the current keycodes used as modifiers.
 *
 * Results:
 *	Returns a new modifier map.
 *
 * Side effects:
 *	Allocates a new modifier map data structure.
 *
 *----------------------------------------------------------------------
 */

XModifierKeymap	*
XGetModifierMapping(
    Display *display)
{
    XModifierKeymap *map = ckalloc(sizeof(XModifierKeymap));

    map->max_keypermod = 1;
    map->modifiermap = ckalloc(sizeof(KeyCode) * 8);
    map->modifiermap[ShiftMapIndex] = VK_SHIFT;
    map->modifiermap[LockMapIndex] = VK_CAPITAL;
    map->modifiermap[ControlMapIndex] = VK_CONTROL;
    map->modifiermap[Mod1MapIndex] = VK_NUMLOCK;
    map->modifiermap[Mod2MapIndex] = VK_MENU;
    map->modifiermap[Mod3MapIndex] = VK_SCROLL;
    map->modifiermap[Mod4MapIndex] = 0;
    map->modifiermap[Mod5MapIndex] = 0;
    return map;
}

/*
 *----------------------------------------------------------------------
 *
 * XFreeModifiermap --
 *
 *	Deallocate a modifier map that was created by XGetModifierMapping.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Frees the datastructure referenced by modmap.
 *
 *----------------------------------------------------------------------
 */

int
XFreeModifiermap(
    XModifierKeymap *modmap)
{
    ckfree(modmap->modifiermap);
    ckfree(modmap);
    return Success;
}

/*
 *----------------------------------------------------------------------
 *
 * XStringToKeysym --
 *
 *	Translate a keysym name to the matching keysym.
 *
 * Results:
 *	Returns the keysym. Since this is already handled by Tk's
 *	StringToKeysym function, we just return NoSymbol.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

KeySym
XStringToKeysym(
    _Xconst char *string)
{
    return NoSymbol;
}

/*
 *----------------------------------------------------------------------
 *
 * XKeysymToString --
 *
 *	Convert a keysym to character form.
 *
 * Results:
 *	Returns NULL, since Tk will have handled this already.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char *
XKeysymToString(
    KeySym keysym)
{
    return NULL;
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */