The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * 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.
 *
 * RCS: @(#) $Id: tkWinKey.c,v 1.14 2001/05/30 22:41:11 hobbs Exp $
 */

#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 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 procedures defined in this file:
 */

static KeySym		KeycodeToKeysym _ANSI_ARGS_((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.
 *
 *----------------------------------------------------------------------
 */

char *
TkpGetString(winPtr, eventPtr, dsPtr)
    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. */
{
    KeySym keysym;
    XKeyEvent* keyEv = &eventPtr->xkey;

    Tcl_DStringInit(dsPtr);
    if (eventPtr->xkey.send_event == -1) {
        if (eventPtr->xkey.nbytes > 0) {
	    Tcl_ExternalToUtfDString(TkWinGetKeyInputEncoding(),
                    eventPtr->xkey.trans_chars, eventPtr->xkey.nbytes, dsPtr);
        }
    } else if (eventPtr->xkey.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 = (eventPtr->xkey.trans_chars[1] & 0xff);
        unichar <<= 8;
        unichar |= (eventPtr->xkey.trans_chars[0] & 0xff);

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

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

	keysym = KeycodeToKeysym(eventPtr->xkey.keycode,
		eventPtr->xkey.state, 0);
	if (((keysym != NoSymbol) && (keysym > 0) && (keysym < 256)) 
		|| (keysym == XK_Return)
		|| (keysym == XK_Tab)) {
	    char buf[TCL_UTF_MAX];
	    int 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, keycode, index)
    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(keycode, state, noascii)
    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 < 0 || 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(dispPtr, eventPtr)
    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 procedure 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(dispPtr)
    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((char *) dispPtr->modKeyCodes);
    }
    dispPtr->numModKeyCodes = 0;
    arraySize = KEYCODE_ARRAY_SIZE;
    dispPtr->modKeyCodes = (KeyCode *) ckalloc((unsigned)
	    (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 = (KeyCode *) ckalloc((unsigned)
		    (arraySize * sizeof(KeyCode)));
	    memcpy((VOID *) new, (VOID *) dispPtr->modKeyCodes,
		    (dispPtr->numModKeyCodes * sizeof(KeyCode)));
	    ckfree((char *) 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(tkwin, keySym, eventPtr)
    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, keysym)
    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* display;
{
    XModifierKeymap *map = (XModifierKeymap *)ckalloc(sizeof(XModifierKeymap));

    map->max_keypermod = 1;
    map->modifiermap = (KeyCode *) 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.
 *
 *----------------------------------------------------------------------
 */

void
XFreeModifiermap(modmap)
    XModifierKeymap* modmap;
{
    ckfree((char *) modmap->modifiermap);
    ckfree((char *) modmap);
}

/*
 *----------------------------------------------------------------------
 *
 * 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(string)
    _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 keysym;
{
    return NULL;
}