Files
cpython-source-deps/unix/tkUnixKey.c
2021-03-30 00:54:10 +01:00

561 lines
14 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* tkUnixKey.c --
*
* This file contains routines for dealing with international keyboard
* input.
*
* Copyright (c) 1997 by 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 "tkInt.h"
#ifdef __GNUC__
/*
* We know that XKeycodeToKeysym is deprecated, nothing we can do about it.
*/
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
/*
** Bug [3607830]: Before using Xkb, it must be initialized. TkpOpenDisplay
** does this and sets the USE_XKB flag if xkb is supported.
** (should this be function ptr?)
*/
#ifdef HAVE_XKBKEYCODETOKEYSYM
# include <X11/XKBlib.h>
#else
# define XkbKeycodeToKeysym(D,K,G,L) XKeycodeToKeysym(D,K,L)
#endif
#define TkKeycodeToKeysym(D,K,G,L) \
((D)->flags & TK_DISPLAY_USE_XKB) ? \
XkbKeycodeToKeysym((D)->display,K,G,L) : \
XKeycodeToKeysym((D)->display,K,L)
/*
* Prototypes for local functions defined in this file:
*/
/*
*----------------------------------------------------------------------
*
* Tk_SetCaretPos --
*
* This enables correct placement of the XIM caret. This is called by
* widgets to indicate their cursor placement. This is currently only
* used for over-the-spot XIM.
*
*----------------------------------------------------------------------
*/
void
Tk_SetCaretPos(
Tk_Window tkwin,
int x,
int y,
int height)
{
TkWindow *winPtr = (TkWindow *) tkwin;
TkDisplay *dispPtr = winPtr->dispPtr;
if ((dispPtr->caret.winPtr == winPtr)
&& (dispPtr->caret.x == x)
&& (dispPtr->caret.y == y)
&& (dispPtr->caret.height == height)) {
return;
}
dispPtr->caret.winPtr = winPtr;
dispPtr->caret.x = x;
dispPtr->caret.y = y;
dispPtr->caret.height = height;
/*
* Adjust the XIM caret position.
*/
#ifdef TK_USE_INPUT_METHODS
if ((dispPtr->flags & TK_DISPLAY_USE_IM)
&& (dispPtr->inputStyle & XIMPreeditPosition)
&& (winPtr->inputContext != NULL)) {
XVaNestedList preedit_attr;
XPoint spot;
spot.x = dispPtr->caret.x;
spot.y = dispPtr->caret.y + dispPtr->caret.height;
preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
XSetICValues(winPtr->inputContext, XNPreeditAttributes, preedit_attr,
NULL);
XFree(preedit_attr);
}
#endif
}
/*
*----------------------------------------------------------------------
*
* TkpGetString --
*
* Retrieve the UTF string associated with a keyboard event.
*
* Results:
* Returns the UTF string.
*
* Side effects:
* Stores the input string in the specified Tcl_DString. Modifies the
* internal input state. This routine can only be called once for a given
* event.
*
*----------------------------------------------------------------------
*/
const char *
TkpGetString(
TkWindow *winPtr, /* Window where event occurred */
XEvent *eventPtr, /* X keyboard event. */
Tcl_DString *dsPtr) /* Initialized, empty string to hold result. */
{
int len;
Tcl_DString buf;
TkKeyEvent *kePtr = (TkKeyEvent *) eventPtr;
/*
* If we have the value cached already, use it now. [Bug 1373712]
*/
if (kePtr->charValuePtr != NULL) {
Tcl_DStringSetLength(dsPtr, kePtr->charValueLen);
memcpy(Tcl_DStringValue(dsPtr), kePtr->charValuePtr,
(unsigned) kePtr->charValueLen+1);
return Tcl_DStringValue(dsPtr);
}
/*
* Only do this for KeyPress events, otherwise
* further Xlib function behavior might be undefined.
*/
if (eventPtr->type != KeyPress) {
len = 0;
Tcl_DStringSetLength(dsPtr, len);
goto done;
}
#ifdef TK_USE_INPUT_METHODS
if ((winPtr->dispPtr->flags & TK_DISPLAY_USE_IM)
&& (winPtr->inputContext != NULL)) {
Status status;
#if X_HAVE_UTF8_STRING
Tcl_DStringSetLength(dsPtr, TCL_DSTRING_STATIC_SIZE-1);
len = Xutf8LookupString(winPtr->inputContext, &eventPtr->xkey,
Tcl_DStringValue(dsPtr), Tcl_DStringLength(dsPtr),
&kePtr->keysym, &status);
if (status == XBufferOverflow) {
/*
* Expand buffer and try again.
*/
Tcl_DStringSetLength(dsPtr, len);
len = Xutf8LookupString(winPtr->inputContext, &eventPtr->xkey,
Tcl_DStringValue(dsPtr), Tcl_DStringLength(dsPtr),
&kePtr->keysym, &status);
}
if ((status != XLookupChars) && (status != XLookupBoth)) {
len = 0;
}
Tcl_DStringSetLength(dsPtr, len);
#else /* !X_HAVE_UTF8_STRING */
/*
* Overallocate the dstring to the maximum stack amount.
*/
Tcl_DStringInit(&buf);
Tcl_DStringSetLength(&buf, TCL_DSTRING_STATIC_SIZE-1);
len = XmbLookupString(winPtr->inputContext, &eventPtr->xkey,
Tcl_DStringValue(&buf), Tcl_DStringLength(&buf),
&kePtr->keysym, &status);
/*
* If the buffer wasn't big enough, grow the buffer and try again.
*/
if (status == XBufferOverflow) {
Tcl_DStringSetLength(&buf, len);
len = XmbLookupString(winPtr->inputContext, &eventPtr->xkey,
Tcl_DStringValue(&buf), len, &kePtr->keysym, &status);
}
if ((status != XLookupChars) && (status != XLookupBoth)) {
len = 0;
}
Tcl_DStringSetLength(&buf, len);
Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(&buf), len, dsPtr);
Tcl_DStringFree(&buf);
#endif /* X_HAVE_UTF8_STRING */
} else
#endif /* TK_USE_INPUT_METHODS */
{
/*
* Fall back to convert a keyboard event to a UTF-8 string using
* XLookupString. This is used when input methods are turned off.
*
* Note: XLookupString() normally returns a single ISO Latin 1 or
* ASCII control character.
*/
Tcl_DStringInit(&buf);
Tcl_DStringSetLength(&buf, TCL_DSTRING_STATIC_SIZE-1);
len = XLookupString(&eventPtr->xkey, Tcl_DStringValue(&buf),
TCL_DSTRING_STATIC_SIZE, &kePtr->keysym, 0);
Tcl_DStringValue(&buf)[len] = '\0';
if (len == 1) {
len = TkUniCharToUtf((unsigned char) Tcl_DStringValue(&buf)[0],
Tcl_DStringValue(dsPtr));
Tcl_DStringSetLength(dsPtr, len);
} else {
/*
* len > 1 should only happen if someone has called XRebindKeysym.
* Assume UTF-8.
*/
Tcl_DStringSetLength(dsPtr, len);
strncpy(Tcl_DStringValue(dsPtr), Tcl_DStringValue(&buf), len);
}
}
/*
* Cache the string in the event so that if/when we return to this
* function, we will be able to produce it without asking X. This stops us
* from having to reenter the XIM engine. [Bug 1373712]
*/
done:
kePtr->charValuePtr = ckalloc(len + 1);
kePtr->charValueLen = len;
memcpy(kePtr->charValuePtr, Tcl_DStringValue(dsPtr), (unsigned) len + 1);
return Tcl_DStringValue(dsPtr);
}
/*
* When mapping from a keysym to a keycode, need information about the
* modifier state to be used so that when they call TkKeycodeToKeysym taking
* into account the xkey.state, they will get back the original keysym.
*/
void
TkpSetKeycodeAndState(
Tk_Window tkwin,
KeySym keySym,
XEvent *eventPtr)
{
TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr;
int state, mincode, maxcode;
KeyCode keycode;
if (keySym == NoSymbol) {
keycode = 0;
} else {
keycode = XKeysymToKeycode(dispPtr->display, keySym);
}
eventPtr->xkey.keycode = keycode;
if (keycode != 0) {
for (state = 0; state < 4; state++) {
if (XLookupKeysym(&eventPtr->xkey, state) == keySym) {
if (state & 1) {
eventPtr->xkey.state |= ShiftMask;
}
if (state & 2) {
eventPtr->xkey.state |= dispPtr->modeModMask;
}
break;
}
}
}
/*
* Filter keycodes out of range, otherwise further Xlib function
* behavior might be undefined, in particular XIM could cause crashes.
*/
mincode = 0;
maxcode = -1;
XDisplayKeycodes(dispPtr->display, &mincode, &maxcode);
if (keycode < (KeyCode)mincode) {
keycode = mincode;
} else if (keycode > (KeyCode)maxcode) {
keycode = maxcode;
}
eventPtr->xkey.keycode = 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.
*
*----------------------------------------------------------------------
*/
#ifdef __GNUC__
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
KeySym
TkpGetKeySym(
TkDisplay *dispPtr, /* Display in which to map keycode. */
XEvent *eventPtr) /* Description of X event. */
{
KeySym sym;
int index;
TkKeyEvent* kePtr = (TkKeyEvent*) eventPtr;
/*
* X11 keycodes always lie in the inclusive range [8,255].
*/
if (eventPtr->xkey.keycode > 0xff) {
return NoSymbol;
}
/*
* Refresh the mapping information if it's stale. This must happen before
* we do any input method processing. [Bug 3599312]
*/
if (dispPtr->bindInfoStale) {
TkpInitKeymapInfo(dispPtr);
}
#ifdef TK_USE_INPUT_METHODS
/*
* If input methods are active, we may already have determined a keysym.
* Return it.
*/
if (eventPtr->type == KeyPress && dispPtr
&& (dispPtr->flags & TK_DISPLAY_USE_IM)) {
if (kePtr->charValuePtr == NULL) {
Tcl_DString ds;
TkWindow *winPtr = (TkWindow *)
Tk_IdToWindow(eventPtr->xany.display, eventPtr->xany.window);
Tcl_DStringInit(&ds);
(void) TkpGetString(winPtr, eventPtr, &ds);
Tcl_DStringFree(&ds);
}
if (kePtr->charValuePtr != NULL) {
return kePtr->keysym;
}
}
#endif
/*
* Figure out which of the four slots in the keymap vector to use for this
* key. Refer to Xlib documentation for more info on how this computation
* works.
*/
index = 0;
if (eventPtr->xkey.state & dispPtr->modeModMask) {
index = 2;
}
if ((eventPtr->xkey.state & ShiftMask)
|| ((dispPtr->lockUsage != LU_IGNORE)
&& (eventPtr->xkey.state & LockMask))) {
index += 1;
}
sym = TkKeycodeToKeysym(dispPtr, eventPtr->xkey.keycode, 0,
index);
/*
* Special handling: if the key was shifted because of Lock, but lock is
* only caps lock, not shift lock, and the shifted keysym isn't upper-case
* alphabetic, then switch back to the unshifted keysym.
*/
#ifndef XK_Oslash
/* XK_Oslash is the official name, but might not be present in older X11 headers */
# define XK_Oslash XK_Ooblique
#endif
if ((index & 1) && !(eventPtr->xkey.state & ShiftMask)
&& (dispPtr->lockUsage == LU_CAPS)) {
if (!(((sym >= XK_A) && (sym <= XK_Z))
|| ((sym >= XK_Agrave) && (sym <= XK_Odiaeresis))
|| ((sym >= XK_Oslash) && (sym <= XK_Thorn)))) {
index &= ~1;
sym = TkKeycodeToKeysym(dispPtr, eventPtr->xkey.keycode,
0, index);
}
}
/*
* Another bit of special handling: if this is a shifted key and there is
* no keysym defined, then use the keysym for the unshifted key.
*/
if ((index & 1) && (sym == NoSymbol)) {
sym = TkKeycodeToKeysym(dispPtr, eventPtr->xkey.keycode,
0, index & ~1);
}
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 = TkKeycodeToKeysym(dispPtr, *codePtr, 0, 0);
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 = TkKeycodeToKeysym(dispPtr, *codePtr, 0, 0);
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) {
/*
* 'continue' the outer loop.
*/
goto nextModCode;
}
}
if (dispPtr->numModKeyCodes >= arraySize) {
KeyCode *newCodes;
/*
* Ran out of space in the array; grow it.
*/
arraySize *= 2;
newCodes = ckalloc(arraySize * sizeof(KeyCode));
memcpy(newCodes, dispPtr->modKeyCodes,
dispPtr->numModKeyCodes * sizeof(KeyCode));
ckfree(dispPtr->modKeyCodes);
dispPtr->modKeyCodes = newCodes;
}
dispPtr->modKeyCodes[dispPtr->numModKeyCodes] = *codePtr;
dispPtr->numModKeyCodes++;
nextModCode:
continue;
}
XFreeModifiermap(modMapPtr);
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/