tclXtNotify.c

Go to the documentation of this file.
00001 /*
00002  * tclXtNotify.c --
00003  *
00004  *      This file contains the notifier driver implementation for the Xt
00005  *      intrinsics.
00006  *
00007  * Copyright (c) 1997 by Sun Microsystems, Inc.
00008  *
00009  * See the file "license.terms" for information on usage and redistribution of
00010  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
00011  *
00012  * RCS: @(#) $Id: tclXtNotify.c,v 1.9 2007/04/16 13:36:36 dkf Exp $
00013  */
00014 
00015 #include <X11/Intrinsic.h>
00016 #include "tclInt.h"
00017 
00018 /*
00019  * This structure is used to keep track of the notifier info for a a
00020  * registered file.
00021  */
00022 
00023 typedef struct FileHandler {
00024     int fd;
00025     int mask;                   /* Mask of desired events: TCL_READABLE,
00026                                  * etc. */
00027     int readyMask;              /* Events that have been seen since the last
00028                                  * time FileHandlerEventProc was called for
00029                                  * this file. */
00030     XtInputId read;             /* Xt read callback handle. */
00031     XtInputId write;            /* Xt write callback handle. */
00032     XtInputId except;           /* Xt exception callback handle. */
00033     Tcl_FileProc *proc;         /* Procedure to call, in the style of
00034                                  * Tcl_CreateFileHandler. */
00035     ClientData clientData;      /* Argument to pass to proc. */
00036     struct FileHandler *nextPtr;/* Next in list of all files we care about. */
00037 } FileHandler;
00038 
00039 /*
00040  * The following structure is what is added to the Tcl event queue when file
00041  * handlers are ready to fire.
00042  */
00043 
00044 typedef struct FileHandlerEvent {
00045     Tcl_Event header;           /* Information that is standard for all
00046                                  * events. */
00047     int fd;                     /* File descriptor that is ready. Used to find
00048                                  * the FileHandler structure for the file
00049                                  * (can't point directly to the FileHandler
00050                                  * structure because it could go away while
00051                                  * the event is queued). */
00052 } FileHandlerEvent;
00053 
00054 /*
00055  * The following static structure contains the state information for the Xt
00056  * based implementation of the Tcl notifier.
00057  */
00058 
00059 static struct NotifierState {
00060     XtAppContext appContext;    /* The context used by the Xt notifier. Can be
00061                                  * set with TclSetAppContext. */
00062     int appContextCreated;      /* Was it created by us? */
00063     XtIntervalId currentTimeout;/* Handle of current timer. */
00064     FileHandler *firstFileHandlerPtr;
00065                                 /* Pointer to head of file handler list. */
00066 } notifier;
00067 
00068 /*
00069  * The following static indicates whether this module has been initialized.
00070  */
00071 
00072 static int initialized = 0;
00073 
00074 /*
00075  * Static routines defined in this file.
00076  */
00077 
00078 static int              FileHandlerEventProc(Tcl_Event *evPtr, int flags);
00079 static void             FileProc(caddr_t clientData, int *source,
00080                             XtInputId *id);
00081 void                    InitNotifier(void);
00082 static void             NotifierExitHandler(ClientData clientData);
00083 static void             TimerProc(caddr_t clientData, XtIntervalId *id);
00084 static void             CreateFileHandler(int fd, int mask,
00085                                 Tcl_FileProc * proc, ClientData clientData);
00086 static void             DeleteFileHandler(int fd);
00087 static void             SetTimer(Tcl_Time * timePtr);
00088 static int              WaitForEvent(Tcl_Time * timePtr);
00089 
00090 /*
00091  * Functions defined in this file for use by users of the Xt Notifier:
00092  */
00093 
00094 EXTERN XtAppContext     TclSetAppContext(XtAppContext ctx);
00095 
00096 /*
00097  *----------------------------------------------------------------------
00098  *
00099  * TclSetAppContext --
00100  *
00101  *      Set the notifier application context.
00102  *
00103  * Results:
00104  *      None.
00105  *
00106  * Side effects:
00107  *      Sets the application context used by the notifier. Panics if the
00108  *      context is already set when called.
00109  *
00110  *----------------------------------------------------------------------
00111  */
00112 
00113 XtAppContext
00114 TclSetAppContext(
00115     XtAppContext appContext)
00116 {
00117     if (!initialized) {
00118         InitNotifier();
00119     }
00120 
00121     /*
00122      * If we already have a context we check whether we were asked to set a
00123      * new context. If so, we panic because we try to prevent switching
00124      * contexts by mistake. Otherwise, we return the one we have.
00125      */
00126 
00127     if (notifier.appContext != NULL) {
00128         if (appContext != NULL) {
00129             /*
00130              * We already have a context. We do not allow switching contexts
00131              * after initialization, so we panic.
00132              */
00133 
00134             Tcl_Panic("TclSetAppContext:  multiple application contexts");
00135         }
00136     } else {
00137         /*
00138          * If we get here we have not yet gotten a context, so either create
00139          * one or use the one supplied by our caller.
00140          */
00141 
00142         if (appContext == NULL) {
00143             /*
00144              * We must create a new context and tell our caller what it is, so
00145              * she can use it too.
00146              */
00147 
00148             notifier.appContext = XtCreateApplicationContext();
00149             notifier.appContextCreated = 1;
00150         } else {
00151             /*
00152              * Otherwise we remember the context that our caller gave us and
00153              * use it.
00154              */
00155 
00156             notifier.appContextCreated = 0;
00157             notifier.appContext = appContext;
00158         }
00159     }
00160 
00161     return notifier.appContext;
00162 }
00163 
00164 /*
00165  *----------------------------------------------------------------------
00166  *
00167  * InitNotifier --
00168  *
00169  *      Initializes the notifier state.
00170  *
00171  * Results:
00172  *      None.
00173  *
00174  * Side effects:
00175  *      Creates a new exit handler.
00176  *
00177  *----------------------------------------------------------------------
00178  */
00179 
00180 void
00181 InitNotifier(void)
00182 {
00183     Tcl_NotifierProcs notifier;
00184 
00185     /*
00186      * Only reinitialize if we are not in exit handling. The notifier can get
00187      * reinitialized after its own exit handler has run, because of exit
00188      * handlers for the I/O and timer sub-systems (order dependency).
00189      */
00190 
00191     if (TclInExit()) {
00192         return;
00193     }
00194 
00195     notifier.createFileHandlerProc = CreateFileHandler;
00196     notifier.deleteFileHandlerProc = DeleteFileHandler;
00197     notifier.setTimerProc = SetTimer;
00198     notifier.waitForEventProc = WaitForEvent;
00199     Tcl_SetNotifier(&notifier);
00200 
00201     /*
00202      * DO NOT create the application context yet; doing so would prevent
00203      * external applications from setting it for us to their own ones.
00204      */
00205 
00206     initialized = 1;
00207     memset(&notifier, 0, sizeof(notifier));
00208     Tcl_CreateExitHandler(NotifierExitHandler, NULL);
00209 }
00210 
00211 /*
00212  *----------------------------------------------------------------------
00213  *
00214  * NotifierExitHandler --
00215  *
00216  *      This function is called to cleanup the notifier state before Tcl is
00217  *      unloaded.
00218  *
00219  * Results:
00220  *      None.
00221  *
00222  * Side effects:
00223  *      Destroys the notifier window.
00224  *
00225  *----------------------------------------------------------------------
00226  */
00227 
00228 static void
00229 NotifierExitHandler(
00230     ClientData clientData)      /* Not used. */
00231 {
00232     if (notifier.currentTimeout != 0) {
00233         XtRemoveTimeOut(notifier.currentTimeout);
00234     }
00235     for (; notifier.firstFileHandlerPtr != NULL; ) {
00236         Tcl_DeleteFileHandler(notifier.firstFileHandlerPtr->fd);
00237     }
00238     if (notifier.appContextCreated) {
00239         XtDestroyApplicationContext(notifier.appContext);
00240         notifier.appContextCreated = 0;
00241         notifier.appContext = NULL;
00242     }
00243     initialized = 0;
00244 }
00245 
00246 /*
00247  *----------------------------------------------------------------------
00248  *
00249  * SetTimer --
00250  *
00251  *      This procedure sets the current notifier timeout value.
00252  *
00253  * Results:
00254  *      None.
00255  *
00256  * Side effects:
00257  *      Replaces any previous timer.
00258  *
00259  *----------------------------------------------------------------------
00260  */
00261 
00262 static void
00263 SetTimer(
00264     Tcl_Time *timePtr)          /* Timeout value, may be NULL. */
00265 {
00266     long timeout;
00267 
00268     if (!initialized) {
00269         InitNotifier();
00270     }
00271 
00272     TclSetAppContext(NULL);
00273     if (notifier.currentTimeout != 0) {
00274         XtRemoveTimeOut(notifier.currentTimeout);
00275     }
00276     if (timePtr) {
00277         timeout = timePtr->sec * 1000 + timePtr->usec / 1000;
00278         notifier.currentTimeout = XtAppAddTimeOut(notifier.appContext,
00279                 (unsigned long) timeout, TimerProc, NULL);
00280     } else {
00281         notifier.currentTimeout = 0;
00282     }
00283 }
00284 
00285 /*
00286  *----------------------------------------------------------------------
00287  *
00288  * TimerProc --
00289  *
00290  *      This procedure is the XtTimerCallbackProc used to handle timeouts.
00291  *
00292  * Results:
00293  *      None.
00294  *
00295  * Side effects:
00296  *      Processes all queued events.
00297  *
00298  *----------------------------------------------------------------------
00299  */
00300 
00301 static void
00302 TimerProc(
00303     caddr_t data,               /* Not used. */
00304     XtIntervalId *id)
00305 {
00306     if (*id != notifier.currentTimeout) {
00307         return;
00308     }
00309     notifier.currentTimeout = 0;
00310 
00311     Tcl_ServiceAll();
00312 }
00313 
00314 /*
00315  *----------------------------------------------------------------------
00316  *
00317  * CreateFileHandler --
00318  *
00319  *      This procedure registers a file handler with the Xt notifier.
00320  *
00321  * Results:
00322  *      None.
00323  *
00324  * Side effects:
00325  *      Creates a new file handler structure and registers one or more input
00326  *      procedures with Xt.
00327  *
00328  *----------------------------------------------------------------------
00329  */
00330 
00331 static void
00332 CreateFileHandler(
00333     int fd,                     /* Handle of stream to watch. */
00334     int mask,                   /* OR'ed combination of TCL_READABLE,
00335                                  * TCL_WRITABLE, and TCL_EXCEPTION: indicates
00336                                  * conditions under which proc should be
00337                                  * called. */
00338     Tcl_FileProc *proc,         /* Procedure to call for each selected
00339                                  * event. */
00340     ClientData clientData)      /* Arbitrary data to pass to proc. */
00341 {
00342     FileHandler *filePtr;
00343 
00344     if (!initialized) {
00345         InitNotifier();
00346     }
00347 
00348     TclSetAppContext(NULL);
00349 
00350     for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL;
00351             filePtr = filePtr->nextPtr) {
00352         if (filePtr->fd == fd) {
00353             break;
00354         }
00355     }
00356     if (filePtr == NULL) {
00357         filePtr = (FileHandler*) ckalloc(sizeof(FileHandler));
00358         filePtr->fd = fd;
00359         filePtr->read = 0;
00360         filePtr->write = 0;
00361         filePtr->except = 0;
00362         filePtr->readyMask = 0;
00363         filePtr->mask = 0;
00364         filePtr->nextPtr = notifier.firstFileHandlerPtr;
00365         notifier.firstFileHandlerPtr = filePtr;
00366     }
00367     filePtr->proc = proc;
00368     filePtr->clientData = clientData;
00369 
00370     /*
00371      * Register the file with the Xt notifier, if it hasn't been done yet.
00372      */
00373 
00374     if (mask & TCL_READABLE) {
00375         if (!(filePtr->mask & TCL_READABLE)) {
00376             filePtr->read = XtAppAddInput(notifier.appContext, fd,
00377                     XtInputReadMask, FileProc, filePtr);
00378         }
00379     } else {
00380         if (filePtr->mask & TCL_READABLE) {
00381             XtRemoveInput(filePtr->read);
00382         }
00383     }
00384     if (mask & TCL_WRITABLE) {
00385         if (!(filePtr->mask & TCL_WRITABLE)) {
00386             filePtr->write = XtAppAddInput(notifier.appContext, fd,
00387                     XtInputWriteMask, FileProc, filePtr);
00388         }
00389     } else {
00390         if (filePtr->mask & TCL_WRITABLE) {
00391             XtRemoveInput(filePtr->write);
00392         }
00393     }
00394     if (mask & TCL_EXCEPTION) {
00395         if (!(filePtr->mask & TCL_EXCEPTION)) {
00396             filePtr->except = XtAppAddInput(notifier.appContext, fd,
00397                     XtInputExceptMask, FileProc, filePtr);
00398         }
00399     } else {
00400         if (filePtr->mask & TCL_EXCEPTION) {
00401             XtRemoveInput(filePtr->except);
00402         }
00403     }
00404     filePtr->mask = mask;
00405 }
00406 
00407 /*
00408  *----------------------------------------------------------------------
00409  *
00410  * DeleteFileHandler --
00411  *
00412  *      Cancel a previously-arranged callback arrangement for a file.
00413  *
00414  * Results:
00415  *      None.
00416  *
00417  * Side effects:
00418  *      If a callback was previously registered on file, remove it.
00419  *
00420  *----------------------------------------------------------------------
00421  */
00422 
00423 static void
00424 DeleteFileHandler(
00425     int fd)                     /* Stream id for which to remove callback
00426                                  * procedure. */
00427 {
00428     FileHandler *filePtr, *prevPtr;
00429 
00430     if (!initialized) {
00431         InitNotifier();
00432     }
00433 
00434     TclSetAppContext(NULL);
00435 
00436     /*
00437      * Find the entry for the given file (and return if there isn't one).
00438      */
00439 
00440     for (prevPtr = NULL, filePtr = notifier.firstFileHandlerPtr; ;
00441             prevPtr = filePtr, filePtr = filePtr->nextPtr) {
00442         if (filePtr == NULL) {
00443             return;
00444         }
00445         if (filePtr->fd == fd) {
00446             break;
00447         }
00448     }
00449 
00450     /*
00451      * Clean up information in the callback record.
00452      */
00453 
00454     if (prevPtr == NULL) {
00455         notifier.firstFileHandlerPtr = filePtr->nextPtr;
00456     } else {
00457         prevPtr->nextPtr = filePtr->nextPtr;
00458     }
00459     if (filePtr->mask & TCL_READABLE) {
00460         XtRemoveInput(filePtr->read);
00461     }
00462     if (filePtr->mask & TCL_WRITABLE) {
00463         XtRemoveInput(filePtr->write);
00464     }
00465     if (filePtr->mask & TCL_EXCEPTION) {
00466         XtRemoveInput(filePtr->except);
00467     }
00468     ckfree((char *) filePtr);
00469 }
00470 
00471 /*
00472  *----------------------------------------------------------------------
00473  *
00474  * FileProc --
00475  *
00476  *      These procedures are called by Xt when a file becomes readable,
00477  *      writable, or has an exception.
00478  *
00479  * Results:
00480  *      None.
00481  *
00482  * Side effects:
00483  *      Makes an entry on the Tcl event queue if the event is interesting.
00484  *
00485  *----------------------------------------------------------------------
00486  */
00487 
00488 static void
00489 FileProc(
00490     caddr_t clientData,
00491     int *fd,
00492     XtInputId *id)
00493 {
00494     FileHandler *filePtr = (FileHandler *)clientData;
00495     FileHandlerEvent *fileEvPtr;
00496     int mask = 0;
00497 
00498     /*
00499      * Determine which event happened.
00500      */
00501 
00502     if (*id == filePtr->read) {
00503         mask = TCL_READABLE;
00504     } else if (*id == filePtr->write) {
00505         mask = TCL_WRITABLE;
00506     } else if (*id == filePtr->except) {
00507         mask = TCL_EXCEPTION;
00508     }
00509 
00510     /*
00511      * Ignore unwanted or duplicate events.
00512      */
00513 
00514     if (!(filePtr->mask & mask) || (filePtr->readyMask & mask)) {
00515         return;
00516     }
00517 
00518     /*
00519      * This is an interesting event, so put it onto the event queue.
00520      */
00521 
00522     filePtr->readyMask |= mask;
00523     fileEvPtr = (FileHandlerEvent *) ckalloc(sizeof(FileHandlerEvent));
00524     fileEvPtr->header.proc = FileHandlerEventProc;
00525     fileEvPtr->fd = filePtr->fd;
00526     Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL);
00527 
00528     /*
00529      * Process events on the Tcl event queue before returning to Xt.
00530      */
00531 
00532     Tcl_ServiceAll();
00533 }
00534 
00535 /*
00536  *----------------------------------------------------------------------
00537  *
00538  * FileHandlerEventProc --
00539  *
00540  *      This procedure is called by Tcl_ServiceEvent when a file event reaches
00541  *      the front of the event queue. This procedure is responsible for
00542  *      actually handling the event by invoking the callback for the file
00543  *      handler.
00544  *
00545  * Results:
00546  *      Returns 1 if the event was handled, meaning it should be removed from
00547  *      the queue. Returns 0 if the event was not handled, meaning it should
00548  *      stay on the queue. The only time the event isn't handled is if the
00549  *      TCL_FILE_EVENTS flag bit isn't set.
00550  *
00551  * Side effects:
00552  *      Whatever the file handler's callback procedure does.
00553  *
00554  *----------------------------------------------------------------------
00555  */
00556 
00557 static int
00558 FileHandlerEventProc(
00559     Tcl_Event *evPtr,           /* Event to service. */
00560     int flags)                  /* Flags that indicate what events to handle,
00561                                  * such as TCL_FILE_EVENTS. */
00562 {
00563     FileHandler *filePtr;
00564     FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr;
00565     int mask;
00566 
00567     if (!(flags & TCL_FILE_EVENTS)) {
00568         return 0;
00569     }
00570 
00571     /*
00572      * Search through the file handlers to find the one whose handle matches
00573      * the event. We do this rather than keeping a pointer to the file handler
00574      * directly in the event, so that the handler can be deleted while the
00575      * event is queued without leaving a dangling pointer.
00576      */
00577 
00578     for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL;
00579             filePtr = filePtr->nextPtr) {
00580         if (filePtr->fd != fileEvPtr->fd) {
00581             continue;
00582         }
00583 
00584         /*
00585          * The code is tricky for two reasons:
00586          * 1. The file handler's desired events could have changed since the
00587          *    time when the event was queued, so AND the ready mask with the
00588          *    desired mask.
00589          * 2. The file could have been closed and re-opened since the time
00590          *    when the event was queued. This is why the ready mask is stored
00591          *    in the file handler rather than the queued event: it will be
00592          *    zeroed when a new file handler is created for the newly opened
00593          *    file.
00594          */
00595 
00596         mask = filePtr->readyMask & filePtr->mask;
00597         filePtr->readyMask = 0;
00598         if (mask != 0) {
00599             (*filePtr->proc)(filePtr->clientData, mask);
00600         }
00601         break;
00602     }
00603     return 1;
00604 }
00605 
00606 /*
00607  *----------------------------------------------------------------------
00608  *
00609  * WaitForEvent --
00610  *
00611  *      This function is called by Tcl_DoOneEvent to wait for new events on
00612  *      the message queue. If the block time is 0, then Tcl_WaitForEvent just
00613  *      polls without blocking.
00614  *
00615  * Results:
00616  *      Returns 1 if an event was found, else 0. This ensures that
00617  *      Tcl_DoOneEvent will return 1, even if the event is handled by non-Tcl
00618  *      code.
00619  *
00620  * Side effects:
00621  *      Queues file events that are detected by the select.
00622  *
00623  *----------------------------------------------------------------------
00624  */
00625 
00626 static int
00627 WaitForEvent(
00628     Tcl_Time *timePtr)          /* Maximum block time, or NULL. */
00629 {
00630     int timeout;
00631 
00632     if (!initialized) {
00633         InitNotifier();
00634     }
00635 
00636     TclSetAppContext(NULL);
00637 
00638     if (timePtr) {
00639         timeout = timePtr->sec * 1000 + timePtr->usec / 1000;
00640         if (timeout == 0) {
00641             if (XtAppPending(notifier.appContext)) {
00642                 goto process;
00643             } else {
00644                 return 0;
00645             }
00646         } else {
00647             Tcl_SetTimer(timePtr);
00648         }
00649     }
00650 
00651   process:
00652     XtAppProcessEvent(notifier.appContext, XtIMAll);
00653     return 1;
00654 }
00655 
00656 /*
00657  * Local Variables:
00658  * mode: c
00659  * c-basic-offset: 4
00660  * fill-column: 78
00661  * End:
00662  */



Generated on Wed Mar 12 12:18:27 2008 by  doxygen 1.5.1