sl@0: /* sl@0: * tclNotify.c -- sl@0: * sl@0: * This file implements the generic portion of the Tcl notifier. sl@0: * The notifier is lowest-level part of the event system. It sl@0: * manages an event queue that holds Tcl_Event structures. The sl@0: * platform specific portion of the notifier is defined in the sl@0: * tcl*Notify.c files in each platform directory. sl@0: * sl@0: * Copyright (c) 1995-1997 Sun Microsystems, Inc. sl@0: * Copyright (c) 1998 by Scriptics Corporation. sl@0: * Copyright (c) 2003 by Kevin B. Kenny. All rights reserved. sl@0: * Portions Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiaries. All rights reserved. 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: tclNotify.c,v 1.11.2.2 2005/04/26 00:46:02 das Exp $ sl@0: */ sl@0: sl@0: #include "tclInt.h" sl@0: #include "tclPort.h" sl@0: #if defined(__SYMBIAN32__) && defined(__WINSCW__) sl@0: #include "tclSymbianGlobals.h" sl@0: #define dataKey getdataKey(5) sl@0: #endif sl@0: sl@0: extern TclStubs tclStubs; sl@0: sl@0: /* sl@0: * For each event source (created with Tcl_CreateEventSource) there sl@0: * is a structure of the following type: sl@0: */ sl@0: sl@0: typedef struct EventSource { sl@0: Tcl_EventSetupProc *setupProc; sl@0: Tcl_EventCheckProc *checkProc; sl@0: ClientData clientData; sl@0: struct EventSource *nextPtr; sl@0: } EventSource; sl@0: sl@0: /* sl@0: * The following structure keeps track of the state of the notifier on a sl@0: * per-thread basis. The first three elements keep track of the event queue. sl@0: * In addition to the first (next to be serviced) and last events in the queue, sl@0: * we keep track of a "marker" event. This provides a simple priority sl@0: * mechanism whereby events can be inserted at the front of the queue but sl@0: * behind all other high-priority events already in the queue (this is used for sl@0: * things like a sequence of Enter and Leave events generated during a grab in sl@0: * Tk). These elements are protected by the queueMutex so that any thread sl@0: * can queue an event on any notifier. Note that all of the values in this sl@0: * structure will be initialized to 0. sl@0: */ sl@0: sl@0: typedef struct ThreadSpecificData { sl@0: Tcl_Event *firstEventPtr; /* First pending event, or NULL if none. */ sl@0: Tcl_Event *lastEventPtr; /* Last pending event, or NULL if none. */ sl@0: Tcl_Event *markerEventPtr; /* Last high-priority event in queue, or sl@0: * NULL if none. */ sl@0: Tcl_Mutex queueMutex; /* Mutex to protect access to the previous sl@0: * three fields. */ sl@0: int serviceMode; /* One of TCL_SERVICE_NONE or sl@0: * TCL_SERVICE_ALL. */ sl@0: int blockTimeSet; /* 0 means there is no maximum block sl@0: * time: block forever. */ sl@0: Tcl_Time blockTime; /* If blockTimeSet is 1, gives the sl@0: * maximum elapsed time for the next block. */ sl@0: int inTraversal; /* 1 if Tcl_SetMaxBlockTime is being sl@0: * called during an event source traversal. */ sl@0: EventSource *firstEventSourcePtr; sl@0: /* Pointer to first event source in sl@0: * list of event sources for this thread. */ sl@0: Tcl_ThreadId threadId; /* Thread that owns this notifier instance. */ sl@0: ClientData clientData; /* Opaque handle for platform specific sl@0: * notifier. */ sl@0: int initialized; /* 1 if notifier has been initialized. */ sl@0: struct ThreadSpecificData *nextPtr; sl@0: /* Next notifier in global list of notifiers. sl@0: * Access is controlled by the listLock global sl@0: * mutex. */ sl@0: } ThreadSpecificData; sl@0: sl@0: #if !defined(__SYMBIAN32__) || !defined(__WINSCW__) sl@0: static Tcl_ThreadDataKey dataKey; sl@0: sl@0: sl@0: /* sl@0: * Global list of notifiers. Access to this list is controlled by the sl@0: * listLock mutex. If this becomes a performance bottleneck, this could sl@0: * be replaced with a hashtable. sl@0: */ sl@0: sl@0: static ThreadSpecificData *firstNotifierPtr; sl@0: #else sl@0: #define firstNotifierPtr (*(ThreadSpecificData**)get_firstNotifierPtr()) sl@0: #endif sl@0: TCL_DECLARE_MUTEX(listLock) sl@0: sl@0: /* sl@0: * Declarations for routines used only in this file. sl@0: */ sl@0: sl@0: static void QueueEvent _ANSI_ARGS_((ThreadSpecificData *tsdPtr, sl@0: Tcl_Event* evPtr, Tcl_QueuePosition position)); sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclInitNotifier -- sl@0: * sl@0: * Initialize the thread local data structures for the notifier sl@0: * subsystem. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Adds the current thread to the global list of notifiers. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: TclInitNotifier() sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: Tcl_MutexLock(&listLock); sl@0: sl@0: tsdPtr->threadId = Tcl_GetCurrentThread(); sl@0: tsdPtr->clientData = tclStubs.tcl_InitNotifier(); sl@0: tsdPtr->initialized = 1; sl@0: tsdPtr->nextPtr = firstNotifierPtr; sl@0: firstNotifierPtr = tsdPtr; sl@0: sl@0: Tcl_MutexUnlock(&listLock); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclFinalizeNotifier -- sl@0: * sl@0: * Finalize the thread local data structures for the notifier sl@0: * subsystem. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Removes the notifier associated with the current thread from sl@0: * the global notifier list. This is done only if the notifier sl@0: * was initialized for this thread by call to TclInitNotifier(). sl@0: * This is always true for threads which have been seeded with sl@0: * an Tcl interpreter, since the call to Tcl_CreateInterp will, sl@0: * among other things, call TclInitializeSubsystems() and this sl@0: * one will, in turn, call the TclInitNotifier() for the thread. sl@0: * For threads created without the Tcl interpreter, though, sl@0: * nobody is explicitly nor implicitly calling the TclInitNotifier sl@0: * hence, TclFinalizeNotifier should not be performed at all. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: TclFinalizeNotifier() sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: ThreadSpecificData **prevPtrPtr; sl@0: Tcl_Event *evPtr, *hold; sl@0: sl@0: if (!tsdPtr->initialized) { sl@0: return; /* Notifier not initialized for the current thread */ sl@0: } sl@0: sl@0: Tcl_MutexLock(&(tsdPtr->queueMutex)); sl@0: for (evPtr = tsdPtr->firstEventPtr; evPtr != (Tcl_Event *) NULL; ) { sl@0: hold = evPtr; sl@0: evPtr = evPtr->nextPtr; sl@0: ckfree((char *) hold); sl@0: } sl@0: tsdPtr->firstEventPtr = NULL; sl@0: tsdPtr->lastEventPtr = NULL; sl@0: Tcl_MutexUnlock(&(tsdPtr->queueMutex)); sl@0: sl@0: Tcl_MutexLock(&listLock); sl@0: sl@0: if (tclStubs.tcl_FinalizeNotifier) { sl@0: tclStubs.tcl_FinalizeNotifier(tsdPtr->clientData); sl@0: } sl@0: Tcl_MutexFinalize(&(tsdPtr->queueMutex)); sl@0: for (prevPtrPtr = &firstNotifierPtr; *prevPtrPtr != NULL; sl@0: prevPtrPtr = &((*prevPtrPtr)->nextPtr)) { sl@0: if (*prevPtrPtr == tsdPtr) { sl@0: *prevPtrPtr = tsdPtr->nextPtr; sl@0: break; sl@0: } sl@0: } sl@0: tsdPtr->initialized = 0; sl@0: sl@0: Tcl_MutexUnlock(&listLock); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_SetNotifier -- sl@0: * sl@0: * Install a set of alternate functions for use with the notifier. sl@0: # In particular, this can be used to install the Xt-based sl@0: * notifier for use with the Browser plugin. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Overstomps part of the stub vector. This relies on hooks sl@0: * added to the default procedures in case those are called sl@0: * directly (i.e., not through the stub table.) sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_SetNotifier(notifierProcPtr) sl@0: Tcl_NotifierProcs *notifierProcPtr; sl@0: { sl@0: #if !defined(__WIN32__) && !defined(MAC_TCL) /* UNIX */ sl@0: tclStubs.tcl_CreateFileHandler = notifierProcPtr->createFileHandlerProc; sl@0: tclStubs.tcl_DeleteFileHandler = notifierProcPtr->deleteFileHandlerProc; sl@0: #endif sl@0: tclStubs.tcl_SetTimer = notifierProcPtr->setTimerProc; sl@0: tclStubs.tcl_WaitForEvent = notifierProcPtr->waitForEventProc; sl@0: tclStubs.tcl_InitNotifier = notifierProcPtr->initNotifierProc; sl@0: tclStubs.tcl_FinalizeNotifier = notifierProcPtr->finalizeNotifierProc; sl@0: tclStubs.tcl_AlertNotifier = notifierProcPtr->alertNotifierProc; sl@0: tclStubs.tcl_ServiceModeHook = notifierProcPtr->serviceModeHookProc; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_CreateEventSource -- sl@0: * sl@0: * This procedure is invoked to create a new source of events. sl@0: * The source is identified by a procedure that gets invoked sl@0: * during Tcl_DoOneEvent to check for events on that source sl@0: * and queue them. sl@0: * sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * SetupProc and checkProc will be invoked each time that Tcl_DoOneEvent sl@0: * runs out of things to do. SetupProc will be invoked before sl@0: * Tcl_DoOneEvent calls select or whatever else it uses to wait sl@0: * for events. SetupProc typically calls functions like sl@0: * Tcl_SetMaxBlockTime to indicate what to wait for. sl@0: * sl@0: * CheckProc is called after select or whatever operation was actually sl@0: * used to wait. It figures out whether anything interesting actually sl@0: * happened (e.g. by calling Tcl_AsyncReady), and then calls sl@0: * Tcl_QueueEvent to queue any events that are ready. sl@0: * sl@0: * Each of these procedures is passed two arguments, e.g. sl@0: * (*checkProc)(ClientData clientData, int flags)); sl@0: * ClientData is the same as the clientData argument here, and flags sl@0: * is a combination of things like TCL_FILE_EVENTS that indicates sl@0: * what events are of interest: setupProc and checkProc use flags sl@0: * to figure out whether their events are relevant or not. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_CreateEventSource(setupProc, checkProc, clientData) sl@0: Tcl_EventSetupProc *setupProc; /* Procedure to invoke to figure out sl@0: * what to wait for. */ sl@0: Tcl_EventCheckProc *checkProc; /* Procedure to call after waiting sl@0: * to see what happened. */ sl@0: ClientData clientData; /* One-word argument to pass to sl@0: * setupProc and checkProc. */ sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: EventSource *sourcePtr = (EventSource *) ckalloc(sizeof(EventSource)); sl@0: sl@0: sourcePtr->setupProc = setupProc; sl@0: sourcePtr->checkProc = checkProc; sl@0: sourcePtr->clientData = clientData; sl@0: sourcePtr->nextPtr = tsdPtr->firstEventSourcePtr; sl@0: tsdPtr->firstEventSourcePtr = sourcePtr; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_DeleteEventSource -- sl@0: * sl@0: * This procedure is invoked to delete the source of events sl@0: * given by proc and clientData. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * The given event source is cancelled, so its procedure will sl@0: * never again be called. If no such source exists, nothing sl@0: * happens. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_DeleteEventSource(setupProc, checkProc, clientData) sl@0: Tcl_EventSetupProc *setupProc; /* Procedure to invoke to figure out sl@0: * what to wait for. */ sl@0: Tcl_EventCheckProc *checkProc; /* Procedure to call after waiting sl@0: * to see what happened. */ sl@0: ClientData clientData; /* One-word argument to pass to sl@0: * setupProc and checkProc. */ sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: EventSource *sourcePtr, *prevPtr; sl@0: sl@0: for (sourcePtr = tsdPtr->firstEventSourcePtr, prevPtr = NULL; sl@0: sourcePtr != NULL; sl@0: prevPtr = sourcePtr, sourcePtr = sourcePtr->nextPtr) { sl@0: if ((sourcePtr->setupProc != setupProc) sl@0: || (sourcePtr->checkProc != checkProc) sl@0: || (sourcePtr->clientData != clientData)) { sl@0: continue; sl@0: } sl@0: if (prevPtr == NULL) { sl@0: tsdPtr->firstEventSourcePtr = sourcePtr->nextPtr; sl@0: } else { sl@0: prevPtr->nextPtr = sourcePtr->nextPtr; sl@0: } sl@0: ckfree((char *) sourcePtr); sl@0: return; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_QueueEvent -- sl@0: * sl@0: * Queue an event on the event queue associated with the sl@0: * current thread. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_QueueEvent(evPtr, position) sl@0: Tcl_Event* evPtr; /* Event to add to queue. The storage sl@0: * space must have been allocated the caller sl@0: * with malloc (ckalloc), and it becomes sl@0: * the property of the event queue. It sl@0: * will be freed after the event has been sl@0: * handled. */ sl@0: Tcl_QueuePosition position; /* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, sl@0: * TCL_QUEUE_MARK. */ sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: QueueEvent(tsdPtr, evPtr, position); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_ThreadQueueEvent -- sl@0: * sl@0: * Queue an event on the specified thread's event queue. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_ThreadQueueEvent(threadId, evPtr, position) sl@0: Tcl_ThreadId threadId; /* Identifier for thread to use. */ sl@0: Tcl_Event* evPtr; /* Event to add to queue. The storage sl@0: * space must have been allocated the caller sl@0: * with malloc (ckalloc), and it becomes sl@0: * the property of the event queue. It sl@0: * will be freed after the event has been sl@0: * handled. */ sl@0: Tcl_QueuePosition position; /* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, sl@0: * TCL_QUEUE_MARK. */ sl@0: { sl@0: ThreadSpecificData *tsdPtr; sl@0: sl@0: /* sl@0: * Find the notifier associated with the specified thread. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&listLock); sl@0: for (tsdPtr = firstNotifierPtr; tsdPtr && tsdPtr->threadId != threadId; sl@0: tsdPtr = tsdPtr->nextPtr) { sl@0: /* Empty loop body. */ sl@0: } sl@0: sl@0: /* sl@0: * Queue the event if there was a notifier associated with the thread. sl@0: */ sl@0: sl@0: if (tsdPtr) { sl@0: QueueEvent(tsdPtr, evPtr, position); sl@0: } sl@0: Tcl_MutexUnlock(&listLock); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * QueueEvent -- sl@0: * sl@0: * Insert an event into the specified thread's event queue at one sl@0: * of three positions: the head, the tail, or before a floating sl@0: * marker. Events inserted before the marker will be processed in sl@0: * first-in-first-out order, but before any events inserted at sl@0: * the tail of the queue. Events inserted at the head of the sl@0: * queue will be processed in last-in-first-out order. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: QueueEvent(tsdPtr, evPtr, position) sl@0: ThreadSpecificData *tsdPtr; /* Handle to thread local data that indicates sl@0: * which event queue to use. */ sl@0: Tcl_Event* evPtr; /* Event to add to queue. The storage sl@0: * space must have been allocated the caller sl@0: * with malloc (ckalloc), and it becomes sl@0: * the property of the event queue. It sl@0: * will be freed after the event has been sl@0: * handled. */ sl@0: Tcl_QueuePosition position; /* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, sl@0: * TCL_QUEUE_MARK. */ sl@0: { sl@0: Tcl_MutexLock(&(tsdPtr->queueMutex)); sl@0: if (position == TCL_QUEUE_TAIL) { sl@0: /* sl@0: * Append the event on the end of the queue. sl@0: */ sl@0: sl@0: evPtr->nextPtr = NULL; sl@0: if (tsdPtr->firstEventPtr == NULL) { sl@0: tsdPtr->firstEventPtr = evPtr; sl@0: } else { sl@0: tsdPtr->lastEventPtr->nextPtr = evPtr; sl@0: } sl@0: tsdPtr->lastEventPtr = evPtr; sl@0: } else if (position == TCL_QUEUE_HEAD) { sl@0: /* sl@0: * Push the event on the head of the queue. sl@0: */ sl@0: sl@0: evPtr->nextPtr = tsdPtr->firstEventPtr; sl@0: if (tsdPtr->firstEventPtr == NULL) { sl@0: tsdPtr->lastEventPtr = evPtr; sl@0: } sl@0: tsdPtr->firstEventPtr = evPtr; sl@0: } else if (position == TCL_QUEUE_MARK) { sl@0: /* sl@0: * Insert the event after the current marker event and advance sl@0: * the marker to the new event. sl@0: */ sl@0: sl@0: if (tsdPtr->markerEventPtr == NULL) { sl@0: evPtr->nextPtr = tsdPtr->firstEventPtr; sl@0: tsdPtr->firstEventPtr = evPtr; sl@0: } else { sl@0: evPtr->nextPtr = tsdPtr->markerEventPtr->nextPtr; sl@0: tsdPtr->markerEventPtr->nextPtr = evPtr; sl@0: } sl@0: tsdPtr->markerEventPtr = evPtr; sl@0: if (evPtr->nextPtr == NULL) { sl@0: tsdPtr->lastEventPtr = evPtr; sl@0: } sl@0: } sl@0: Tcl_MutexUnlock(&(tsdPtr->queueMutex)); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_DeleteEvents -- sl@0: * sl@0: * Calls a procedure for each event in the queue and deletes those sl@0: * for which the procedure returns 1. Events for which the sl@0: * procedure returns 0 are left in the queue. Operates on the sl@0: * queue associated with the current thread. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Potentially removes one or more events from the event queue. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_DeleteEvents(proc, clientData) sl@0: Tcl_EventDeleteProc *proc; /* The procedure to call. */ sl@0: ClientData clientData; /* type-specific data. */ sl@0: { sl@0: Tcl_Event *evPtr, *prevPtr, *hold; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: Tcl_MutexLock(&(tsdPtr->queueMutex)); sl@0: for (prevPtr = (Tcl_Event *) NULL, evPtr = tsdPtr->firstEventPtr; sl@0: evPtr != (Tcl_Event *) NULL; sl@0: ) { sl@0: if ((*proc) (evPtr, clientData) == 1) { sl@0: if (tsdPtr->firstEventPtr == evPtr) { sl@0: tsdPtr->firstEventPtr = evPtr->nextPtr; sl@0: } else { sl@0: prevPtr->nextPtr = evPtr->nextPtr; sl@0: } sl@0: if (evPtr->nextPtr == (Tcl_Event *) NULL) { sl@0: tsdPtr->lastEventPtr = prevPtr; sl@0: } sl@0: if (tsdPtr->markerEventPtr == evPtr) { sl@0: tsdPtr->markerEventPtr = prevPtr; sl@0: } sl@0: hold = evPtr; sl@0: evPtr = evPtr->nextPtr; sl@0: ckfree((char *) hold); sl@0: } else { sl@0: prevPtr = evPtr; sl@0: evPtr = evPtr->nextPtr; sl@0: } sl@0: } sl@0: Tcl_MutexUnlock(&(tsdPtr->queueMutex)); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_ServiceEvent -- sl@0: * sl@0: * Process one event from the event queue, or invoke an sl@0: * asynchronous event handler. Operates on event queue for sl@0: * current thread. sl@0: * sl@0: * Results: sl@0: * The return value is 1 if the procedure actually found an event sl@0: * to process. If no processing occurred, then 0 is returned. sl@0: * sl@0: * Side effects: sl@0: * Invokes all of the event handlers for the highest priority sl@0: * event in the event queue. May collapse some events into a sl@0: * single event or discard stale events. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C int sl@0: Tcl_ServiceEvent(flags) sl@0: int flags; /* Indicates what events should be processed. sl@0: * May be any combination of TCL_WINDOW_EVENTS sl@0: * TCL_FILE_EVENTS, TCL_TIMER_EVENTS, or other sl@0: * flags defined elsewhere. Events not sl@0: * matching this will be skipped for processing sl@0: * later. */ sl@0: { sl@0: Tcl_Event *evPtr, *prevPtr; sl@0: Tcl_EventProc *proc; sl@0: int result; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: /* sl@0: * Asynchronous event handlers are considered to be the highest sl@0: * priority events, and so must be invoked before we process events sl@0: * on the event queue. sl@0: */ sl@0: sl@0: if (Tcl_AsyncReady()) { sl@0: (void) Tcl_AsyncInvoke((Tcl_Interp *) NULL, 0); sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: * No event flags is equivalent to TCL_ALL_EVENTS. sl@0: */ sl@0: sl@0: if ((flags & TCL_ALL_EVENTS) == 0) { sl@0: flags |= TCL_ALL_EVENTS; sl@0: } sl@0: sl@0: /* sl@0: * Loop through all the events in the queue until we find one sl@0: * that can actually be handled. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&(tsdPtr->queueMutex)); sl@0: for (evPtr = tsdPtr->firstEventPtr; evPtr != NULL; sl@0: evPtr = evPtr->nextPtr) { sl@0: /* sl@0: * Call the handler for the event. If it actually handles the sl@0: * event then free the storage for the event. There are two sl@0: * tricky things here, both stemming from the fact that the event sl@0: * code may be re-entered while servicing the event: sl@0: * sl@0: * 1. Set the "proc" field to NULL. This is a signal to ourselves sl@0: * that we shouldn't reexecute the handler if the event loop sl@0: * is re-entered. sl@0: * 2. When freeing the event, must search the queue again from the sl@0: * front to find it. This is because the event queue could sl@0: * change almost arbitrarily while handling the event, so we sl@0: * can't depend on pointers found now still being valid when sl@0: * the handler returns. sl@0: */ sl@0: sl@0: proc = evPtr->proc; sl@0: if (proc == NULL) { sl@0: continue; sl@0: } sl@0: evPtr->proc = NULL; sl@0: sl@0: /* sl@0: * Release the lock before calling the event procedure. This sl@0: * allows other threads to post events if we enter a recursive sl@0: * event loop in this thread. Note that we are making the assumption sl@0: * that if the proc returns 0, the event is still in the list. sl@0: */ sl@0: sl@0: Tcl_MutexUnlock(&(tsdPtr->queueMutex)); sl@0: result = (*proc)(evPtr, flags); sl@0: Tcl_MutexLock(&(tsdPtr->queueMutex)); sl@0: sl@0: if (result) { sl@0: /* sl@0: * The event was processed, so remove it from the queue. sl@0: */ sl@0: sl@0: if (tsdPtr->firstEventPtr == evPtr) { sl@0: tsdPtr->firstEventPtr = evPtr->nextPtr; sl@0: if (evPtr->nextPtr == NULL) { sl@0: tsdPtr->lastEventPtr = NULL; sl@0: } sl@0: if (tsdPtr->markerEventPtr == evPtr) { sl@0: tsdPtr->markerEventPtr = NULL; sl@0: } sl@0: } else { sl@0: for (prevPtr = tsdPtr->firstEventPtr; sl@0: prevPtr && prevPtr->nextPtr != evPtr; sl@0: prevPtr = prevPtr->nextPtr) { sl@0: /* Empty loop body. */ sl@0: } sl@0: if (prevPtr) { sl@0: prevPtr->nextPtr = evPtr->nextPtr; sl@0: if (evPtr->nextPtr == NULL) { sl@0: tsdPtr->lastEventPtr = prevPtr; sl@0: } sl@0: if (tsdPtr->markerEventPtr == evPtr) { sl@0: tsdPtr->markerEventPtr = prevPtr; sl@0: } sl@0: } else { sl@0: evPtr = NULL; sl@0: } sl@0: } sl@0: if (evPtr) { sl@0: ckfree((char *) evPtr); sl@0: } sl@0: Tcl_MutexUnlock(&(tsdPtr->queueMutex)); sl@0: return 1; sl@0: } else { sl@0: /* sl@0: * The event wasn't actually handled, so we have to restore sl@0: * the proc field to allow the event to be attempted again. sl@0: */ sl@0: sl@0: evPtr->proc = proc; sl@0: } sl@0: } sl@0: Tcl_MutexUnlock(&(tsdPtr->queueMutex)); sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_GetServiceMode -- sl@0: * sl@0: * This routine returns the current service mode of the notifier. sl@0: * sl@0: * Results: sl@0: * Returns either TCL_SERVICE_ALL or TCL_SERVICE_NONE. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C int sl@0: Tcl_GetServiceMode() sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: return tsdPtr->serviceMode; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_SetServiceMode -- sl@0: * sl@0: * This routine sets the current service mode of the tsdPtr-> sl@0: * sl@0: * Results: sl@0: * Returns the previous service mode. sl@0: * sl@0: * Side effects: sl@0: * Invokes the notifier service mode hook procedure. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C int sl@0: Tcl_SetServiceMode(mode) sl@0: int mode; /* New service mode: TCL_SERVICE_ALL or sl@0: * TCL_SERVICE_NONE */ sl@0: { sl@0: int oldMode; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: oldMode = tsdPtr->serviceMode; sl@0: tsdPtr->serviceMode = mode; sl@0: if (tclStubs.tcl_ServiceModeHook) { sl@0: tclStubs.tcl_ServiceModeHook(mode); sl@0: } sl@0: return oldMode; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_SetMaxBlockTime -- sl@0: * sl@0: * This procedure is invoked by event sources to tell the notifier sl@0: * how long it may block the next time it blocks. The timePtr sl@0: * argument gives a maximum time; the actual time may be less if sl@0: * some other event source requested a smaller time. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * May reduce the length of the next sleep in the tsdPtr-> sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_SetMaxBlockTime(timePtr) sl@0: Tcl_Time *timePtr; /* Specifies a maximum elapsed time for sl@0: * the next blocking operation in the sl@0: * event tsdPtr-> */ sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (!tsdPtr->blockTimeSet || (timePtr->sec < tsdPtr->blockTime.sec) sl@0: || ((timePtr->sec == tsdPtr->blockTime.sec) sl@0: && (timePtr->usec < tsdPtr->blockTime.usec))) { sl@0: tsdPtr->blockTime = *timePtr; sl@0: tsdPtr->blockTimeSet = 1; sl@0: } sl@0: sl@0: /* sl@0: * If we are called outside an event source traversal, set the sl@0: * timeout immediately. sl@0: */ sl@0: sl@0: if (!tsdPtr->inTraversal) { sl@0: if (tsdPtr->blockTimeSet) { sl@0: Tcl_SetTimer(&tsdPtr->blockTime); sl@0: } else { sl@0: Tcl_SetTimer(NULL); sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_DoOneEvent -- sl@0: * sl@0: * Process a single event of some sort. If there's no work to sl@0: * do, wait for an event to occur, then process it. sl@0: * sl@0: * Results: sl@0: * The return value is 1 if the procedure actually found an event sl@0: * to process. If no processing occurred, then 0 is returned (this sl@0: * can happen if the TCL_DONT_WAIT flag is set or if there are no sl@0: * event handlers to wait for in the set specified by flags). sl@0: * sl@0: * Side effects: sl@0: * May delay execution of process while waiting for an event, sl@0: * unless TCL_DONT_WAIT is set in the flags argument. Event sl@0: * sources are invoked to check for and queue events. Event sl@0: * handlers may produce arbitrary side effects. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C int sl@0: Tcl_DoOneEvent(flags) sl@0: int flags; /* Miscellaneous flag values: may be any sl@0: * combination of TCL_DONT_WAIT, sl@0: * TCL_WINDOW_EVENTS, TCL_FILE_EVENTS, sl@0: * TCL_TIMER_EVENTS, TCL_IDLE_EVENTS, or sl@0: * others defined by event sources. */ sl@0: { sl@0: int result = 0, oldMode; sl@0: EventSource *sourcePtr; sl@0: Tcl_Time *timePtr; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: /* sl@0: * The first thing we do is to service any asynchronous event sl@0: * handlers. sl@0: */ sl@0: sl@0: if (Tcl_AsyncReady()) { sl@0: (void) Tcl_AsyncInvoke((Tcl_Interp *) NULL, 0); sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: * No event flags is equivalent to TCL_ALL_EVENTS. sl@0: */ sl@0: sl@0: if ((flags & TCL_ALL_EVENTS) == 0) { sl@0: flags |= TCL_ALL_EVENTS; sl@0: } sl@0: sl@0: /* sl@0: * Set the service mode to none so notifier event routines won't sl@0: * try to service events recursively. sl@0: */ sl@0: sl@0: oldMode = tsdPtr->serviceMode; sl@0: tsdPtr->serviceMode = TCL_SERVICE_NONE; sl@0: sl@0: /* sl@0: * The core of this procedure is an infinite loop, even though sl@0: * we only service one event. The reason for this is that we sl@0: * may be processing events that don't do anything inside of Tcl. sl@0: */ sl@0: sl@0: while (1) { sl@0: sl@0: /* sl@0: * If idle events are the only things to service, skip the sl@0: * main part of the loop and go directly to handle idle sl@0: * events (i.e. don't wait even if TCL_DONT_WAIT isn't set). sl@0: */ sl@0: sl@0: if ((flags & TCL_ALL_EVENTS) == TCL_IDLE_EVENTS) { sl@0: flags = TCL_IDLE_EVENTS|TCL_DONT_WAIT; sl@0: goto idleEvents; sl@0: } sl@0: sl@0: /* sl@0: * Ask Tcl to service a queued event, if there are any. sl@0: */ sl@0: sl@0: if (Tcl_ServiceEvent(flags)) { sl@0: result = 1; sl@0: break; sl@0: } sl@0: sl@0: /* sl@0: * If TCL_DONT_WAIT is set, be sure to poll rather than sl@0: * blocking, otherwise reset the block time to infinity. sl@0: */ sl@0: sl@0: if (flags & TCL_DONT_WAIT) { sl@0: tsdPtr->blockTime.sec = 0; sl@0: tsdPtr->blockTime.usec = 0; sl@0: tsdPtr->blockTimeSet = 1; sl@0: } else { sl@0: tsdPtr->blockTimeSet = 0; sl@0: } sl@0: sl@0: /* sl@0: * Set up all the event sources for new events. This will sl@0: * cause the block time to be updated if necessary. sl@0: */ sl@0: sl@0: tsdPtr->inTraversal = 1; sl@0: for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; sl@0: sourcePtr = sourcePtr->nextPtr) { sl@0: if (sourcePtr->setupProc) { sl@0: (sourcePtr->setupProc)(sourcePtr->clientData, flags); sl@0: } sl@0: } sl@0: tsdPtr->inTraversal = 0; sl@0: sl@0: if ((flags & TCL_DONT_WAIT) || tsdPtr->blockTimeSet) { sl@0: timePtr = &tsdPtr->blockTime; sl@0: } else { sl@0: timePtr = NULL; sl@0: } sl@0: sl@0: /* sl@0: * Wait for a new event or a timeout. If Tcl_WaitForEvent sl@0: * returns -1, we should abort Tcl_DoOneEvent. sl@0: */ sl@0: sl@0: result = Tcl_WaitForEvent(timePtr); sl@0: if (result < 0) { sl@0: result = 0; sl@0: break; sl@0: } sl@0: sl@0: /* sl@0: * Check all the event sources for new events. sl@0: */ sl@0: sl@0: for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; sl@0: sourcePtr = sourcePtr->nextPtr) { sl@0: if (sourcePtr->checkProc) { sl@0: (sourcePtr->checkProc)(sourcePtr->clientData, flags); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Check for events queued by the notifier or event sources. sl@0: */ sl@0: sl@0: if (Tcl_ServiceEvent(flags)) { sl@0: result = 1; sl@0: break; sl@0: } sl@0: sl@0: /* sl@0: * We've tried everything at this point, but nobody we know sl@0: * about had anything to do. Check for idle events. If none, sl@0: * either quit or go back to the top and try again. sl@0: */ sl@0: sl@0: idleEvents: sl@0: if (flags & TCL_IDLE_EVENTS) { sl@0: if (TclServiceIdle()) { sl@0: result = 1; sl@0: break; sl@0: } sl@0: } sl@0: if (flags & TCL_DONT_WAIT) { sl@0: break; sl@0: } sl@0: sl@0: /* sl@0: * If Tcl_WaitForEvent has returned 1, sl@0: * indicating that one system event has been dispatched sl@0: * (and thus that some Tcl code might have been indirectly executed), sl@0: * we break out of the loop. sl@0: * We do this to give VwaitCmd for instance a chance to check sl@0: * if that system event had the side effect of changing the sl@0: * variable (so the vwait can return and unwind properly). sl@0: * sl@0: * NB: We will process idle events if any first, because sl@0: * otherwise we might never do the idle events if the notifier sl@0: * always gets system events. sl@0: */ sl@0: sl@0: if (result) { sl@0: break; sl@0: } sl@0: sl@0: } sl@0: sl@0: tsdPtr->serviceMode = oldMode; sl@0: return result; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_ServiceAll -- sl@0: * sl@0: * This routine checks all of the event sources, processes sl@0: * events that are on the Tcl event queue, and then calls the sl@0: * any idle handlers. Platform specific notifier callbacks that sl@0: * generate events should call this routine before returning to sl@0: * the system in order to ensure that Tcl gets a chance to sl@0: * process the new events. sl@0: * sl@0: * Results: sl@0: * Returns 1 if an event or idle handler was invoked, else 0. sl@0: * sl@0: * Side effects: sl@0: * Anything that an event or idle handler may do. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C int sl@0: Tcl_ServiceAll() sl@0: { sl@0: int result = 0; sl@0: EventSource *sourcePtr; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (tsdPtr->serviceMode == TCL_SERVICE_NONE) { sl@0: return result; sl@0: } sl@0: sl@0: /* sl@0: * We need to turn off event servicing like we to in Tcl_DoOneEvent, sl@0: * to avoid recursive calls. sl@0: */ sl@0: sl@0: tsdPtr->serviceMode = TCL_SERVICE_NONE; sl@0: sl@0: /* sl@0: * Check async handlers first. sl@0: */ sl@0: sl@0: if (Tcl_AsyncReady()) { sl@0: (void) Tcl_AsyncInvoke((Tcl_Interp *) NULL, 0); sl@0: } sl@0: sl@0: /* sl@0: * Make a single pass through all event sources, queued events, sl@0: * and idle handlers. Note that we wait to update the notifier sl@0: * timer until the end so we can avoid multiple changes. sl@0: */ sl@0: sl@0: tsdPtr->inTraversal = 1; sl@0: tsdPtr->blockTimeSet = 0; sl@0: sl@0: for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; sl@0: sourcePtr = sourcePtr->nextPtr) { sl@0: if (sourcePtr->setupProc) { sl@0: (sourcePtr->setupProc)(sourcePtr->clientData, TCL_ALL_EVENTS); sl@0: } sl@0: } sl@0: for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; sl@0: sourcePtr = sourcePtr->nextPtr) { sl@0: if (sourcePtr->checkProc) { sl@0: (sourcePtr->checkProc)(sourcePtr->clientData, TCL_ALL_EVENTS); sl@0: } sl@0: } sl@0: sl@0: while (Tcl_ServiceEvent(0)) { sl@0: result = 1; sl@0: } sl@0: if (TclServiceIdle()) { sl@0: result = 1; sl@0: } sl@0: sl@0: if (!tsdPtr->blockTimeSet) { sl@0: Tcl_SetTimer(NULL); sl@0: } else { sl@0: Tcl_SetTimer(&tsdPtr->blockTime); sl@0: } sl@0: tsdPtr->inTraversal = 0; sl@0: tsdPtr->serviceMode = TCL_SERVICE_ALL; sl@0: return result; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_ThreadAlert -- sl@0: * sl@0: * This function wakes up the notifier associated with the sl@0: * specified thread (if there is one). sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: EXPORT_C void sl@0: Tcl_ThreadAlert(threadId) sl@0: Tcl_ThreadId threadId; /* Identifier for thread to use. */ sl@0: { sl@0: ThreadSpecificData *tsdPtr; sl@0: sl@0: /* sl@0: * Find the notifier associated with the specified thread. sl@0: * Note that we need to hold the listLock while calling sl@0: * Tcl_AlertNotifier to avoid a race condition where sl@0: * the specified thread might destroy its notifier. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&listLock); sl@0: for (tsdPtr = firstNotifierPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { sl@0: if (tsdPtr->threadId == threadId) { sl@0: if (tclStubs.tcl_AlertNotifier) { sl@0: tclStubs.tcl_AlertNotifier(tsdPtr->clientData); sl@0: } sl@0: break; sl@0: } sl@0: } sl@0: Tcl_MutexUnlock(&listLock); sl@0: }