1/* 2 * tclXtNotify.c -- 3 * 4 * This file contains the notifier driver implementation for the 5 * Xt intrinsics. 6 * 7 * Copyright (c) 1997 by 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: tclXtNotify.c,v 1.4 1999/07/02 06:05:34 welch Exp $ 13 */ 14 15#include <X11/Intrinsic.h> 16#include <tclInt.h> 17 18/* 19 * This structure is used to keep track of the notifier info for a 20 * a registered file. 21 */ 22 23typedef struct FileHandler { 24 int fd; 25 int mask; /* Mask of desired events: TCL_READABLE, etc. */ 26 int readyMask; /* Events that have been seen since the 27 last time FileHandlerEventProc was called 28 for this file. */ 29 XtInputId read; /* Xt read callback handle. */ 30 XtInputId write; /* Xt write callback handle. */ 31 XtInputId except; /* Xt exception callback handle. */ 32 Tcl_FileProc *proc; /* Procedure to call, in the style of 33 * Tcl_CreateFileHandler. */ 34 ClientData clientData; /* Argument to pass to proc. */ 35 struct FileHandler *nextPtr;/* Next in list of all files we care about. */ 36} FileHandler; 37 38/* 39 * The following structure is what is added to the Tcl event queue when 40 * file handlers are ready to fire. 41 */ 42 43typedef struct FileHandlerEvent { 44 Tcl_Event header; /* Information that is standard for 45 * all events. */ 46 int fd; /* File descriptor that is ready. Used 47 * to find the FileHandler structure for 48 * the file (can't point directly to the 49 * FileHandler structure because it could 50 * go away while the event is queued). */ 51} FileHandlerEvent; 52 53/* 54 * The following static structure contains the state information for the 55 * Xt based implementation of the Tcl notifier. 56 */ 57 58static struct NotifierState { 59 XtAppContext appContext; /* The context used by the Xt 60 * notifier. Can be set with 61 * TclSetAppContext. */ 62 int appContextCreated; /* Was it created by us? */ 63 XtIntervalId currentTimeout; /* Handle of current timer. */ 64 FileHandler *firstFileHandlerPtr; /* Pointer to head of file handler 65 * list. */ 66} notifier; 67 68/* 69 * The following static indicates whether this module has been initialized. 70 */ 71 72static int initialized = 0; 73 74/* 75 * Static routines defined in this file. 76 */ 77 78static int FileHandlerEventProc _ANSI_ARGS_((Tcl_Event *evPtr, 79 int flags)); 80static void FileProc _ANSI_ARGS_((caddr_t clientData, 81 int *source, XtInputId *id)); 82void InitNotifier _ANSI_ARGS_((void)); 83static void NotifierExitHandler _ANSI_ARGS_(( 84 ClientData clientData)); 85static void TimerProc _ANSI_ARGS_((caddr_t clientData, 86 XtIntervalId *id)); 87static void CreateFileHandler _ANSI_ARGS_((int fd, int mask, 88 Tcl_FileProc * proc, ClientData clientData)); 89static void DeleteFileHandler _ANSI_ARGS_((int fd)); 90static void SetTimer _ANSI_ARGS_((Tcl_Time * timePtr)); 91static int WaitForEvent _ANSI_ARGS_((Tcl_Time * timePtr)); 92 93/* 94 * Functions defined in this file for use by users of the Xt Notifier: 95 */ 96 97EXTERN XtAppContext TclSetAppContext _ANSI_ARGS_((XtAppContext ctx)); 98 99/* 100 *---------------------------------------------------------------------- 101 * 102 * TclSetAppContext -- 103 * 104 * Set the notifier application context. 105 * 106 * Results: 107 * None. 108 * 109 * Side effects: 110 * Sets the application context used by the notifier. Panics if 111 * the context is already set when called. 112 * 113 *---------------------------------------------------------------------- 114 */ 115 116XtAppContext 117TclSetAppContext(appContext) 118 XtAppContext appContext; 119{ 120 if (!initialized) { 121 InitNotifier(); 122 } 123 124 /* 125 * If we already have a context we check whether we were asked to set a 126 * new context. If so, we panic because we try to prevent switching 127 * contexts by mistake. Otherwise, we return the one we have. 128 */ 129 130 if (notifier.appContext != NULL) { 131 if (appContext != NULL) { 132 133 /* 134 * We already have a context. We do not allow switching contexts 135 * after initialization, so we panic. 136 */ 137 138 panic("TclSetAppContext: multiple application contexts"); 139 140 } 141 } else { 142 143 /* 144 * If we get here we have not yet gotten a context, so either create 145 * one or use the one supplied by our caller. 146 */ 147 148 if (appContext == NULL) { 149 150 /* 151 * We must create a new context and tell our caller what it is, so 152 * she can use it too. 153 */ 154 155 notifier.appContext = XtCreateApplicationContext(); 156 notifier.appContextCreated = 1; 157 } else { 158 159 /* 160 * Otherwise we remember the context that our caller gave us 161 * and use it. 162 */ 163 164 notifier.appContextCreated = 0; 165 notifier.appContext = appContext; 166 } 167 } 168 169 return notifier.appContext; 170} 171 172/* 173 *---------------------------------------------------------------------- 174 * 175 * InitNotifier -- 176 * 177 * Initializes the notifier state. 178 * 179 * Results: 180 * None. 181 * 182 * Side effects: 183 * Creates a new exit handler. 184 * 185 *---------------------------------------------------------------------- 186 */ 187 188void 189InitNotifier() 190{ 191 Tcl_NotifierProcs notifier; 192 /* 193 * Only reinitialize if we are not in exit handling. The notifier 194 * can get reinitialized after its own exit handler has run, because 195 * of exit handlers for the I/O and timer sub-systems (order dependency). 196 */ 197 198 if (TclInExit()) { 199 return; 200 } 201 202 notifier.createFileHandlerProc = CreateFileHandler; 203 notifier.deleteFileHandlerProc = DeleteFileHandler; 204 notifier.setTimerProc = SetTimer; 205 notifier.waitForEventProc = WaitForEvent; 206 Tcl_SetNotifier(¬ifier); 207 208 /* 209 * DO NOT create the application context yet; doing so would prevent 210 * external applications from setting it for us to their own ones. 211 */ 212 213 initialized = 1; 214 memset(¬ifier, 0, sizeof(notifier)); 215 Tcl_CreateExitHandler(NotifierExitHandler, NULL); 216} 217 218/* 219 *---------------------------------------------------------------------- 220 * 221 * NotifierExitHandler -- 222 * 223 * This function is called to cleanup the notifier state before 224 * Tcl is unloaded. 225 * 226 * Results: 227 * None. 228 * 229 * Side effects: 230 * Destroys the notifier window. 231 * 232 *---------------------------------------------------------------------- 233 */ 234 235static void 236NotifierExitHandler( 237 ClientData clientData) /* Not used. */ 238{ 239 if (notifier.currentTimeout != 0) { 240 XtRemoveTimeOut(notifier.currentTimeout); 241 } 242 for (; notifier.firstFileHandlerPtr != NULL; ) { 243 Tcl_DeleteFileHandler(notifier.firstFileHandlerPtr->fd); 244 } 245 if (notifier.appContextCreated) { 246 XtDestroyApplicationContext(notifier.appContext); 247 notifier.appContextCreated = 0; 248 notifier.appContext = NULL; 249 } 250 initialized = 0; 251} 252 253/* 254 *---------------------------------------------------------------------- 255 * 256 * SetTimer -- 257 * 258 * This procedure sets the current notifier timeout value. 259 * 260 * Results: 261 * None. 262 * 263 * Side effects: 264 * Replaces any previous timer. 265 * 266 *---------------------------------------------------------------------- 267 */ 268 269static void 270SetTimer(timePtr) 271 Tcl_Time *timePtr; /* Timeout value, may be NULL. */ 272{ 273 long timeout; 274 275 if (!initialized) { 276 InitNotifier(); 277 } 278 279 TclSetAppContext(NULL); 280 if (notifier.currentTimeout != 0) { 281 XtRemoveTimeOut(notifier.currentTimeout); 282 } 283 if (timePtr) { 284 timeout = timePtr->sec * 1000 + timePtr->usec / 1000; 285 notifier.currentTimeout = 286 XtAppAddTimeOut(notifier.appContext, (unsigned long) timeout, 287 TimerProc, NULL); 288 } else { 289 notifier.currentTimeout = 0; 290 } 291} 292 293/* 294 *---------------------------------------------------------------------- 295 * 296 * TimerProc -- 297 * 298 * This procedure is the XtTimerCallbackProc used to handle 299 * timeouts. 300 * 301 * Results: 302 * None. 303 * 304 * Side effects: 305 * Processes all queued events. 306 * 307 *---------------------------------------------------------------------- 308 */ 309 310static void 311TimerProc(data, id) 312 caddr_t data; /* Not used. */ 313 XtIntervalId *id; 314{ 315 if (*id != notifier.currentTimeout) { 316 return; 317 } 318 notifier.currentTimeout = 0; 319 320 Tcl_ServiceAll(); 321} 322 323/* 324 *---------------------------------------------------------------------- 325 * 326 * CreateFileHandler -- 327 * 328 * This procedure registers a file handler with the Xt notifier. 329 * 330 * Results: 331 * None. 332 * 333 * Side effects: 334 * Creates a new file handler structure and registers one or more 335 * input procedures with Xt. 336 * 337 *---------------------------------------------------------------------- 338 */ 339 340static void 341CreateFileHandler(fd, mask, proc, clientData) 342 int fd; /* Handle of stream to watch. */ 343 int mask; /* OR'ed combination of TCL_READABLE, 344 * TCL_WRITABLE, and TCL_EXCEPTION: 345 * indicates conditions under which 346 * proc should be called. */ 347 Tcl_FileProc *proc; /* Procedure to call for each 348 * selected event. */ 349 ClientData clientData; /* Arbitrary data to pass to proc. */ 350{ 351 FileHandler *filePtr; 352 353 if (!initialized) { 354 InitNotifier(); 355 } 356 357 TclSetAppContext(NULL); 358 359 for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL; 360 filePtr = filePtr->nextPtr) { 361 if (filePtr->fd == fd) { 362 break; 363 } 364 } 365 if (filePtr == NULL) { 366 filePtr = (FileHandler*) ckalloc(sizeof(FileHandler)); 367 filePtr->fd = fd; 368 filePtr->read = 0; 369 filePtr->write = 0; 370 filePtr->except = 0; 371 filePtr->readyMask = 0; 372 filePtr->mask = 0; 373 filePtr->nextPtr = notifier.firstFileHandlerPtr; 374 notifier.firstFileHandlerPtr = filePtr; 375 } 376 filePtr->proc = proc; 377 filePtr->clientData = clientData; 378 379 /* 380 * Register the file with the Xt notifier, if it hasn't been done yet. 381 */ 382 383 if (mask & TCL_READABLE) { 384 if (!(filePtr->mask & TCL_READABLE)) { 385 filePtr->read = 386 XtAppAddInput(notifier.appContext, fd, XtInputReadMask, 387 FileProc, filePtr); 388 } 389 } else { 390 if (filePtr->mask & TCL_READABLE) { 391 XtRemoveInput(filePtr->read); 392 } 393 } 394 if (mask & TCL_WRITABLE) { 395 if (!(filePtr->mask & TCL_WRITABLE)) { 396 filePtr->write = 397 XtAppAddInput(notifier.appContext, fd, XtInputWriteMask, 398 FileProc, filePtr); 399 } 400 } else { 401 if (filePtr->mask & TCL_WRITABLE) { 402 XtRemoveInput(filePtr->write); 403 } 404 } 405 if (mask & TCL_EXCEPTION) { 406 if (!(filePtr->mask & TCL_EXCEPTION)) { 407 filePtr->except = 408 XtAppAddInput(notifier.appContext, fd, XtInputExceptMask, 409 FileProc, filePtr); 410 } 411 } else { 412 if (filePtr->mask & TCL_EXCEPTION) { 413 XtRemoveInput(filePtr->except); 414 } 415 } 416 filePtr->mask = mask; 417} 418 419/* 420 *---------------------------------------------------------------------- 421 * 422 * DeleteFileHandler -- 423 * 424 * Cancel a previously-arranged callback arrangement for 425 * a file. 426 * 427 * Results: 428 * None. 429 * 430 * Side effects: 431 * If a callback was previously registered on file, remove it. 432 * 433 *---------------------------------------------------------------------- 434 */ 435 436static void 437DeleteFileHandler(fd) 438 int fd; /* Stream id for which to remove 439 * callback procedure. */ 440{ 441 FileHandler *filePtr, *prevPtr; 442 443 if (!initialized) { 444 InitNotifier(); 445 } 446 447 TclSetAppContext(NULL); 448 449 /* 450 * Find the entry for the given file (and return if there 451 * isn't one). 452 */ 453 454 for (prevPtr = NULL, filePtr = notifier.firstFileHandlerPtr; ; 455 prevPtr = filePtr, filePtr = filePtr->nextPtr) { 456 if (filePtr == NULL) { 457 return; 458 } 459 if (filePtr->fd == fd) { 460 break; 461 } 462 } 463 464 /* 465 * Clean up information in the callback record. 466 */ 467 468 if (prevPtr == NULL) { 469 notifier.firstFileHandlerPtr = filePtr->nextPtr; 470 } else { 471 prevPtr->nextPtr = filePtr->nextPtr; 472 } 473 if (filePtr->mask & TCL_READABLE) { 474 XtRemoveInput(filePtr->read); 475 } 476 if (filePtr->mask & TCL_WRITABLE) { 477 XtRemoveInput(filePtr->write); 478 } 479 if (filePtr->mask & TCL_EXCEPTION) { 480 XtRemoveInput(filePtr->except); 481 } 482 ckfree((char *) filePtr); 483} 484 485/* 486 *---------------------------------------------------------------------- 487 * 488 * FileProc -- 489 * 490 * These procedures are called by Xt when a file becomes readable, 491 * writable, or has an exception. 492 * 493 * Results: 494 * None. 495 * 496 * Side effects: 497 * Makes an entry on the Tcl event queue if the event is 498 * interesting. 499 * 500 *---------------------------------------------------------------------- 501 */ 502 503static void 504FileProc(clientData, fd, id) 505 caddr_t clientData; 506 int *fd; 507 XtInputId *id; 508{ 509 FileHandler *filePtr = (FileHandler *)clientData; 510 FileHandlerEvent *fileEvPtr; 511 int mask = 0; 512 513 /* 514 * Determine which event happened. 515 */ 516 517 if (*id == filePtr->read) { 518 mask = TCL_READABLE; 519 } else if (*id == filePtr->write) { 520 mask = TCL_WRITABLE; 521 } else if (*id == filePtr->except) { 522 mask = TCL_EXCEPTION; 523 } 524 525 /* 526 * Ignore unwanted or duplicate events. 527 */ 528 529 if (!(filePtr->mask & mask) || (filePtr->readyMask & mask)) { 530 return; 531 } 532 533 /* 534 * This is an interesting event, so put it onto the event queue. 535 */ 536 537 filePtr->readyMask |= mask; 538 fileEvPtr = (FileHandlerEvent *) ckalloc(sizeof(FileHandlerEvent)); 539 fileEvPtr->header.proc = FileHandlerEventProc; 540 fileEvPtr->fd = filePtr->fd; 541 Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); 542 543 /* 544 * Process events on the Tcl event queue before returning to Xt. 545 */ 546 547 Tcl_ServiceAll(); 548} 549 550/* 551 *---------------------------------------------------------------------- 552 * 553 * FileHandlerEventProc -- 554 * 555 * This procedure is called by Tcl_ServiceEvent when a file event 556 * reaches the front of the event queue. This procedure is 557 * responsible for actually handling the event by invoking the 558 * callback for the file handler. 559 * 560 * Results: 561 * Returns 1 if the event was handled, meaning it should be removed 562 * from the queue. Returns 0 if the event was not handled, meaning 563 * it should stay on the queue. The only time the event isn't 564 * handled is if the TCL_FILE_EVENTS flag bit isn't set. 565 * 566 * Side effects: 567 * Whatever the file handler's callback procedure does. 568 * 569 *---------------------------------------------------------------------- 570 */ 571 572static int 573FileHandlerEventProc(evPtr, flags) 574 Tcl_Event *evPtr; /* Event to service. */ 575 int flags; /* Flags that indicate what events to 576 * handle, such as TCL_FILE_EVENTS. */ 577{ 578 FileHandler *filePtr; 579 FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr; 580 int mask; 581 582 if (!(flags & TCL_FILE_EVENTS)) { 583 return 0; 584 } 585 586 /* 587 * Search through the file handlers to find the one whose handle matches 588 * the event. We do this rather than keeping a pointer to the file 589 * handler directly in the event, so that the handler can be deleted 590 * while the event is queued without leaving a dangling pointer. 591 */ 592 593 for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL; 594 filePtr = filePtr->nextPtr) { 595 if (filePtr->fd != fileEvPtr->fd) { 596 continue; 597 } 598 599 /* 600 * The code is tricky for two reasons: 601 * 1. The file handler's desired events could have changed 602 * since the time when the event was queued, so AND the 603 * ready mask with the desired mask. 604 * 2. The file could have been closed and re-opened since 605 * the time when the event was queued. This is why the 606 * ready mask is stored in the file handler rather than 607 * the queued event: it will be zeroed when a new 608 * file handler is created for the newly opened file. 609 */ 610 611 mask = filePtr->readyMask & filePtr->mask; 612 filePtr->readyMask = 0; 613 if (mask != 0) { 614 (*filePtr->proc)(filePtr->clientData, mask); 615 } 616 break; 617 } 618 return 1; 619} 620 621/* 622 *---------------------------------------------------------------------- 623 * 624 * WaitForEvent -- 625 * 626 * This function is called by Tcl_DoOneEvent to wait for new 627 * events on the message queue. If the block time is 0, then 628 * Tcl_WaitForEvent just polls without blocking. 629 * 630 * Results: 631 * Returns 1 if an event was found, else 0. This ensures that 632 * Tcl_DoOneEvent will return 1, even if the event is handled 633 * by non-Tcl code. 634 * 635 * Side effects: 636 * Queues file events that are detected by the select. 637 * 638 *---------------------------------------------------------------------- 639 */ 640 641static int 642WaitForEvent( 643 Tcl_Time *timePtr) /* Maximum block time, or NULL. */ 644{ 645 int timeout; 646 647 if (!initialized) { 648 InitNotifier(); 649 } 650 651 TclSetAppContext(NULL); 652 653 if (timePtr) { 654 timeout = timePtr->sec * 1000 + timePtr->usec / 1000; 655 if (timeout == 0) { 656 if (XtAppPending(notifier.appContext)) { 657 goto process; 658 } else { 659 return 0; 660 } 661 } else { 662 Tcl_SetTimer(timePtr); 663 } 664 } 665process: 666 XtAppProcessEvent(notifier.appContext, XtIMAll); 667 return 1; 668} 669