1 /*
2  * tkUnixEvent.c --
3  *
4  *	This file implements an event source for X displays for the UNIX
5  *	version of Tk.
6  *
7  * Copyright (c) 1995-1997 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 
13 #include "tkUnixInt.h"
14 #include <signal.h>
15 #ifdef HAVE_XKBKEYCODETOKEYSYM
16 #  include <X11/XKBlib.h>
17 #else
18 #  define XkbOpenDisplay(D,V,E,M,m,R) ((V),(E),(M),(m),(R),(NULL))
19 #endif
20 
21 /*
22  * The following static indicates whether this module has been initialized in
23  * the current thread.
24  */
25 
26 typedef struct ThreadSpecificData {
27     int initialized;
28 } ThreadSpecificData;
29 static Tcl_ThreadDataKey dataKey;
30 
31 /*
32  * Prototypes for functions that are referenced only in this file:
33  */
34 
35 static void		DisplayCheckProc(ClientData clientData, int flags);
36 static void		DisplayExitHandler(ClientData clientData);
37 static void		DisplayFileProc(ClientData clientData, int flags);
38 static void		DisplaySetupProc(ClientData clientData, int flags);
39 static void		TransferXEventsToTcl(Display *display);
40 #ifdef TK_USE_INPUT_METHODS
41 static void		OpenIM(TkDisplay *dispPtr);
42 #endif
43 
44 /*
45  *----------------------------------------------------------------------
46  *
47  * TkCreateXEventSource --
48  *
49  *	This function is called during Tk initialization to create the event
50  *	source for X Window events.
51  *
52  * Results:
53  *	None.
54  *
55  * Side effects:
56  *	A new event source is created.
57  *
58  *----------------------------------------------------------------------
59  */
60 
61 void
TkCreateXEventSource(void)62 TkCreateXEventSource(void)
63 {
64     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
65 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
66 
67     if (!tsdPtr->initialized) {
68 	tsdPtr->initialized = 1;
69 	Tcl_CreateEventSource(DisplaySetupProc, DisplayCheckProc, NULL);
70 	TkCreateExitHandler(DisplayExitHandler, NULL);
71     }
72 }
73 
74 /*
75  *----------------------------------------------------------------------
76  *
77  * DisplayExitHandler --
78  *
79  *	This function is called during finalization to clean up the display
80  *	module.
81  *
82  * Results:
83  *	None.
84  *
85  * Side effects:
86  *	None.
87  *
88  *----------------------------------------------------------------------
89  */
90 
91 static void
DisplayExitHandler(ClientData clientData)92 DisplayExitHandler(
93     ClientData clientData)	/* Not used. */
94 {
95     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
96 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
97 
98     Tcl_DeleteEventSource(DisplaySetupProc, DisplayCheckProc, NULL);
99     tsdPtr->initialized = 0;
100 }
101 
102 /*
103  *----------------------------------------------------------------------
104  *
105  * TkpOpenDisplay --
106  *
107  *	Allocates a new TkDisplay, opens the X display, and establishes the
108  *	file handler for the connection.
109  *
110  * Results:
111  *	A pointer to a Tk display structure.
112  *
113  * Side effects:
114  *	Opens a display.
115  *
116  *----------------------------------------------------------------------
117  */
118 
119 TkDisplay *
TkpOpenDisplay(CONST char * displayNameStr)120 TkpOpenDisplay(
121     CONST char *displayNameStr)
122 {
123     TkDisplay *dispPtr;
124     Display *display;
125     int event = 0;
126     int error = 0;
127     int major = 1;
128     int minor = 0;
129     int reason = 0;
130     unsigned int use_xkb = 0;
131 
132     /*
133     ** Bug [3607830]: Before using Xkb, it must be initialized and confirmed
134     **                that the serve supports it.  The XkbOpenDisplay call
135     **                will perform this check and return NULL if the extension
136     **                is not supported.
137     **
138     ** Work around un-const-ified Xkb headers using (char *) cast.
139     */
140     display = XkbOpenDisplay((char *)displayNameStr, &event, &error, &major,
141 	    &minor, &reason);
142 
143     if (display == NULL) {
144 	/*fprintf(stderr,"event=%d error=%d major=%d minor=%d reason=%d\nDisabling xkb\n",
145 	event, error, major, minor, reason);*/
146 	display  = XOpenDisplay(displayNameStr);
147     } else {
148 	use_xkb = TK_DISPLAY_USE_XKB;
149 	/*fprintf(stderr, "Using xkb %d.%d\n", major, minor);*/
150     }
151 
152     if (display == NULL) {
153 	return NULL;
154     }
155     dispPtr = (TkDisplay *) ckalloc(sizeof(TkDisplay));
156     memset(dispPtr, 0, sizeof(TkDisplay));
157     dispPtr->display = display;
158     dispPtr->flags |= use_xkb;
159 #ifdef TK_USE_INPUT_METHODS
160     OpenIM(dispPtr);
161 #endif
162     Tcl_CreateFileHandler(ConnectionNumber(display), TCL_READABLE,
163 	    DisplayFileProc, (ClientData) dispPtr);
164     return dispPtr;
165 }
166 
167 /*
168  *----------------------------------------------------------------------
169  *
170  * TkpCloseDisplay --
171  *
172  *	Cancels notifier callbacks and closes a display.
173  *
174  * Results:
175  *	None.
176  *
177  * Side effects:
178  *	Deallocates the displayPtr and unix-specific resources.
179  *
180  *----------------------------------------------------------------------
181  */
182 
183 void
TkpCloseDisplay(TkDisplay * dispPtr)184 TkpCloseDisplay(
185     TkDisplay *dispPtr)
186 {
187     TkSendCleanup(dispPtr);
188 
189     TkFreeXId(dispPtr);
190 
191     TkWmCleanup(dispPtr);
192 
193 #ifdef TK_USE_INPUT_METHODS
194     if (dispPtr->inputXfs) {
195 	XFreeFontSet(dispPtr->display, dispPtr->inputXfs);
196     }
197     if (dispPtr->inputMethod) {
198 	XCloseIM(dispPtr->inputMethod);
199     }
200 #endif
201 
202     if (dispPtr->display != 0) {
203 	Tcl_DeleteFileHandler(ConnectionNumber(dispPtr->display));
204 	(void) XSync(dispPtr->display, False);
205 	(void) XCloseDisplay(dispPtr->display);
206     }
207 }
208 
209 /*
210  *----------------------------------------------------------------------
211  *
212  * TkClipCleanup --
213  *
214  *	This function is called to cleanup resources associated with claiming
215  *	clipboard ownership and for receiving selection get results. This
216  *	function is called in tkWindow.c. This has to be called by the display
217  *	cleanup function because we still need the access display elements.
218  *
219  * Results:
220  *	None.
221  *
222  * Side effects:
223  *	Resources are freed - the clipboard may no longer be used.
224  *
225  *----------------------------------------------------------------------
226  */
227 
228 void
TkClipCleanup(TkDisplay * dispPtr)229 TkClipCleanup(
230     TkDisplay *dispPtr)		/* Display associated with clipboard */
231 {
232     if (dispPtr->clipWindow != NULL) {
233 	Tk_DeleteSelHandler(dispPtr->clipWindow, dispPtr->clipboardAtom,
234 		dispPtr->applicationAtom);
235 	Tk_DeleteSelHandler(dispPtr->clipWindow, dispPtr->clipboardAtom,
236 		dispPtr->windowAtom);
237 
238 	Tk_DestroyWindow(dispPtr->clipWindow);
239 	Tcl_Release((ClientData) dispPtr->clipWindow);
240 	dispPtr->clipWindow = NULL;
241     }
242 }
243 
244 /*
245  *----------------------------------------------------------------------
246  *
247  * DisplaySetupProc --
248  *
249  *	This function implements the setup part of the UNIX X display event
250  *	source. It is invoked by Tcl_DoOneEvent before entering the notifier
251  *	to check for events on all displays.
252  *
253  * Results:
254  *	None.
255  *
256  * Side effects:
257  *	If data is queued on a display inside Xlib, then the maximum block
258  *	time will be set to 0 to ensure that the notifier returns control to
259  *	Tcl even if there is no more data on the X connection.
260  *
261  *----------------------------------------------------------------------
262  */
263 
264 static void
DisplaySetupProc(ClientData clientData,int flags)265 DisplaySetupProc(
266     ClientData clientData,	/* Not used. */
267     int flags)
268 {
269     TkDisplay *dispPtr;
270     static Tcl_Time blockTime = { 0, 0 };
271 
272     if (!(flags & TCL_WINDOW_EVENTS)) {
273 	return;
274     }
275 
276     for (dispPtr = TkGetDisplayList(); dispPtr != NULL;
277 	    dispPtr = dispPtr->nextPtr) {
278 	/*
279 	 * Flush the display. If data is pending on the X queue, set the block
280 	 * time to zero. This ensures that we won't block in the notifier if
281 	 * there is data in the X queue, but not on the server socket.
282 	 */
283 
284 	XFlush(dispPtr->display);
285 	if (QLength(dispPtr->display) > 0) {
286 	    Tcl_SetMaxBlockTime(&blockTime);
287 	}
288     }
289 }
290 
291 /*
292  *----------------------------------------------------------------------
293  *
294  * TransferXEventsToTcl --
295  *
296  *	Transfer events from the X event queue to the Tk event queue.
297  *
298  * Results:
299  *	None.
300  *
301  * Side effects:
302  *	Moves queued X events onto the Tcl event queue.
303  *
304  *----------------------------------------------------------------------
305  */
306 
307 static void
TransferXEventsToTcl(Display * display)308 TransferXEventsToTcl(
309     Display *display)
310 {
311     union {
312 	int type;
313 	XEvent x;
314 	TkKeyEvent k;
315     } event;
316     Window w;
317     TkDisplay *dispPtr = NULL;
318 
319     /*
320      * Transfer events from the X event queue to the Tk event queue after XIM
321      * event filtering. KeyPress and KeyRelease events need special treatment
322      * so that they get directed according to Tk's focus rules during XIM
323      * handling. Theoretically they can go to the wrong place still (if
324      * there's a focus change in the queue) but if we push the handling off
325      * until Tk_HandleEvent then many input methods actually cease to work
326      * correctly. Most of the time, Tk processes its event queue fast enough
327      * for this to not be an issue anyway. [Bug 1924761]
328      */
329 
330     while (QLength(display) > 0) {
331 	XNextEvent(display, &event.x);
332 	w = None;
333 	if (event.type == KeyPress || event.type == KeyRelease) {
334 	    for (dispPtr = TkGetDisplayList(); ; dispPtr = dispPtr->nextPtr) {
335 		if (dispPtr == NULL) {
336 		    break;
337 		} else if (dispPtr->display == event.x.xany.display) {
338 		    if (dispPtr->focusPtr != NULL) {
339 			w = dispPtr->focusPtr->window;
340 		    }
341 		    break;
342 		}
343 	    }
344 	}
345 	if (XFilterEvent(&event.x, w)) {
346 	    continue;
347 	}
348 	if (event.type == KeyPress || event.type == KeyRelease) {
349 	    event.k.charValuePtr = NULL;
350 	    event.k.charValueLen = 0;
351 	    event.k.keysym = NoSymbol;
352 
353 	    /*
354 	     * Force the calling of the input method engine now. The results
355 	     * from it will be cached in the event so that they don't get lost
356 	     * (to a race condition with other XIM-handled key events) between
357 	     * entering the event queue and getting serviced. [Bug 1924761]
358 	     */
359 
360 #ifdef TK_USE_INPUT_METHODS
361 	    if (event.type == KeyPress && dispPtr &&
362 		    (dispPtr->flags & TK_DISPLAY_USE_IM)) {
363 		if (dispPtr->focusPtr && dispPtr->focusPtr->inputContext) {
364 		    Tcl_DString ds;
365 
366 		    Tcl_DStringInit(&ds);
367 		    (void) TkpGetString(dispPtr->focusPtr, &event.x, &ds);
368 		    Tcl_DStringFree(&ds);
369 		}
370 	    }
371 #endif
372 	}
373 	Tk_QueueWindowEvent(&event.x, TCL_QUEUE_TAIL);
374     }
375 }
376 
377 /*
378  *----------------------------------------------------------------------
379  *
380  * DisplayCheckProc --
381  *
382  *	This function checks for events sitting in the X event queue.
383  *
384  * Results:
385  *	None.
386  *
387  * Side effects:
388  *	Moves queued events onto the Tcl event queue.
389  *
390  *----------------------------------------------------------------------
391  */
392 
393 static void
DisplayCheckProc(ClientData clientData,int flags)394 DisplayCheckProc(
395     ClientData clientData,	/* Not used. */
396     int flags)
397 {
398     TkDisplay *dispPtr;
399 
400     if (!(flags & TCL_WINDOW_EVENTS)) {
401 	return;
402     }
403 
404     for (dispPtr = TkGetDisplayList(); dispPtr != NULL;
405 	    dispPtr = dispPtr->nextPtr) {
406 	XFlush(dispPtr->display);
407 	TransferXEventsToTcl(dispPtr->display);
408     }
409 }
410 
411 /*
412  *----------------------------------------------------------------------
413  *
414  * DisplayFileProc --
415  *
416  *	This function implements the file handler for the X connection.
417  *
418  * Results:
419  *	None.
420  *
421  * Side effects:
422  *	Makes entries on the Tcl event queue for all the events available from
423  *	all the displays.
424  *
425  *----------------------------------------------------------------------
426  */
427 
428 static void
DisplayFileProc(ClientData clientData,int flags)429 DisplayFileProc(
430     ClientData clientData,	/* The display pointer. */
431     int flags)			/* Should be TCL_READABLE. */
432 {
433     TkDisplay *dispPtr = (TkDisplay *) clientData;
434     Display *display = dispPtr->display;
435     int numFound;
436 
437     XFlush(display);
438     numFound = XEventsQueued(display, QueuedAfterReading);
439     if (numFound == 0) {
440 	/*
441 	 * Things are very tricky if there aren't any events readable at this
442 	 * point (after all, there was supposedly data available on the
443 	 * connection). A couple of things could have occurred:
444 	 *
445 	 * One possibility is that there were only error events in the input
446 	 * from the server. If this happens, we should return (we don't want
447 	 * to go to sleep in XNextEvent below, since this would block out
448 	 * other sources of input to the process).
449 	 *
450 	 * Another possibility is that our connection to the server has been
451 	 * closed. This will not necessarily be detected in XEventsQueued (!!)
452 	 * so if we just return then there will be an infinite loop. To detect
453 	 * such an error, generate a NoOp protocol request to exercise the
454 	 * connection to the server, then return. However, must disable
455 	 * SIGPIPE while sending the request, or else the process will die
456 	 * from the signal and won't invoke the X error function to print a
457 	 * nice (?!) message.
458 	 */
459 
460 	void (*oldHandler)();
461 
462 	oldHandler = (void (*)()) signal(SIGPIPE, SIG_IGN);
463 	XNoOp(display);
464 	XFlush(display);
465 	(void) signal(SIGPIPE, oldHandler);
466     }
467 
468     TransferXEventsToTcl(display);
469 }
470 
471 /*
472  *----------------------------------------------------------------------
473  *
474  * TkUnixDoOneXEvent --
475  *
476  *	This routine waits for an X event to be processed or for a timeout to
477  *	occur. The timeout is specified as an absolute time. This routine is
478  *	called when Tk needs to wait for a particular X event without letting
479  *	arbitrary events be processed. The caller will typically call
480  *	Tk_RestrictEvents to set up an event filter before calling this
481  *	routine. This routine will service at most one event per invocation.
482  *
483  * Results:
484  *	Returns 0 if the timeout has expired, otherwise returns 1.
485  *
486  * Side effects:
487  *	Can invoke arbitrary Tcl scripts.
488  *
489  *----------------------------------------------------------------------
490  */
491 
492 int
TkUnixDoOneXEvent(Tcl_Time * timePtr)493 TkUnixDoOneXEvent(
494     Tcl_Time *timePtr)		/* Specifies the absolute time when the call
495 				 * should time out. */
496 {
497     TkDisplay *dispPtr;
498     static fd_mask readMask[MASK_SIZE];
499     struct timeval blockTime, *timeoutPtr;
500     Tcl_Time now;
501     int fd, index, numFound, numFdBits = 0;
502     fd_mask bit, *readMaskPtr = readMask;
503 
504     /*
505      * Look for queued events first.
506      */
507 
508     if (Tcl_ServiceEvent(TCL_WINDOW_EVENTS)) {
509 	return 1;
510     }
511 
512     /*
513      * Compute the next block time and check to see if we have timed out. Note
514      * that HP-UX defines tv_sec to be unsigned so we have to be careful in
515      * our arithmetic.
516      */
517 
518     if (timePtr) {
519 	Tcl_GetTime(&now);
520 	blockTime.tv_sec = timePtr->sec;
521 	blockTime.tv_usec = timePtr->usec - now.usec;
522 	if (blockTime.tv_usec < 0) {
523 	    now.sec += 1;
524 	    blockTime.tv_usec += 1000000;
525 	}
526 	if (blockTime.tv_sec < now.sec) {
527 	    blockTime.tv_sec = 0;
528 	    blockTime.tv_usec = 0;
529 	} else {
530 	    blockTime.tv_sec -= now.sec;
531 	}
532 	timeoutPtr = &blockTime;
533     } else {
534 	timeoutPtr = NULL;
535     }
536 
537     /*
538      * Set up the select mask for all of the displays. If a display has data
539      * pending, then we want to poll instead of blocking.
540      */
541 
542     memset(readMask, 0, MASK_SIZE*sizeof(fd_mask));
543     for (dispPtr = TkGetDisplayList(); dispPtr != NULL;
544 	    dispPtr = dispPtr->nextPtr) {
545 	XFlush(dispPtr->display);
546 	if (QLength(dispPtr->display) > 0) {
547 	    blockTime.tv_sec = 0;
548 	    blockTime.tv_usec = 0;
549 	}
550 	fd = ConnectionNumber(dispPtr->display);
551 	index = fd/(NBBY*sizeof(fd_mask));
552 	bit = ((fd_mask)1) << (fd%(NBBY*sizeof(fd_mask)));
553 	readMask[index] |= bit;
554 	if (numFdBits <= fd) {
555 	    numFdBits = fd+1;
556 	}
557     }
558 
559     numFound = select(numFdBits, (SELECT_MASK *) readMaskPtr, NULL, NULL,
560 	    timeoutPtr);
561     if (numFound <= 0) {
562 	/*
563 	 * Some systems don't clear the masks after an error, so we have to do
564 	 * it here.
565 	 */
566 
567 	memset(readMask, 0, MASK_SIZE*sizeof(fd_mask));
568     }
569 
570     /*
571      * Process any new events on the display connections.
572      */
573 
574     for (dispPtr = TkGetDisplayList(); dispPtr != NULL;
575 	    dispPtr = dispPtr->nextPtr) {
576 	fd = ConnectionNumber(dispPtr->display);
577 	index = fd/(NBBY*sizeof(fd_mask));
578 	bit = ((fd_mask)1) << (fd%(NBBY*sizeof(fd_mask)));
579 	if ((readMask[index] & bit) || (QLength(dispPtr->display) > 0)) {
580 	    DisplayFileProc((ClientData)dispPtr, TCL_READABLE);
581 	}
582     }
583     if (Tcl_ServiceEvent(TCL_WINDOW_EVENTS)) {
584 	return 1;
585     }
586 
587     /*
588      * Check to see if we timed out.
589      */
590 
591     if (timePtr) {
592 	Tcl_GetTime(&now);
593 	if ((now.sec > timePtr->sec) || ((now.sec == timePtr->sec)
594 		&& (now.usec > timePtr->usec))) {
595 	    return 0;
596 	}
597     }
598 
599     /*
600      * We had an event but we did not generate a Tcl event from it. Behave as
601      * though we dealt with it. (JYL&SS)
602      */
603 
604     return 1;
605 }
606 
607 /*
608  *----------------------------------------------------------------------
609  *
610  * TkpSync --
611  *
612  *	This routine ensures that all pending X requests have been seen by the
613  *	server, and that any pending X events have been moved onto the Tk
614  *	event queue.
615  *
616  * Results:
617  *	None.
618  *
619  * Side effects:
620  *	Places new events on the Tk event queue.
621  *
622  *----------------------------------------------------------------------
623  */
624 
625 void
TkpSync(Display * display)626 TkpSync(
627     Display *display)		/* Display to sync. */
628 {
629     XSync(display, False);
630 
631     /*
632      * Transfer events from the X event queue to the Tk event queue.
633      */
634 
635     TransferXEventsToTcl(display);
636 }
637 #ifdef TK_USE_INPUT_METHODS
638 
639 /*
640  *--------------------------------------------------------------
641  *
642  * OpenIM --
643  *
644  *	Tries to open an X input method associated with the given display.
645  *
646  * Results:
647  *	Stores the input method in dispPtr->inputMethod; if there isn't a
648  *	suitable input method, then NULL is stored in dispPtr->inputMethod.
649  *
650  * Side effects:
651  *	An input method gets opened.
652  *
653  *--------------------------------------------------------------
654  */
655 
656 static void
OpenIM(TkDisplay * dispPtr)657 OpenIM(
658     TkDisplay *dispPtr)		/* Tk's structure for the display. */
659 {
660     int i;
661     XIMStyles *stylePtr;
662     XIMStyle bestStyle = 0;
663 
664     if (XSetLocaleModifiers("") == NULL) {
665 	return;
666     }
667 
668     dispPtr->inputMethod = XOpenIM(dispPtr->display, NULL, NULL, NULL);
669     if (dispPtr->inputMethod == NULL) {
670 	return;
671     }
672 
673     if ((XGetIMValues(dispPtr->inputMethod, XNQueryInputStyle, &stylePtr,
674 	    NULL) != NULL) || (stylePtr == NULL)) {
675 	goto error;
676     }
677 
678     /*
679      * Select the best input style supported by both the IM and Tk.
680      */
681     for (i = 0; i < stylePtr->count_styles; i++) {
682 	XIMStyle thisStyle = stylePtr->supported_styles[i];
683 	if (thisStyle == (XIMPreeditPosition | XIMStatusNothing)) {
684 	    bestStyle = thisStyle;
685 	    break;
686 	} else if (thisStyle == (XIMPreeditNothing | XIMStatusNothing)) {
687 	    bestStyle = thisStyle;
688 	}
689     }
690     XFree(stylePtr);
691     if (bestStyle == 0) {
692 	goto error;
693     }
694 
695     dispPtr->inputStyle = bestStyle;
696 
697     /*
698      * Create an XFontSet for preedit area.
699      */
700     if (dispPtr->inputStyle & XIMPreeditPosition) {
701 	char **missing_list;
702 	int missing_count;
703 	char *def_string;
704 
705 	dispPtr->inputXfs = XCreateFontSet(dispPtr->display,
706 		"-*-*-*-R-Normal--14-130-75-75-*-*",
707 		&missing_list, &missing_count, &def_string);
708 	if (missing_count > 0) {
709 	    XFreeStringList(missing_list);
710 	}
711     }
712 
713     return;
714 
715 error:
716     if (dispPtr->inputMethod) {
717 	XCloseIM(dispPtr->inputMethod);
718 	dispPtr->inputMethod = NULL;
719     }
720 }
721 #endif /* TK_USE_INPUT_METHODS */
722 
723 /*
724  * Local Variables:
725  * mode: c
726  * c-basic-offset: 4
727  * fill-column: 78
728  * End:
729  */
730