/*1* tclNotify.c --2*3* This file provides the parts of the Tcl event notifier that are4* the same on all platforms, plus a few other parts that are used5* on more than one platform but not all.6*7* The notifier is the lowest-level part of the event system. It8* manages an event queue that holds Tcl_Event structures and a list9* of event sources that can add events to the queue. It also10* contains the procedure Tcl_DoOneEvent that invokes the event11* sources and blocks to wait for new events, but Tcl_DoOneEvent12* is in the platform-specific part of the notifier (in files like13* tclUnixNotify.c).14*15* Copyright (c) 1995 Sun Microsystems, Inc.16*17* See the file "license.terms" for information on usage and redistribution18* of this file, and for a DISCLAIMER OF ALL WARRANTIES.19*20* SCCS: @(#) tclNotify.c 1.7 96/09/19 16:40:1621*/2223#include "tclInt.h"24#include "tclPort.h"2526/*27* The following variable records the address of the first event28* source in the list of all event sources for the application.29* This variable is accessed by the notifier to traverse the list30* and invoke each event source.31*/3233TclEventSource *tclFirstEventSourcePtr = NULL;3435/*36* The following variables indicate how long to block in the event37* notifier the next time it blocks (default: block forever).38*/3940static int blockTimeSet = 0; /* 0 means there is no maximum block41* time: block forever. */42static Tcl_Time blockTime; /* If blockTimeSet is 1, gives the43* maximum elapsed time for the next block. */4445/*46* The following variables keep track of the event queue. In addition47* to the first (next to be serviced) and last events in the queue,48* we keep track of a "marker" event. This provides a simple priority49* mechanism whereby events can be inserted at the front of the queue50* but behind all other high-priority events already in the queue (this51* is used for things like a sequence of Enter and Leave events generated52* during a grab in Tk).53*/5455static Tcl_Event *firstEventPtr = NULL;56/* First pending event, or NULL if none. */57static Tcl_Event *lastEventPtr = NULL;58/* Last pending event, or NULL if none. */59static Tcl_Event *markerEventPtr = NULL;60/* Last high-priority event in queue, or61* NULL if none. */6263/*64* Prototypes for procedures used only in this file:65*/6667static int ServiceEvent _ANSI_ARGS_((int flags));6869/*70*----------------------------------------------------------------------71*72* Tcl_CreateEventSource --73*74* This procedure is invoked to create a new source of events.75* The source is identified by a procedure that gets invoked76* during Tcl_DoOneEvent to check for events on that source77* and queue them.78*79*80* Results:81* None.82*83* Side effects:84* SetupProc and checkProc will be invoked each time that Tcl_DoOneEvent85* runs out of things to do. SetupProc will be invoked before86* Tcl_DoOneEvent calls select or whatever else it uses to wait87* for events. SetupProc typically calls functions like Tcl_WatchFile88* or Tcl_SetMaxBlockTime to indicate what to wait for.89*90* CheckProc is called after select or whatever operation was actually91* used to wait. It figures out whether anything interesting actually92* happened (e.g. by calling Tcl_FileReady), and then calls93* Tcl_QueueEvent to queue any events that are ready.94*95* Each of these procedures is passed two arguments, e.g.96* (*checkProc)(ClientData clientData, int flags));97* ClientData is the same as the clientData argument here, and flags98* is a combination of things like TCL_FILE_EVENTS that indicates99* what events are of interest: setupProc and checkProc use flags100* to figure out whether their events are relevant or not.101*102*----------------------------------------------------------------------103*/104105void106Tcl_CreateEventSource(setupProc, checkProc, clientData)107Tcl_EventSetupProc *setupProc; /* Procedure to invoke to figure out108* what to wait for. */109Tcl_EventCheckProc *checkProc; /* Procedure to call after waiting110* to see what happened. */111ClientData clientData; /* One-word argument to pass to112* setupProc and checkProc. */113{114TclEventSource *sourcePtr;115116sourcePtr = (TclEventSource *) ckalloc(sizeof(TclEventSource));117sourcePtr->setupProc = setupProc;118sourcePtr->checkProc = checkProc;119sourcePtr->clientData = clientData;120sourcePtr->nextPtr = tclFirstEventSourcePtr;121tclFirstEventSourcePtr = sourcePtr;122}123124/*125*----------------------------------------------------------------------126*127* Tcl_DeleteEventSource --128*129* This procedure is invoked to delete the source of events130* given by proc and clientData.131*132* Results:133* None.134*135* Side effects:136* The given event source is cancelled, so its procedure will137* never again be called. If no such source exists, nothing138* happens.139*140*----------------------------------------------------------------------141*/142143void144Tcl_DeleteEventSource(setupProc, checkProc, clientData)145Tcl_EventSetupProc *setupProc; /* Procedure to invoke to figure out146* what to wait for. */147Tcl_EventCheckProc *checkProc; /* Procedure to call after waiting148* to see what happened. */149ClientData clientData; /* One-word argument to pass to150* setupProc and checkProc. */151{152TclEventSource *sourcePtr, *prevPtr;153154for (sourcePtr = tclFirstEventSourcePtr, prevPtr = NULL;155sourcePtr != NULL;156prevPtr = sourcePtr, sourcePtr = sourcePtr->nextPtr) {157if ((sourcePtr->setupProc != setupProc)158|| (sourcePtr->checkProc != checkProc)159|| (sourcePtr->clientData != clientData)) {160continue;161}162if (prevPtr == NULL) {163tclFirstEventSourcePtr = sourcePtr->nextPtr;164} else {165prevPtr->nextPtr = sourcePtr->nextPtr;166}167ckfree((char *) sourcePtr);168return;169}170}171172/*173*----------------------------------------------------------------------174*175* Tcl_QueueEvent --176*177* Insert an event into the Tk event queue at one of three178* positions: the head, the tail, or before a floating marker.179* Events inserted before the marker will be processed in180* first-in-first-out order, but before any events inserted at181* the tail of the queue. Events inserted at the head of the182* queue will be processed in last-in-first-out order.183*184* Results:185* None.186*187* Side effects:188* None.189*190*----------------------------------------------------------------------191*/192193void194Tcl_QueueEvent(evPtr, position)195Tcl_Event* evPtr; /* Event to add to queue. The storage196* space must have been allocated the caller197* with malloc (ckalloc), and it becomes198* the property of the event queue. It199* will be freed after the event has been200* handled. */201Tcl_QueuePosition position; /* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD,202* TCL_QUEUE_MARK. */203{204if (position == TCL_QUEUE_TAIL) {205/*206* Append the event on the end of the queue.207*/208209evPtr->nextPtr = NULL;210if (firstEventPtr == NULL) {211firstEventPtr = evPtr;212} else {213lastEventPtr->nextPtr = evPtr;214}215lastEventPtr = evPtr;216} else if (position == TCL_QUEUE_HEAD) {217/*218* Push the event on the head of the queue.219*/220221evPtr->nextPtr = firstEventPtr;222if (firstEventPtr == NULL) {223lastEventPtr = evPtr;224}225firstEventPtr = evPtr;226} else if (position == TCL_QUEUE_MARK) {227/*228* Insert the event after the current marker event and advance229* the marker to the new event.230*/231232if (markerEventPtr == NULL) {233evPtr->nextPtr = firstEventPtr;234firstEventPtr = evPtr;235} else {236evPtr->nextPtr = markerEventPtr->nextPtr;237markerEventPtr->nextPtr = evPtr;238}239markerEventPtr = evPtr;240if (evPtr->nextPtr == NULL) {241lastEventPtr = evPtr;242}243}244}245246/*247*----------------------------------------------------------------------248*249* Tcl_DeleteEvents --250*251* Calls a procedure for each event in the queue and deletes those252* for which the procedure returns 1. Events for which the253* procedure returns 0 are left in the queue.254*255* Results:256* None.257*258* Side effects:259* Potentially removes one or more events from the event queue.260*261*----------------------------------------------------------------------262*/263264void265Tcl_DeleteEvents(proc, clientData)266Tcl_EventDeleteProc *proc; /* The procedure to call. */267ClientData clientData; /* type-specific data. */268{269Tcl_Event *evPtr, *prevPtr, *hold;270271for (prevPtr = (Tcl_Event *) NULL, evPtr = firstEventPtr;272evPtr != (Tcl_Event *) NULL;273) {274if ((*proc) (evPtr, clientData) == 1) {275if (firstEventPtr == evPtr) {276firstEventPtr = evPtr->nextPtr;277if (evPtr->nextPtr == (Tcl_Event *) NULL) {278lastEventPtr = (Tcl_Event *) NULL;279}280} else {281prevPtr->nextPtr = evPtr->nextPtr;282}283hold = evPtr;284evPtr = evPtr->nextPtr;285ckfree((char *) hold);286} else {287prevPtr = evPtr;288evPtr = evPtr->nextPtr;289}290}291}292293/*294*----------------------------------------------------------------------295*296* ServiceEvent --297*298* Process one event from the event queue. This routine is called299* by the notifier whenever it wants Tk to process an event.300*301* Results:302* The return value is 1 if the procedure actually found an event303* to process. If no processing occurred, then 0 is returned.304*305* Side effects:306* Invokes all of the event handlers for the highest priority307* event in the event queue. May collapse some events into a308* single event or discard stale events.309*310*----------------------------------------------------------------------311*/312313static int314ServiceEvent(flags)315int flags; /* Indicates what events should be processed.316* May be any combination of TCL_WINDOW_EVENTS317* TCL_FILE_EVENTS, TCL_TIMER_EVENTS, or other318* flags defined elsewhere. Events not319* matching this will be skipped for processing320* later. */321{322Tcl_Event *evPtr, *prevPtr;323Tcl_EventProc *proc;324325/*326* No event flags is equivalent to TCL_ALL_EVENTS.327*/328329if ((flags & TCL_ALL_EVENTS) == 0) {330flags |= TCL_ALL_EVENTS;331}332333/*334* Loop through all the events in the queue until we find one335* that can actually be handled.336*/337338for (evPtr = firstEventPtr; evPtr != NULL; evPtr = evPtr->nextPtr) {339/*340* Call the handler for the event. If it actually handles the341* event then free the storage for the event. There are two342* tricky things here, but stemming from the fact that the event343* code may be re-entered while servicing the event:344*345* 1. Set the "proc" field to NULL. This is a signal to ourselves346* that we shouldn't reexecute the handler if the event loop347* is re-entered.348* 2. When freeing the event, must search the queue again from the349* front to find it. This is because the event queue could350* change almost arbitrarily while handling the event, so we351* can't depend on pointers found now still being valid when352* the handler returns.353*/354355proc = evPtr->proc;356evPtr->proc = NULL;357if ((proc != NULL) && (*proc)(evPtr, flags)) {358if (firstEventPtr == evPtr) {359firstEventPtr = evPtr->nextPtr;360if (evPtr->nextPtr == NULL) {361lastEventPtr = NULL;362}363if (markerEventPtr == evPtr) {364markerEventPtr = NULL;365}366} else {367for (prevPtr = firstEventPtr; prevPtr->nextPtr != evPtr;368prevPtr = prevPtr->nextPtr) {369/* Empty loop body. */370}371prevPtr->nextPtr = evPtr->nextPtr;372if (evPtr->nextPtr == NULL) {373lastEventPtr = prevPtr;374}375if (markerEventPtr == evPtr) {376markerEventPtr = prevPtr;377}378}379ckfree((char *) evPtr);380return 1;381} else {382/*383* The event wasn't actually handled, so we have to restore384* the proc field to allow the event to be attempted again.385*/386387evPtr->proc = proc;388}389390/*391* The handler for this event asked to defer it. Just go on to392* the next event.393*/394395continue;396}397return 0;398}399400/*401*----------------------------------------------------------------------402*403* Tcl_SetMaxBlockTime --404*405* This procedure is invoked by event sources to tell the notifier406* how long it may block the next time it blocks. The timePtr407* argument gives a maximum time; the actual time may be less if408* some other event source requested a smaller time.409*410* Results:411* None.412*413* Side effects:414* May reduce the length of the next sleep in the notifier.415*416*----------------------------------------------------------------------417*/418419void420Tcl_SetMaxBlockTime(timePtr)421Tcl_Time *timePtr; /* Specifies a maximum elapsed time for422* the next blocking operation in the423* event notifier. */424{425if (!blockTimeSet || (timePtr->sec < blockTime.sec)426|| ((timePtr->sec == blockTime.sec)427&& (timePtr->usec < blockTime.usec))) {428blockTime = *timePtr;429blockTimeSet = 1;430}431}432433/*434*----------------------------------------------------------------------435*436* Tcl_DoOneEvent --437*438* Process a single event of some sort. If there's no work to439* do, wait for an event to occur, then process it.440*441* Results:442* The return value is 1 if the procedure actually found an event443* to process. If no processing occurred, then 0 is returned (this444* can happen if the TCL_DONT_WAIT flag is set or if there are no445* event handlers to wait for in the set specified by flags).446*447* Side effects:448* May delay execution of process while waiting for an event,449* unless TCL_DONT_WAIT is set in the flags argument. Event450* sources are invoked to check for and queue events. Event451* handlers may produce arbitrary side effects.452*453*----------------------------------------------------------------------454*/455456int457Tcl_DoOneEvent(flags)458int flags; /* Miscellaneous flag values: may be any459* combination of TCL_DONT_WAIT,460* TCL_WINDOW_EVENTS, TCL_FILE_EVENTS,461* TCL_TIMER_EVENTS, TCL_IDLE_EVENTS, or462* others defined by event sources. */463{464TclEventSource *sourcePtr;465Tcl_Time *timePtr;466467/*468* No event flags is equivalent to TCL_ALL_EVENTS.469*/470471if ((flags & TCL_ALL_EVENTS) == 0) {472flags |= TCL_ALL_EVENTS;473}474475/*476* The core of this procedure is an infinite loop, even though477* we only service one event. The reason for this is that we478* might think we have an event ready (e.g. the connection to479* the server becomes readable), but then we might discover that480* there's nothing interesting on that connection, so no event481* was serviced. Or, the select operation could return prematurely482* due to a signal. The easiest thing in both these cases is483* just to loop back and try again.484*/485486while (1) {487488sh_sigcheck(0); /* XXX: tksh specific */489490/*491* The first thing we do is to service any asynchronous event492* handlers.493*/494495if (Tcl_AsyncReady()) {496(void) Tcl_AsyncInvoke((Tcl_Interp *) NULL, 0);497return 1;498}499500/*501* If idle events are the only things to service, skip the502* main part of the loop and go directly to handle idle503* events (i.e. don't wait even if TCL_DONT_WAIT isn't set.504*/505506if (flags == TCL_IDLE_EVENTS) {507flags = TCL_IDLE_EVENTS|TCL_DONT_WAIT;508goto idleEvents;509}510511/*512* Ask Tk to service a queued event, if there are any.513*/514515if (ServiceEvent(flags)) {516return 1;517}518519/*520* There are no events already queued. Invoke all of the521* event sources to give them a chance to setup for the wait.522*/523524blockTimeSet = 0;525for (sourcePtr = tclFirstEventSourcePtr; sourcePtr != NULL;526sourcePtr = sourcePtr->nextPtr) {527(*sourcePtr->setupProc)(sourcePtr->clientData, flags);528}529if ((flags & TCL_DONT_WAIT) ||530((flags & TCL_IDLE_EVENTS) && TclIdlePending())) {531/*532* Don't block: there are idle events waiting, or we don't533* care about idle events anyway, or the caller asked us not534* to block.535*/536537blockTime.sec = 0;538blockTime.usec = 0;539timePtr = &blockTime;540} else if (blockTimeSet) {541timePtr = &blockTime;542} else {543timePtr = NULL;544}545546/*547* Wait until an event occurs or the timer expires.548*/549550if (Tcl_WaitForEvent(timePtr) == TCL_ERROR) {551return 0;552}553554/*555* Give each of the event sources a chance to queue events,556* then call ServiceEvent and give it another chance to557* service events.558*/559560for (sourcePtr = tclFirstEventSourcePtr; sourcePtr != NULL;561sourcePtr = sourcePtr->nextPtr) {562(*sourcePtr->checkProc)(sourcePtr->clientData, flags);563}564if (ServiceEvent(flags)) {565return 1;566}567568/*569* We've tried everything at this point, but nobody had anything570* to do. Check for idle events. If none, either quit or go back571* to the top and try again.572*/573574idleEvents:575if ((flags & TCL_IDLE_EVENTS) && TclServiceIdle()) {576return 1;577}578if (flags & TCL_DONT_WAIT) {579return 0;580}581}582}583584585