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