1/*
2 * tkUnixKey.c --
3 *
4 *	This file contains routines for dealing with international keyboard
5 *	input.
6 *
7 * Copyright (c) 1997 by Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id$
13 */
14
15#include "tkInt.h"
16
17/*
18 * Prototypes for local functions defined in this file:
19 */
20
21/*
22 *----------------------------------------------------------------------
23 *
24 * Tk_SetCaretPos --
25 *
26 *	This enables correct placement of the XIM caret. This is called by
27 *	widgets to indicate their cursor placement.  This is currently only
28 *	used for over-the-spot XIM.
29 *
30 *----------------------------------------------------------------------
31 */
32
33void
34Tk_SetCaretPos(
35    Tk_Window tkwin,
36    int x,
37    int y,
38    int height)
39{
40    TkWindow *winPtr = (TkWindow *) tkwin;
41    TkDisplay *dispPtr = winPtr->dispPtr;
42
43    if (   dispPtr->caret.winPtr == winPtr
44	&& dispPtr->caret.x == x
45	&& dispPtr->caret.y == y
46	&& dispPtr->caret.height == height)
47    {
48	return;
49    }
50
51    dispPtr->caret.winPtr = winPtr;
52    dispPtr->caret.x = x;
53    dispPtr->caret.y = y;
54    dispPtr->caret.height = height;
55
56#ifdef TK_USE_INPUT_METHODS
57    /*
58     * Adjust the XIM caret position.
59     */
60    if (   (dispPtr->flags & TK_DISPLAY_USE_IM)
61	&& (dispPtr->inputStyle & XIMPreeditPosition)
62	&& (winPtr->inputContext != NULL) )
63    {
64	XVaNestedList preedit_attr;
65	XPoint spot;
66
67	spot.x = dispPtr->caret.x;
68	spot.y = dispPtr->caret.y + dispPtr->caret.height;
69	preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
70	XSetICValues(winPtr->inputContext,
71		XNPreeditAttributes, preedit_attr,
72		NULL);
73	XFree(preedit_attr);
74    }
75#endif
76}
77
78/*
79 *----------------------------------------------------------------------
80 *
81 * TkpGetString --
82 *
83 *	Retrieve the UTF string associated with a keyboard event.
84 *
85 * Results:
86 *	Returns the UTF string.
87 *
88 * Side effects:
89 *	Stores the input string in the specified Tcl_DString. Modifies the
90 *	internal input state. This routine can only be called once for a given
91 *	event.
92 *
93 *----------------------------------------------------------------------
94 */
95
96char *
97TkpGetString(
98    TkWindow *winPtr,		/* Window where event occurred */
99    XEvent *eventPtr,		/* X keyboard event. */
100    Tcl_DString *dsPtr)		/* Initialized, empty string to hold result. */
101{
102    int len;
103    Tcl_DString buf;
104    TkKeyEvent *kePtr = (TkKeyEvent *) eventPtr;
105
106    /*
107     * If we have the value cached already, use it now. [Bug 1373712]
108     */
109
110    if (kePtr->charValuePtr != NULL) {
111	Tcl_DStringSetLength(dsPtr, kePtr->charValueLen);
112	memcpy(Tcl_DStringValue(dsPtr), kePtr->charValuePtr,
113		(unsigned) kePtr->charValueLen+1);
114	return Tcl_DStringValue(dsPtr);
115    }
116
117#ifdef TK_USE_INPUT_METHODS
118    if ((winPtr->dispPtr->flags & TK_DISPLAY_USE_IM)
119	    && (winPtr->inputContext != NULL)
120	    && (eventPtr->type == KeyPress))
121    {
122	Status status;
123
124#if X_HAVE_UTF8_STRING
125	Tcl_DStringSetLength(dsPtr, TCL_DSTRING_STATIC_SIZE-1);
126	len = Xutf8LookupString(winPtr->inputContext, &eventPtr->xkey,
127		Tcl_DStringValue(dsPtr), Tcl_DStringLength(dsPtr),
128		NULL, &status);
129
130	if (status == XBufferOverflow) { /* Expand buffer and try again */
131	    Tcl_DStringSetLength(dsPtr, len);
132	    len = Xutf8LookupString(winPtr->inputContext, &eventPtr->xkey,
133		    Tcl_DStringValue(dsPtr), Tcl_DStringLength(dsPtr),
134		    NULL, &status);
135	}
136	if ((status != XLookupChars) && (status != XLookupBoth)) {
137	    len = 0;
138	}
139	Tcl_DStringSetLength(dsPtr, len);
140#else /* !X_HAVE_UTF8_STRING */
141	/*
142	 * Overallocate the dstring to the maximum stack amount.
143	 */
144
145	Tcl_DStringInit(&buf);
146	Tcl_DStringSetLength(&buf, TCL_DSTRING_STATIC_SIZE-1);
147
148	len = XmbLookupString(winPtr->inputContext, &eventPtr->xkey,
149		Tcl_DStringValue(&buf), Tcl_DStringLength(&buf), NULL,
150		&status);
151
152	/*
153	 * If the buffer wasn't big enough, grow the buffer and try again.
154	 */
155
156	if (status == XBufferOverflow) {
157	    Tcl_DStringSetLength(&buf, len);
158	    len = XmbLookupString(winPtr->inputContext, &eventPtr->xkey,
159		    Tcl_DStringValue(&buf), len, NULL, &status);
160	}
161	if ((status != XLookupChars) && (status != XLookupBoth)) {
162	    len = 0;
163	}
164
165	Tcl_DStringSetLength(&buf, len);
166	Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(&buf), len, dsPtr);
167	Tcl_DStringFree(&buf);
168#endif /* X_HAVE_UTF8_STRING */
169    } else
170#endif /* TK_USE_INPUT_METHODS */
171    {
172	/*
173	 * Fall back to convert a keyboard event to a UTF-8 string using
174	 * XLookupString. This is used when input methods are turned off and
175	 * for KeyRelease events.
176	 *
177	 * Note: XLookupString() normally returns a single ISO Latin 1 or
178	 * ASCII control character.
179	 */
180
181	Tcl_DStringInit(&buf);
182	Tcl_DStringSetLength(&buf, TCL_DSTRING_STATIC_SIZE-1);
183	len = XLookupString(&eventPtr->xkey, Tcl_DStringValue(&buf),
184		TCL_DSTRING_STATIC_SIZE, 0, 0);
185	Tcl_DStringValue(&buf)[len] = '\0';
186
187	if (len == 1) {
188	    len = Tcl_UniCharToUtf((unsigned char) Tcl_DStringValue(&buf)[0],
189		    Tcl_DStringValue(dsPtr));
190	    Tcl_DStringSetLength(dsPtr, len);
191	} else {
192	    /*
193	     * len > 1 should only happen if someone has called XRebindKeysym.
194	     * Assume UTF-8.
195	     */
196
197	    Tcl_DStringSetLength(dsPtr, len);
198	    strncpy(Tcl_DStringValue(dsPtr), Tcl_DStringValue(&buf), len);
199	}
200    }
201
202    /*
203     * Cache the string in the event so that if/when we return to this
204     * function, we will be able to produce it without asking X. This stops us
205     * from having to reenter the XIM engine. [Bug 1373712]
206     */
207
208    kePtr->charValuePtr = ckalloc((unsigned) len + 1);
209    kePtr->charValueLen = len;
210    memcpy(kePtr->charValuePtr, Tcl_DStringValue(dsPtr), (unsigned) len + 1);
211    return Tcl_DStringValue(dsPtr);
212}
213
214/*
215 * When mapping from a keysym to a keycode, need information about the
216 * modifier state that should be used so that when they call XKeycodeToKeysym
217 * taking into account the xkey.state, they will get back the original keysym.
218 */
219
220void
221TkpSetKeycodeAndState(
222    Tk_Window tkwin,
223    KeySym keySym,
224    XEvent *eventPtr)
225{
226    Display *display;
227    int state;
228    KeyCode keycode;
229
230    display = Tk_Display(tkwin);
231
232    if (keySym == NoSymbol) {
233	keycode = 0;
234    } else {
235	keycode = XKeysymToKeycode(display, keySym);
236    }
237    if (keycode != 0) {
238	for (state = 0; state < 4; state++) {
239	    if (XKeycodeToKeysym(display, keycode, state) == keySym) {
240		if (state & 1) {
241		    eventPtr->xkey.state |= ShiftMask;
242		}
243		if (state & 2) {
244		    TkDisplay *dispPtr;
245
246		    dispPtr = ((TkWindow *) tkwin)->dispPtr;
247		    eventPtr->xkey.state |= dispPtr->modeModMask;
248		}
249		break;
250	    }
251	}
252    }
253    eventPtr->xkey.keycode = keycode;
254}
255
256/*
257 *----------------------------------------------------------------------
258 *
259 * TkpGetKeySym --
260 *
261 *	Given an X KeyPress or KeyRelease event, map the keycode in the event
262 *	into a KeySym.
263 *
264 * Results:
265 *	The return value is the KeySym corresponding to eventPtr, or NoSymbol
266 *	if no matching Keysym could be found.
267 *
268 * Side effects:
269 *	In the first call for a given display, keycode-to-KeySym maps get
270 *	loaded.
271 *
272 *----------------------------------------------------------------------
273 */
274
275KeySym
276TkpGetKeySym(
277    TkDisplay *dispPtr,		/* Display in which to map keycode. */
278    XEvent *eventPtr)		/* Description of X event. */
279{
280    KeySym sym;
281    int index;
282
283    /*
284     * Refresh the mapping information if it's stale
285     */
286
287    if (dispPtr->bindInfoStale) {
288	TkpInitKeymapInfo(dispPtr);
289    }
290
291    /*
292     * Figure out which of the four slots in the keymap vector to use for this
293     * key. Refer to Xlib documentation for more info on how this computation
294     * works.
295     */
296
297    index = 0;
298    if (eventPtr->xkey.state & dispPtr->modeModMask) {
299	index = 2;
300    }
301    if ((eventPtr->xkey.state & ShiftMask)
302	    || ((dispPtr->lockUsage != LU_IGNORE)
303	    && (eventPtr->xkey.state & LockMask))) {
304	index += 1;
305    }
306    sym = XKeycodeToKeysym(dispPtr->display, eventPtr->xkey.keycode, index);
307
308    /*
309     * Special handling: if the key was shifted because of Lock, but lock is
310     * only caps lock, not shift lock, and the shifted keysym isn't upper-case
311     * alphabetic, then switch back to the unshifted keysym.
312     */
313
314    if ((index & 1) && !(eventPtr->xkey.state & ShiftMask)
315	    && (dispPtr->lockUsage == LU_CAPS)) {
316	if (!(((sym >= XK_A) && (sym <= XK_Z))
317		|| ((sym >= XK_Agrave) && (sym <= XK_Odiaeresis))
318		|| ((sym >= XK_Ooblique) && (sym <= XK_Thorn)))) {
319	    index &= ~1;
320	    sym = XKeycodeToKeysym(dispPtr->display, eventPtr->xkey.keycode,
321		    index);
322	}
323    }
324
325    /*
326     * Another bit of special handling: if this is a shifted key and there is
327     * no keysym defined, then use the keysym for the unshifted key.
328     */
329
330    if ((index & 1) && (sym == NoSymbol)) {
331	sym = XKeycodeToKeysym(dispPtr->display, eventPtr->xkey.keycode,
332		index & ~1);
333    }
334    return sym;
335}
336
337/*
338 *--------------------------------------------------------------
339 *
340 * TkpInitKeymapInfo --
341 *
342 *	This function is invoked to scan keymap information to recompute stuff
343 *	that's important for binding, such as the modifier key (if any) that
344 *	corresponds to "mode switch".
345 *
346 * Results:
347 *	None.
348 *
349 * Side effects:
350 *	Keymap-related information in dispPtr is updated.
351 *
352 *--------------------------------------------------------------
353 */
354
355void
356TkpInitKeymapInfo(
357    TkDisplay *dispPtr)		/* Display for which to recompute keymap
358				 * information. */
359{
360    XModifierKeymap *modMapPtr;
361    KeyCode *codePtr;
362    KeySym keysym;
363    int count, i, j, max, arraySize;
364#define KEYCODE_ARRAY_SIZE 20
365
366    dispPtr->bindInfoStale = 0;
367    modMapPtr = XGetModifierMapping(dispPtr->display);
368
369    /*
370     * Check the keycodes associated with the Lock modifier. If any of them is
371     * associated with the XK_Shift_Lock modifier, then Lock has to be
372     * interpreted as Shift Lock, not Caps Lock.
373     */
374
375    dispPtr->lockUsage = LU_IGNORE;
376    codePtr = modMapPtr->modifiermap + modMapPtr->max_keypermod*LockMapIndex;
377    for (count = modMapPtr->max_keypermod; count > 0; count--, codePtr++) {
378	if (*codePtr == 0) {
379	    continue;
380	}
381	keysym = XKeycodeToKeysym(dispPtr->display, *codePtr, 0);
382	if (keysym == XK_Shift_Lock) {
383	    dispPtr->lockUsage = LU_SHIFT;
384	    break;
385	}
386	if (keysym == XK_Caps_Lock) {
387	    dispPtr->lockUsage = LU_CAPS;
388	    break;
389	}
390    }
391
392    /*
393     * Look through the keycodes associated with modifiers to see if the the
394     * "mode switch", "meta", or "alt" keysyms are associated with any
395     * modifiers. If so, remember their modifier mask bits.
396     */
397
398    dispPtr->modeModMask = 0;
399    dispPtr->metaModMask = 0;
400    dispPtr->altModMask = 0;
401    codePtr = modMapPtr->modifiermap;
402    max = 8*modMapPtr->max_keypermod;
403    for (i = 0; i < max; i++, codePtr++) {
404	if (*codePtr == 0) {
405	    continue;
406	}
407	keysym = XKeycodeToKeysym(dispPtr->display, *codePtr, 0);
408	if (keysym == XK_Mode_switch) {
409	    dispPtr->modeModMask |= ShiftMask << (i/modMapPtr->max_keypermod);
410	}
411	if ((keysym == XK_Meta_L) || (keysym == XK_Meta_R)) {
412	    dispPtr->metaModMask |= ShiftMask << (i/modMapPtr->max_keypermod);
413	}
414	if ((keysym == XK_Alt_L) || (keysym == XK_Alt_R)) {
415	    dispPtr->altModMask |= ShiftMask << (i/modMapPtr->max_keypermod);
416	}
417    }
418
419    /*
420     * Create an array of the keycodes for all modifier keys.
421     */
422
423    if (dispPtr->modKeyCodes != NULL) {
424	ckfree((char *) dispPtr->modKeyCodes);
425    }
426    dispPtr->numModKeyCodes = 0;
427    arraySize = KEYCODE_ARRAY_SIZE;
428    dispPtr->modKeyCodes = (KeyCode *)
429	    ckalloc((unsigned) (KEYCODE_ARRAY_SIZE * sizeof(KeyCode)));
430    for (i = 0, codePtr = modMapPtr->modifiermap; i < max; i++, codePtr++) {
431	if (*codePtr == 0) {
432	    continue;
433	}
434
435	/*
436	 * Make sure that the keycode isn't already in the array.
437	 */
438
439	for (j = 0; j < dispPtr->numModKeyCodes; j++) {
440	    if (dispPtr->modKeyCodes[j] == *codePtr) {
441		goto nextModCode;
442	    }
443	}
444	if (dispPtr->numModKeyCodes >= arraySize) {
445	    KeyCode *new;
446
447	    /*
448	     * Ran out of space in the array; grow it.
449	     */
450
451	    arraySize *= 2;
452	    new = (KeyCode *)
453		    ckalloc((unsigned) (arraySize * sizeof(KeyCode)));
454	    memcpy(new, dispPtr->modKeyCodes,
455		    (dispPtr->numModKeyCodes * sizeof(KeyCode)));
456	    ckfree((char *) dispPtr->modKeyCodes);
457	    dispPtr->modKeyCodes = new;
458	}
459	dispPtr->modKeyCodes[dispPtr->numModKeyCodes] = *codePtr;
460	dispPtr->numModKeyCodes++;
461    nextModCode:
462	continue;
463    }
464    XFreeModifiermap(modMapPtr);
465}
466
467/*
468 * Local Variables:
469 * mode: c
470 * c-basic-offset: 4
471 * fill-column: 78
472 * End:
473 */
474