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