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