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