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