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