sl@0: /* sl@0: * tclXtNotify.c -- sl@0: * sl@0: * This file contains the notifier driver implementation for the sl@0: * Xt intrinsics. sl@0: * sl@0: * Copyright (c) 1997 by Sun Microsystems, Inc. sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclXtNotify.c,v 1.4 1999/07/02 06:05:34 welch Exp $ sl@0: */ sl@0: sl@0: #include sl@0: #include sl@0: sl@0: /* sl@0: * This structure is used to keep track of the notifier info for a sl@0: * a registered file. sl@0: */ sl@0: sl@0: typedef struct FileHandler { sl@0: int fd; sl@0: int mask; /* Mask of desired events: TCL_READABLE, etc. */ sl@0: int readyMask; /* Events that have been seen since the sl@0: last time FileHandlerEventProc was called sl@0: for this file. */ sl@0: XtInputId read; /* Xt read callback handle. */ sl@0: XtInputId write; /* Xt write callback handle. */ sl@0: XtInputId except; /* Xt exception callback handle. */ sl@0: Tcl_FileProc *proc; /* Procedure to call, in the style of sl@0: * Tcl_CreateFileHandler. */ sl@0: ClientData clientData; /* Argument to pass to proc. */ sl@0: struct FileHandler *nextPtr;/* Next in list of all files we care about. */ sl@0: } FileHandler; sl@0: sl@0: /* sl@0: * The following structure is what is added to the Tcl event queue when sl@0: * file handlers are ready to fire. sl@0: */ sl@0: sl@0: typedef struct FileHandlerEvent { sl@0: Tcl_Event header; /* Information that is standard for sl@0: * all events. */ sl@0: int fd; /* File descriptor that is ready. Used sl@0: * to find the FileHandler structure for sl@0: * the file (can't point directly to the sl@0: * FileHandler structure because it could sl@0: * go away while the event is queued). */ sl@0: } FileHandlerEvent; sl@0: sl@0: /* sl@0: * The following static structure contains the state information for the sl@0: * Xt based implementation of the Tcl notifier. sl@0: */ sl@0: sl@0: static struct NotifierState { sl@0: XtAppContext appContext; /* The context used by the Xt sl@0: * notifier. Can be set with sl@0: * TclSetAppContext. */ sl@0: int appContextCreated; /* Was it created by us? */ sl@0: XtIntervalId currentTimeout; /* Handle of current timer. */ sl@0: FileHandler *firstFileHandlerPtr; /* Pointer to head of file handler sl@0: * list. */ sl@0: } notifier; sl@0: sl@0: /* sl@0: * The following static indicates whether this module has been initialized. sl@0: */ sl@0: sl@0: static int initialized = 0; sl@0: sl@0: /* sl@0: * Static routines defined in this file. sl@0: */ sl@0: sl@0: static int FileHandlerEventProc _ANSI_ARGS_((Tcl_Event *evPtr, sl@0: int flags)); sl@0: static void FileProc _ANSI_ARGS_((caddr_t clientData, sl@0: int *source, XtInputId *id)); sl@0: void InitNotifier _ANSI_ARGS_((void)); sl@0: static void NotifierExitHandler _ANSI_ARGS_(( sl@0: ClientData clientData)); sl@0: static void TimerProc _ANSI_ARGS_((caddr_t clientData, sl@0: XtIntervalId *id)); sl@0: static void CreateFileHandler _ANSI_ARGS_((int fd, int mask, sl@0: Tcl_FileProc * proc, ClientData clientData)); sl@0: static void DeleteFileHandler _ANSI_ARGS_((int fd)); sl@0: static void SetTimer _ANSI_ARGS_((Tcl_Time * timePtr)); sl@0: static int WaitForEvent _ANSI_ARGS_((Tcl_Time * timePtr)); sl@0: sl@0: /* sl@0: * Functions defined in this file for use by users of the Xt Notifier: sl@0: */ sl@0: sl@0: EXTERN XtAppContext TclSetAppContext _ANSI_ARGS_((XtAppContext ctx)); sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclSetAppContext -- sl@0: * sl@0: * Set the notifier application context. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Sets the application context used by the notifier. Panics if sl@0: * the context is already set when called. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: XtAppContext sl@0: TclSetAppContext(appContext) sl@0: XtAppContext appContext; sl@0: { sl@0: if (!initialized) { sl@0: InitNotifier(); sl@0: } sl@0: sl@0: /* sl@0: * If we already have a context we check whether we were asked to set a sl@0: * new context. If so, we panic because we try to prevent switching sl@0: * contexts by mistake. Otherwise, we return the one we have. sl@0: */ sl@0: sl@0: if (notifier.appContext != NULL) { sl@0: if (appContext != NULL) { sl@0: sl@0: /* sl@0: * We already have a context. We do not allow switching contexts sl@0: * after initialization, so we panic. sl@0: */ sl@0: sl@0: panic("TclSetAppContext: multiple application contexts"); sl@0: sl@0: } sl@0: } else { sl@0: sl@0: /* sl@0: * If we get here we have not yet gotten a context, so either create sl@0: * one or use the one supplied by our caller. sl@0: */ sl@0: sl@0: if (appContext == NULL) { sl@0: sl@0: /* sl@0: * We must create a new context and tell our caller what it is, so sl@0: * she can use it too. sl@0: */ sl@0: sl@0: notifier.appContext = XtCreateApplicationContext(); sl@0: notifier.appContextCreated = 1; sl@0: } else { sl@0: sl@0: /* sl@0: * Otherwise we remember the context that our caller gave us sl@0: * and use it. sl@0: */ sl@0: sl@0: notifier.appContextCreated = 0; sl@0: notifier.appContext = appContext; sl@0: } sl@0: } sl@0: sl@0: return notifier.appContext; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * InitNotifier -- sl@0: * sl@0: * Initializes the notifier state. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Creates a new exit handler. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: InitNotifier() sl@0: { sl@0: Tcl_NotifierProcs notifier; sl@0: /* sl@0: * Only reinitialize if we are not in exit handling. The notifier sl@0: * can get reinitialized after its own exit handler has run, because sl@0: * of exit handlers for the I/O and timer sub-systems (order dependency). sl@0: */ sl@0: sl@0: if (TclInExit()) { sl@0: return; sl@0: } sl@0: sl@0: notifier.createFileHandlerProc = CreateFileHandler; sl@0: notifier.deleteFileHandlerProc = DeleteFileHandler; sl@0: notifier.setTimerProc = SetTimer; sl@0: notifier.waitForEventProc = WaitForEvent; sl@0: Tcl_SetNotifier(¬ifier); sl@0: sl@0: /* sl@0: * DO NOT create the application context yet; doing so would prevent sl@0: * external applications from setting it for us to their own ones. sl@0: */ sl@0: sl@0: initialized = 1; sl@0: memset(¬ifier, 0, sizeof(notifier)); sl@0: Tcl_CreateExitHandler(NotifierExitHandler, NULL); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * NotifierExitHandler -- sl@0: * sl@0: * This function is called to cleanup the notifier state before sl@0: * Tcl is unloaded. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Destroys the notifier window. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: NotifierExitHandler( sl@0: ClientData clientData) /* Not used. */ sl@0: { sl@0: if (notifier.currentTimeout != 0) { sl@0: XtRemoveTimeOut(notifier.currentTimeout); sl@0: } sl@0: for (; notifier.firstFileHandlerPtr != NULL; ) { sl@0: Tcl_DeleteFileHandler(notifier.firstFileHandlerPtr->fd); sl@0: } sl@0: if (notifier.appContextCreated) { sl@0: XtDestroyApplicationContext(notifier.appContext); sl@0: notifier.appContextCreated = 0; sl@0: notifier.appContext = NULL; sl@0: } sl@0: initialized = 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SetTimer -- sl@0: * sl@0: * This procedure sets the current notifier timeout value. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Replaces any previous timer. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: SetTimer(timePtr) sl@0: Tcl_Time *timePtr; /* Timeout value, may be NULL. */ sl@0: { sl@0: long timeout; sl@0: sl@0: if (!initialized) { sl@0: InitNotifier(); sl@0: } sl@0: sl@0: TclSetAppContext(NULL); sl@0: if (notifier.currentTimeout != 0) { sl@0: XtRemoveTimeOut(notifier.currentTimeout); sl@0: } sl@0: if (timePtr) { sl@0: timeout = timePtr->sec * 1000 + timePtr->usec / 1000; sl@0: notifier.currentTimeout = sl@0: XtAppAddTimeOut(notifier.appContext, (unsigned long) timeout, sl@0: TimerProc, NULL); sl@0: } else { sl@0: notifier.currentTimeout = 0; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TimerProc -- sl@0: * sl@0: * This procedure is the XtTimerCallbackProc used to handle sl@0: * timeouts. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Processes all queued events. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: TimerProc(data, id) sl@0: caddr_t data; /* Not used. */ sl@0: XtIntervalId *id; sl@0: { sl@0: if (*id != notifier.currentTimeout) { sl@0: return; sl@0: } sl@0: notifier.currentTimeout = 0; sl@0: sl@0: Tcl_ServiceAll(); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * CreateFileHandler -- sl@0: * sl@0: * This procedure registers a file handler with the Xt notifier. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Creates a new file handler structure and registers one or more sl@0: * input procedures with Xt. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: CreateFileHandler(fd, mask, proc, clientData) sl@0: int fd; /* Handle of stream to watch. */ sl@0: int mask; /* OR'ed combination of TCL_READABLE, sl@0: * TCL_WRITABLE, and TCL_EXCEPTION: sl@0: * indicates conditions under which sl@0: * proc should be called. */ sl@0: Tcl_FileProc *proc; /* Procedure to call for each sl@0: * selected event. */ sl@0: ClientData clientData; /* Arbitrary data to pass to proc. */ sl@0: { sl@0: FileHandler *filePtr; sl@0: sl@0: if (!initialized) { sl@0: InitNotifier(); sl@0: } sl@0: sl@0: TclSetAppContext(NULL); sl@0: sl@0: for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL; sl@0: filePtr = filePtr->nextPtr) { sl@0: if (filePtr->fd == fd) { sl@0: break; sl@0: } sl@0: } sl@0: if (filePtr == NULL) { sl@0: filePtr = (FileHandler*) ckalloc(sizeof(FileHandler)); sl@0: filePtr->fd = fd; sl@0: filePtr->read = 0; sl@0: filePtr->write = 0; sl@0: filePtr->except = 0; sl@0: filePtr->readyMask = 0; sl@0: filePtr->mask = 0; sl@0: filePtr->nextPtr = notifier.firstFileHandlerPtr; sl@0: notifier.firstFileHandlerPtr = filePtr; sl@0: } sl@0: filePtr->proc = proc; sl@0: filePtr->clientData = clientData; sl@0: sl@0: /* sl@0: * Register the file with the Xt notifier, if it hasn't been done yet. sl@0: */ sl@0: sl@0: if (mask & TCL_READABLE) { sl@0: if (!(filePtr->mask & TCL_READABLE)) { sl@0: filePtr->read = sl@0: XtAppAddInput(notifier.appContext, fd, XtInputReadMask, sl@0: FileProc, filePtr); sl@0: } sl@0: } else { sl@0: if (filePtr->mask & TCL_READABLE) { sl@0: XtRemoveInput(filePtr->read); sl@0: } sl@0: } sl@0: if (mask & TCL_WRITABLE) { sl@0: if (!(filePtr->mask & TCL_WRITABLE)) { sl@0: filePtr->write = sl@0: XtAppAddInput(notifier.appContext, fd, XtInputWriteMask, sl@0: FileProc, filePtr); sl@0: } sl@0: } else { sl@0: if (filePtr->mask & TCL_WRITABLE) { sl@0: XtRemoveInput(filePtr->write); sl@0: } sl@0: } sl@0: if (mask & TCL_EXCEPTION) { sl@0: if (!(filePtr->mask & TCL_EXCEPTION)) { sl@0: filePtr->except = sl@0: XtAppAddInput(notifier.appContext, fd, XtInputExceptMask, sl@0: FileProc, filePtr); sl@0: } sl@0: } else { sl@0: if (filePtr->mask & TCL_EXCEPTION) { sl@0: XtRemoveInput(filePtr->except); sl@0: } sl@0: } sl@0: filePtr->mask = mask; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * DeleteFileHandler -- sl@0: * sl@0: * Cancel a previously-arranged callback arrangement for sl@0: * a file. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * If a callback was previously registered on file, remove it. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: DeleteFileHandler(fd) sl@0: int fd; /* Stream id for which to remove sl@0: * callback procedure. */ sl@0: { sl@0: FileHandler *filePtr, *prevPtr; sl@0: sl@0: if (!initialized) { sl@0: InitNotifier(); sl@0: } sl@0: sl@0: TclSetAppContext(NULL); sl@0: sl@0: /* sl@0: * Find the entry for the given file (and return if there sl@0: * isn't one). sl@0: */ sl@0: sl@0: for (prevPtr = NULL, filePtr = notifier.firstFileHandlerPtr; ; sl@0: prevPtr = filePtr, filePtr = filePtr->nextPtr) { sl@0: if (filePtr == NULL) { sl@0: return; sl@0: } sl@0: if (filePtr->fd == fd) { sl@0: break; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Clean up information in the callback record. sl@0: */ sl@0: sl@0: if (prevPtr == NULL) { sl@0: notifier.firstFileHandlerPtr = filePtr->nextPtr; sl@0: } else { sl@0: prevPtr->nextPtr = filePtr->nextPtr; sl@0: } sl@0: if (filePtr->mask & TCL_READABLE) { sl@0: XtRemoveInput(filePtr->read); sl@0: } sl@0: if (filePtr->mask & TCL_WRITABLE) { sl@0: XtRemoveInput(filePtr->write); sl@0: } sl@0: if (filePtr->mask & TCL_EXCEPTION) { sl@0: XtRemoveInput(filePtr->except); sl@0: } sl@0: ckfree((char *) filePtr); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * FileProc -- sl@0: * sl@0: * These procedures are called by Xt when a file becomes readable, sl@0: * writable, or has an exception. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Makes an entry on the Tcl event queue if the event is sl@0: * interesting. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: FileProc(clientData, fd, id) sl@0: caddr_t clientData; sl@0: int *fd; sl@0: XtInputId *id; sl@0: { sl@0: FileHandler *filePtr = (FileHandler *)clientData; sl@0: FileHandlerEvent *fileEvPtr; sl@0: int mask = 0; sl@0: sl@0: /* sl@0: * Determine which event happened. sl@0: */ sl@0: sl@0: if (*id == filePtr->read) { sl@0: mask = TCL_READABLE; sl@0: } else if (*id == filePtr->write) { sl@0: mask = TCL_WRITABLE; sl@0: } else if (*id == filePtr->except) { sl@0: mask = TCL_EXCEPTION; sl@0: } sl@0: sl@0: /* sl@0: * Ignore unwanted or duplicate events. sl@0: */ sl@0: sl@0: if (!(filePtr->mask & mask) || (filePtr->readyMask & mask)) { sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * This is an interesting event, so put it onto the event queue. sl@0: */ sl@0: sl@0: filePtr->readyMask |= mask; sl@0: fileEvPtr = (FileHandlerEvent *) ckalloc(sizeof(FileHandlerEvent)); sl@0: fileEvPtr->header.proc = FileHandlerEventProc; sl@0: fileEvPtr->fd = filePtr->fd; sl@0: Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); sl@0: sl@0: /* sl@0: * Process events on the Tcl event queue before returning to Xt. sl@0: */ sl@0: sl@0: Tcl_ServiceAll(); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * FileHandlerEventProc -- sl@0: * sl@0: * This procedure is called by Tcl_ServiceEvent when a file event sl@0: * reaches the front of the event queue. This procedure is sl@0: * responsible for actually handling the event by invoking the sl@0: * callback for the file handler. sl@0: * sl@0: * Results: sl@0: * Returns 1 if the event was handled, meaning it should be removed sl@0: * from the queue. Returns 0 if the event was not handled, meaning sl@0: * it should stay on the queue. The only time the event isn't sl@0: * handled is if the TCL_FILE_EVENTS flag bit isn't set. sl@0: * sl@0: * Side effects: sl@0: * Whatever the file handler's callback procedure does. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: FileHandlerEventProc(evPtr, flags) sl@0: Tcl_Event *evPtr; /* Event to service. */ sl@0: int flags; /* Flags that indicate what events to sl@0: * handle, such as TCL_FILE_EVENTS. */ sl@0: { sl@0: FileHandler *filePtr; sl@0: FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr; sl@0: int mask; sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * Search through the file handlers to find the one whose handle matches sl@0: * the event. We do this rather than keeping a pointer to the file sl@0: * handler directly in the event, so that the handler can be deleted sl@0: * while the event is queued without leaving a dangling pointer. sl@0: */ sl@0: sl@0: for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL; sl@0: filePtr = filePtr->nextPtr) { sl@0: if (filePtr->fd != fileEvPtr->fd) { sl@0: continue; sl@0: } sl@0: sl@0: /* sl@0: * The code is tricky for two reasons: sl@0: * 1. The file handler's desired events could have changed sl@0: * since the time when the event was queued, so AND the sl@0: * ready mask with the desired mask. sl@0: * 2. The file could have been closed and re-opened since sl@0: * the time when the event was queued. This is why the sl@0: * ready mask is stored in the file handler rather than sl@0: * the queued event: it will be zeroed when a new sl@0: * file handler is created for the newly opened file. sl@0: */ sl@0: sl@0: mask = filePtr->readyMask & filePtr->mask; sl@0: filePtr->readyMask = 0; sl@0: if (mask != 0) { sl@0: (*filePtr->proc)(filePtr->clientData, mask); sl@0: } sl@0: break; sl@0: } sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * WaitForEvent -- sl@0: * sl@0: * This function is called by Tcl_DoOneEvent to wait for new sl@0: * events on the message queue. If the block time is 0, then sl@0: * Tcl_WaitForEvent just polls without blocking. sl@0: * sl@0: * Results: sl@0: * Returns 1 if an event was found, else 0. This ensures that sl@0: * Tcl_DoOneEvent will return 1, even if the event is handled sl@0: * by non-Tcl code. sl@0: * sl@0: * Side effects: sl@0: * Queues file events that are detected by the select. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: WaitForEvent( sl@0: Tcl_Time *timePtr) /* Maximum block time, or NULL. */ sl@0: { sl@0: int timeout; sl@0: sl@0: if (!initialized) { sl@0: InitNotifier(); sl@0: } sl@0: sl@0: TclSetAppContext(NULL); sl@0: sl@0: if (timePtr) { sl@0: timeout = timePtr->sec * 1000 + timePtr->usec / 1000; sl@0: if (timeout == 0) { sl@0: if (XtAppPending(notifier.appContext)) { sl@0: goto process; sl@0: } else { sl@0: return 0; sl@0: } sl@0: } else { sl@0: Tcl_SetTimer(timePtr); sl@0: } sl@0: } sl@0: process: sl@0: XtAppProcessEvent(notifier.appContext, XtIMAll); sl@0: return 1; sl@0: }