1 /*
2  * tclNotify.c --
3  *
4  *	This file implements the generic portion of the Tcl notifier. The
5  *	notifier is lowest-level part of the event system. It manages an event
6  *	queue that holds Tcl_Event structures. The platform specific portion
7  *	of the notifier is defined in the tcl*Notify.c files in each platform
8  *	directory.
9  *
10  * Copyright © 1995-1997 Sun Microsystems, Inc.
11  * Copyright © 1998 Scriptics Corporation.
12  * Copyright © 2003 Kevin B. Kenny.  All rights reserved.
13  * Copyright © 2021 Donal K. Fellows
14  *
15  * See the file "license.terms" for information on usage and redistribution of
16  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
17  */
18 
19 #include "tclInt.h"
20 
21 /*
22  * Notifier hooks that are checked in the public wrappers for the default
23  * notifier functions (for overriding via Tcl_SetNotifier).
24  */
25 
26 static Tcl_NotifierProcs tclNotifierHooks = {
27     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
28 };
29 
30 /*
31  * For each event source (created with Tcl_CreateEventSource) there is a
32  * structure of the following type:
33  */
34 
35 typedef struct EventSource {
36     Tcl_EventSetupProc *setupProc;
37     Tcl_EventCheckProc *checkProc;
38     ClientData clientData;
39     struct EventSource *nextPtr;
40 } EventSource;
41 
42 /*
43  * The following structure keeps track of the state of the notifier on a
44  * per-thread basis. The first three elements keep track of the event queue.
45  * In addition to the first (next to be serviced) and last events in the
46  * queue, we keep track of a "marker" event. This provides a simple priority
47  * mechanism whereby events can be inserted at the front of the queue but
48  * behind all other high-priority events already in the queue (this is used
49  * for things like a sequence of Enter and Leave events generated during a
50  * grab in Tk). These elements are protected by the queueMutex so that any
51  * thread can queue an event on any notifier. Note that all of the values in
52  * this structure will be initialized to 0.
53  */
54 
55 typedef struct ThreadSpecificData {
56     Tcl_Event *firstEventPtr;	/* First pending event, or NULL if none. */
57     Tcl_Event *lastEventPtr;	/* Last pending event, or NULL if none. */
58     Tcl_Event *markerEventPtr;	/* Last high-priority event in queue, or NULL
59 				 * if none. */
60     Tcl_Mutex queueMutex;	/* Mutex to protect access to the previous
61 				 * three fields. */
62     int serviceMode;		/* One of TCL_SERVICE_NONE or
63 				 * TCL_SERVICE_ALL. */
64     int blockTimeSet;		/* 0 means there is no maximum block time:
65 				 * block forever. */
66     Tcl_Time blockTime;		/* If blockTimeSet is 1, gives the maximum
67 				 * elapsed time for the next block. */
68     int inTraversal;		/* 1 if Tcl_SetMaxBlockTime is being called
69 				 * during an event source traversal. */
70     EventSource *firstEventSourcePtr;
71 				/* Pointer to first event source in list of
72 				 * event sources for this thread. */
73     Tcl_ThreadId threadId;	/* Thread that owns this notifier instance. */
74     ClientData clientData;	/* Opaque handle for platform specific
75 				 * notifier. */
76     int initialized;		/* 1 if notifier has been initialized. */
77     struct ThreadSpecificData *nextPtr;
78 				/* Next notifier in global list of notifiers.
79 				 * Access is controlled by the listLock global
80 				 * mutex. */
81 } ThreadSpecificData;
82 
83 static Tcl_ThreadDataKey dataKey;
84 
85 /*
86  * Global list of notifiers. Access to this list is controlled by the listLock
87  * mutex. If this becomes a performance bottleneck, this could be replaced
88  * with a hashtable.
89  */
90 
91 static ThreadSpecificData *firstNotifierPtr = NULL;
92 TCL_DECLARE_MUTEX(listLock)
93 
94 /*
95  * Declarations for routines used only in this file.
96  */
97 
98 static void		QueueEvent(ThreadSpecificData *tsdPtr,
99 			    Tcl_Event *evPtr, Tcl_QueuePosition position);
100 
101 /*
102  *----------------------------------------------------------------------
103  *
104  * TclInitNotifier --
105  *
106  *	Initialize the thread local data structures for the notifier
107  *	subsystem.
108  *
109  * Results:
110  *	None.
111  *
112  * Side effects:
113  *	Adds the current thread to the global list of notifiers.
114  *
115  *----------------------------------------------------------------------
116  */
117 
118 void
TclInitNotifier(void)119 TclInitNotifier(void)
120 {
121     ThreadSpecificData *tsdPtr;
122     Tcl_ThreadId threadId = Tcl_GetCurrentThread();
123 
124     Tcl_MutexLock(&listLock);
125     for (tsdPtr = firstNotifierPtr; tsdPtr && tsdPtr->threadId != threadId;
126 	    tsdPtr = tsdPtr->nextPtr) {
127 	/* Empty loop body. */
128     }
129 
130     if (NULL == tsdPtr) {
131 	/*
132 	 * Notifier not yet initialized in this thread.
133 	 */
134 
135 	tsdPtr = TCL_TSD_INIT(&dataKey);
136 	tsdPtr->threadId = threadId;
137 	tsdPtr->clientData = Tcl_InitNotifier();
138 	tsdPtr->initialized = 1;
139 	tsdPtr->nextPtr = firstNotifierPtr;
140 	firstNotifierPtr = tsdPtr;
141     }
142     Tcl_MutexUnlock(&listLock);
143 }
144 
145 /*
146  *----------------------------------------------------------------------
147  *
148  * TclFinalizeNotifier --
149  *
150  *	Finalize the thread local data structures for the notifier subsystem.
151  *
152  * Results:
153  *	None.
154  *
155  * Side effects:
156  *	Removes the notifier associated with the current thread from the
157  *	global notifier list. This is done only if the notifier was
158  *	initialized for this thread by call to TclInitNotifier(). This is
159  *	always true for threads which have been seeded with an Tcl
160  *	interpreter, since the call to Tcl_CreateInterp will, among other
161  *	things, call TclInitializeSubsystems() and this one will, in turn,
162  *	call the TclInitNotifier() for the thread. For threads created without
163  *	the Tcl interpreter, though, nobody is explicitly nor implicitly
164  *	calling the TclInitNotifier hence, TclFinalizeNotifier should not be
165  *	performed at all.
166  *
167  *----------------------------------------------------------------------
168  */
169 
170 void
TclFinalizeNotifier(void)171 TclFinalizeNotifier(void)
172 {
173     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
174     ThreadSpecificData **prevPtrPtr;
175     Tcl_Event *evPtr, *hold;
176 
177     if (!tsdPtr->initialized) {
178 	return;			/* Notifier not initialized for the current
179 				 * thread. */
180     }
181 
182     Tcl_MutexLock(&(tsdPtr->queueMutex));
183     for (evPtr = tsdPtr->firstEventPtr; evPtr != NULL; ) {
184 	hold = evPtr;
185 	evPtr = evPtr->nextPtr;
186 	ckfree(hold);
187     }
188     tsdPtr->firstEventPtr = NULL;
189     tsdPtr->lastEventPtr = NULL;
190     Tcl_MutexUnlock(&(tsdPtr->queueMutex));
191 
192     Tcl_MutexLock(&listLock);
193 
194     Tcl_FinalizeNotifier(tsdPtr->clientData);
195     Tcl_MutexFinalize(&(tsdPtr->queueMutex));
196     for (prevPtrPtr = &firstNotifierPtr; *prevPtrPtr != NULL;
197 	    prevPtrPtr = &((*prevPtrPtr)->nextPtr)) {
198 	if (*prevPtrPtr == tsdPtr) {
199 	    *prevPtrPtr = tsdPtr->nextPtr;
200 	    break;
201 	}
202     }
203     tsdPtr->initialized = 0;
204 
205     Tcl_MutexUnlock(&listLock);
206 }
207 
208 /*
209  *----------------------------------------------------------------------
210  *
211  * Tcl_SetNotifier --
212  *
213  *	Install a set of alternate functions for use with the notifier. In
214  *	particular, this can be used to install the Xt-based notifier for use
215  *	with the Browser plugin.
216  *
217  * Results:
218  *	None.
219  *
220  * Side effects:
221  *	Set the tclNotifierHooks global, which is checked in the default
222  *	notifier functions.
223  *
224  *----------------------------------------------------------------------
225  */
226 
227 void
Tcl_SetNotifier(Tcl_NotifierProcs * notifierProcPtr)228 Tcl_SetNotifier(
229     Tcl_NotifierProcs *notifierProcPtr)
230 {
231     tclNotifierHooks = *notifierProcPtr;
232 
233     /*
234      * Don't allow hooks to refer to the hook point functions; avoids infinite
235      * loop.
236      */
237 
238     if (tclNotifierHooks.setTimerProc == Tcl_SetTimer) {
239 	tclNotifierHooks.setTimerProc = NULL;
240     }
241     if (tclNotifierHooks.waitForEventProc == Tcl_WaitForEvent) {
242 	tclNotifierHooks.waitForEventProc = NULL;
243     }
244     if (tclNotifierHooks.initNotifierProc == Tcl_InitNotifier) {
245 	tclNotifierHooks.initNotifierProc = NULL;
246     }
247     if (tclNotifierHooks.finalizeNotifierProc == Tcl_FinalizeNotifier) {
248 	tclNotifierHooks.finalizeNotifierProc = NULL;
249     }
250     if (tclNotifierHooks.alertNotifierProc == Tcl_AlertNotifier) {
251 	tclNotifierHooks.alertNotifierProc = NULL;
252     }
253     if (tclNotifierHooks.serviceModeHookProc == Tcl_ServiceModeHook) {
254 	tclNotifierHooks.serviceModeHookProc = NULL;
255     }
256 #ifndef _WIN32
257     if (tclNotifierHooks.createFileHandlerProc == Tcl_CreateFileHandler) {
258 	tclNotifierHooks.createFileHandlerProc = NULL;
259     }
260     if (tclNotifierHooks.deleteFileHandlerProc == Tcl_DeleteFileHandler) {
261 	tclNotifierHooks.deleteFileHandlerProc = NULL;
262     }
263 #endif /* !_WIN32 */
264 }
265 
266 /*
267  *----------------------------------------------------------------------
268  *
269  * Tcl_CreateEventSource --
270  *
271  *	This function is invoked to create a new source of events. The source
272  *	is identified by a function that gets invoked during Tcl_DoOneEvent to
273  *	check for events on that source and queue them.
274  *
275  *
276  * Results:
277  *	None.
278  *
279  * Side effects:
280  *	SetupProc and checkProc will be invoked each time that Tcl_DoOneEvent
281  *	runs out of things to do. SetupProc will be invoked before
282  *	Tcl_DoOneEvent calls select or whatever else it uses to wait for
283  *	events. SetupProc typically calls functions like Tcl_SetMaxBlockTime
284  *	to indicate what to wait for.
285  *
286  *	CheckProc is called after select or whatever operation was actually
287  *	used to wait. It figures out whether anything interesting actually
288  *	happened (e.g. by calling Tcl_AsyncReady), and then calls
289  *	Tcl_QueueEvent to queue any events that are ready.
290  *
291  *	Each of these functions is passed two arguments, e.g.
292  *		(*checkProc)(ClientData clientData, int flags));
293  *	ClientData is the same as the clientData argument here, and flags is a
294  *	combination of things like TCL_FILE_EVENTS that indicates what events
295  *	are of interest: setupProc and checkProc use flags to figure out
296  *	whether their events are relevant or not.
297  *
298  *----------------------------------------------------------------------
299  */
300 
301 void
Tcl_CreateEventSource(Tcl_EventSetupProc * setupProc,Tcl_EventCheckProc * checkProc,ClientData clientData)302 Tcl_CreateEventSource(
303     Tcl_EventSetupProc *setupProc,
304 				/* Function to invoke to figure out what to
305 				 * wait for. */
306     Tcl_EventCheckProc *checkProc,
307 				/* Function to call after waiting to see what
308 				 * happened. */
309     ClientData clientData)	/* One-word argument to pass to setupProc and
310 				 * checkProc. */
311 {
312     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
313     EventSource *sourcePtr = (EventSource *) ckalloc(sizeof(EventSource));
314 
315     sourcePtr->setupProc = setupProc;
316     sourcePtr->checkProc = checkProc;
317     sourcePtr->clientData = clientData;
318     sourcePtr->nextPtr = tsdPtr->firstEventSourcePtr;
319     tsdPtr->firstEventSourcePtr = sourcePtr;
320 }
321 
322 /*
323  *----------------------------------------------------------------------
324  *
325  * Tcl_DeleteEventSource --
326  *
327  *	This function is invoked to delete the source of events given by proc
328  *	and clientData.
329  *
330  * Results:
331  *	None.
332  *
333  * Side effects:
334  *	The given event source is canceled, so its function will never again
335  *	be called. If no such source exists, nothing happens.
336  *
337  *----------------------------------------------------------------------
338  */
339 
340 void
Tcl_DeleteEventSource(Tcl_EventSetupProc * setupProc,Tcl_EventCheckProc * checkProc,ClientData clientData)341 Tcl_DeleteEventSource(
342     Tcl_EventSetupProc *setupProc,
343 				/* Function to invoke to figure out what to
344 				 * wait for. */
345     Tcl_EventCheckProc *checkProc,
346 				/* Function to call after waiting to see what
347 				 * happened. */
348     ClientData clientData)	/* One-word argument to pass to setupProc and
349 				 * checkProc. */
350 {
351     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
352     EventSource *sourcePtr, *prevPtr;
353 
354     for (sourcePtr = tsdPtr->firstEventSourcePtr, prevPtr = NULL;
355 	    sourcePtr != NULL;
356 	    prevPtr = sourcePtr, sourcePtr = sourcePtr->nextPtr) {
357 	if ((sourcePtr->setupProc != setupProc)
358 		|| (sourcePtr->checkProc != checkProc)
359 		|| (sourcePtr->clientData != clientData)) {
360 	    continue;
361 	}
362 	if (prevPtr == NULL) {
363 	    tsdPtr->firstEventSourcePtr = sourcePtr->nextPtr;
364 	} else {
365 	    prevPtr->nextPtr = sourcePtr->nextPtr;
366 	}
367 	ckfree(sourcePtr);
368 	return;
369     }
370 }
371 
372 /*
373  *----------------------------------------------------------------------
374  *
375  * Tcl_QueueEvent --
376  *
377  *	Queue an event on the event queue associated with the current thread.
378  *
379  * Results:
380  *	None.
381  *
382  * Side effects:
383  *	None.
384  *
385  *----------------------------------------------------------------------
386  */
387 
388 void
Tcl_QueueEvent(Tcl_Event * evPtr,Tcl_QueuePosition position)389 Tcl_QueueEvent(
390     Tcl_Event *evPtr,		/* Event to add to queue. The storage space
391 				 * must have been allocated the caller with
392 				 * malloc (ckalloc), and it becomes the
393 				 * property of the event queue. It will be
394 				 * freed after the event has been handled. */
395     Tcl_QueuePosition position)	/* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD,
396 				 * TCL_QUEUE_MARK. */
397 {
398     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
399 
400     QueueEvent(tsdPtr, evPtr, position);
401 }
402 
403 /*
404  *----------------------------------------------------------------------
405  *
406  * Tcl_ThreadQueueEvent --
407  *
408  *	Queue an event on the specified thread's event queue.
409  *
410  * Results:
411  *	None.
412  *
413  * Side effects:
414  *	None.
415  *
416  *----------------------------------------------------------------------
417  */
418 
419 void
Tcl_ThreadQueueEvent(Tcl_ThreadId threadId,Tcl_Event * evPtr,Tcl_QueuePosition position)420 Tcl_ThreadQueueEvent(
421     Tcl_ThreadId threadId,	/* Identifier for thread to use. */
422     Tcl_Event *evPtr,		/* Event to add to queue. The storage space
423 				 * must have been allocated the caller with
424 				 * malloc (ckalloc), and it becomes the
425 				 * property of the event queue. It will be
426 				 * freed after the event has been handled. */
427     Tcl_QueuePosition position)	/* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD,
428 				 * TCL_QUEUE_MARK. */
429 {
430     ThreadSpecificData *tsdPtr;
431 
432     /*
433      * Find the notifier associated with the specified thread.
434      */
435 
436     Tcl_MutexLock(&listLock);
437     for (tsdPtr = firstNotifierPtr; tsdPtr && tsdPtr->threadId != threadId;
438 	    tsdPtr = tsdPtr->nextPtr) {
439 	/* Empty loop body. */
440     }
441 
442     /*
443      * Queue the event if there was a notifier associated with the thread.
444      */
445 
446     if (tsdPtr) {
447 	QueueEvent(tsdPtr, evPtr, position);
448     } else {
449 	ckfree(evPtr);
450     }
451     Tcl_MutexUnlock(&listLock);
452 }
453 
454 /*
455  *----------------------------------------------------------------------
456  *
457  * QueueEvent --
458  *
459  *	Insert an event into the specified thread's event queue at one of
460  *	three positions: the head, the tail, or before a floating marker.
461  *	Events inserted before the marker will be processed in first-in-
462  *	first-out order, but before any events inserted at the tail of the
463  *	queue. Events inserted at the head of the queue will be processed in
464  *	last-in-first-out order.
465  *
466  * Results:
467  *	None.
468  *
469  * Side effects:
470  *	None.
471  *
472  *----------------------------------------------------------------------
473  */
474 
475 static void
QueueEvent(ThreadSpecificData * tsdPtr,Tcl_Event * evPtr,Tcl_QueuePosition position)476 QueueEvent(
477     ThreadSpecificData *tsdPtr,	/* Handle to thread local data that indicates
478 				 * which event queue to use. */
479     Tcl_Event *evPtr,		/* Event to add to queue.  The storage space
480 				 * must have been allocated the caller with
481 				 * malloc (ckalloc), and it becomes the
482 				 * property of the event queue. It will be
483 				 * freed after the event has been handled. */
484     Tcl_QueuePosition position)	/* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD,
485 				 * TCL_QUEUE_MARK. */
486 {
487     Tcl_MutexLock(&(tsdPtr->queueMutex));
488     if (position == TCL_QUEUE_TAIL) {
489 	/*
490 	 * Append the event on the end of the queue.
491 	 */
492 
493 	evPtr->nextPtr = NULL;
494 	if (tsdPtr->firstEventPtr == NULL) {
495 	    tsdPtr->firstEventPtr = evPtr;
496 	} else {
497 	    tsdPtr->lastEventPtr->nextPtr = evPtr;
498 	}
499 	tsdPtr->lastEventPtr = evPtr;
500     } else if (position == TCL_QUEUE_HEAD) {
501 	/*
502 	 * Push the event on the head of the queue.
503 	 */
504 
505 	evPtr->nextPtr = tsdPtr->firstEventPtr;
506 	if (tsdPtr->firstEventPtr == NULL) {
507 	    tsdPtr->lastEventPtr = evPtr;
508 	}
509 	tsdPtr->firstEventPtr = evPtr;
510     } else if (position == TCL_QUEUE_MARK) {
511 	/*
512 	 * Insert the event after the current marker event and advance the
513 	 * marker to the new event.
514 	 */
515 
516 	if (tsdPtr->markerEventPtr == NULL) {
517 	    evPtr->nextPtr = tsdPtr->firstEventPtr;
518 	    tsdPtr->firstEventPtr = evPtr;
519 	} else {
520 	    evPtr->nextPtr = tsdPtr->markerEventPtr->nextPtr;
521 	    tsdPtr->markerEventPtr->nextPtr = evPtr;
522 	}
523 	tsdPtr->markerEventPtr = evPtr;
524 	if (evPtr->nextPtr == NULL) {
525 	    tsdPtr->lastEventPtr = evPtr;
526 	}
527     }
528     Tcl_MutexUnlock(&(tsdPtr->queueMutex));
529 }
530 
531 /*
532  *----------------------------------------------------------------------
533  *
534  * Tcl_DeleteEvents --
535  *
536  *	Calls a function for each event in the queue and deletes those for
537  *	which the function returns 1. Events for which the function returns 0
538  *	are left in the queue. Operates on the queue associated with the
539  *	current thread.
540  *
541  * Results:
542  *	None.
543  *
544  * Side effects:
545  *	Potentially removes one or more events from the event queue.
546  *
547  *----------------------------------------------------------------------
548  */
549 
550 void
Tcl_DeleteEvents(Tcl_EventDeleteProc * proc,ClientData clientData)551 Tcl_DeleteEvents(
552     Tcl_EventDeleteProc *proc,	/* The function to call. */
553     ClientData clientData)	/* The type-specific data. */
554 {
555     Tcl_Event *evPtr;		/* Pointer to the event being examined */
556     Tcl_Event *prevPtr;		/* Pointer to evPtr's predecessor, or NULL if
557 				 * evPtr designates the first event in the
558 				 * queue for the thread. */
559     Tcl_Event *hold;
560     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
561 
562     Tcl_MutexLock(&(tsdPtr->queueMutex));
563 
564     /*
565      * Walk the queue of events for the thread, applying 'proc' to each to
566      * decide whether to eliminate the event.
567      */
568 
569     prevPtr = NULL;
570     evPtr = tsdPtr->firstEventPtr;
571     while (evPtr != NULL) {
572 	if (proc(evPtr, clientData) == 1) {
573 	    /*
574 	     * This event should be deleted. Unlink it.
575 	     */
576 
577 	    if (prevPtr == NULL) {
578 		tsdPtr->firstEventPtr = evPtr->nextPtr;
579 	    } else {
580 		prevPtr->nextPtr = evPtr->nextPtr;
581 	    }
582 
583 	    /*
584 	     * Update 'last' and 'marker' events if either has been deleted.
585 	     */
586 
587 	    if (evPtr->nextPtr == NULL) {
588 		tsdPtr->lastEventPtr = prevPtr;
589 	    }
590 	    if (tsdPtr->markerEventPtr == evPtr) {
591 		tsdPtr->markerEventPtr = prevPtr;
592 	    }
593 
594 	    /*
595 	     * Delete the event data structure.
596 	     */
597 
598 	    hold = evPtr;
599 	    evPtr = evPtr->nextPtr;
600 	    ckfree(hold);
601 	} else {
602 	    /*
603 	     * Event is to be retained.
604 	     */
605 
606 	    prevPtr = evPtr;
607 	    evPtr = evPtr->nextPtr;
608 	}
609     }
610     Tcl_MutexUnlock(&(tsdPtr->queueMutex));
611 }
612 
613 /*
614  *----------------------------------------------------------------------
615  *
616  * Tcl_ServiceEvent --
617  *
618  *	Process one event from the event queue, or invoke an asynchronous
619  *	event handler. Operates on event queue for current thread.
620  *
621  * Results:
622  *	The return value is 1 if the function actually found an event to
623  *	process. If no processing occurred, then 0 is returned.
624  *
625  * Side effects:
626  *	Invokes all of the event handlers for the highest priority event in
627  *	the event queue. May collapse some events into a single event or
628  *	discard stale events.
629  *
630  *----------------------------------------------------------------------
631  */
632 
633 int
Tcl_ServiceEvent(int flags)634 Tcl_ServiceEvent(
635     int flags)			/* Indicates what events should be processed.
636 				 * May be any combination of TCL_WINDOW_EVENTS
637 				 * TCL_FILE_EVENTS, TCL_TIMER_EVENTS, or other
638 				 * flags defined elsewhere. Events not
639 				 * matching this will be skipped for
640 				 * processing later. */
641 {
642     Tcl_Event *evPtr, *prevPtr;
643     Tcl_EventProc *proc;
644     int result;
645     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
646 
647     /*
648      * Asynchronous event handlers are considered to be the highest priority
649      * events, and so must be invoked before we process events on the event
650      * queue.
651      */
652 
653     if (Tcl_AsyncReady()) {
654 	(void) Tcl_AsyncInvoke(NULL, 0);
655 	return 1;
656     }
657 
658     /*
659      * No event flags is equivalent to TCL_ALL_EVENTS.
660      */
661 
662     if ((flags & TCL_ALL_EVENTS) == 0) {
663 	flags |= TCL_ALL_EVENTS;
664     }
665 
666     /*
667      * Loop through all the events in the queue until we find one that can
668      * actually be handled.
669      */
670 
671     Tcl_MutexLock(&(tsdPtr->queueMutex));
672     for (evPtr = tsdPtr->firstEventPtr; evPtr != NULL;
673 	    evPtr = evPtr->nextPtr) {
674 	/*
675 	 * Call the handler for the event. If it actually handles the event
676 	 * then free the storage for the event. There are two tricky things
677 	 * here, both stemming from the fact that the event code may be
678 	 * re-entered while servicing the event:
679 	 *
680 	 * 1. Set the "proc" field to NULL.  This is a signal to ourselves
681 	 *    that we shouldn't reexecute the handler if the event loop is
682 	 *    re-entered.
683 	 * 2. When freeing the event, must search the queue again from the
684 	 *    front to find it. This is because the event queue could change
685 	 *    almost arbitrarily while handling the event, so we can't depend
686 	 *    on pointers found now still being valid when the handler
687 	 *    returns.
688 	 */
689 
690 	proc = evPtr->proc;
691 	if (proc == NULL) {
692 	    continue;
693 	}
694 	evPtr->proc = NULL;
695 
696 	/*
697 	 * Release the lock before calling the event function. This allows
698 	 * other threads to post events if we enter a recursive event loop in
699 	 * this thread. Note that we are making the assumption that if the
700 	 * proc returns 0, the event is still in the list.
701 	 */
702 
703 	Tcl_MutexUnlock(&(tsdPtr->queueMutex));
704 	result = proc(evPtr, flags);
705 	Tcl_MutexLock(&(tsdPtr->queueMutex));
706 
707 	if (result) {
708 	    /*
709 	     * The event was processed, so remove it from the queue.
710 	     */
711 
712 	    if (tsdPtr->firstEventPtr == evPtr) {
713 		tsdPtr->firstEventPtr = evPtr->nextPtr;
714 		if (evPtr->nextPtr == NULL) {
715 		    tsdPtr->lastEventPtr = NULL;
716 		}
717 		if (tsdPtr->markerEventPtr == evPtr) {
718 		    tsdPtr->markerEventPtr = NULL;
719 		}
720 	    } else {
721 		for (prevPtr = tsdPtr->firstEventPtr;
722 			prevPtr && prevPtr->nextPtr != evPtr;
723 			prevPtr = prevPtr->nextPtr) {
724 		    /* Empty loop body. */
725 		}
726 		if (prevPtr) {
727 		    prevPtr->nextPtr = evPtr->nextPtr;
728 		    if (evPtr->nextPtr == NULL) {
729 			tsdPtr->lastEventPtr = prevPtr;
730 		    }
731 		    if (tsdPtr->markerEventPtr == evPtr) {
732 			tsdPtr->markerEventPtr = prevPtr;
733 		    }
734 		} else {
735 		    evPtr = NULL;
736 		}
737 	    }
738 	    if (evPtr) {
739 		ckfree(evPtr);
740 	    }
741 	    Tcl_MutexUnlock(&(tsdPtr->queueMutex));
742 	    return 1;
743 	} else {
744 	    /*
745 	     * The event wasn't actually handled, so we have to restore the
746 	     * proc field to allow the event to be attempted again.
747 	     */
748 
749 	    evPtr->proc = proc;
750 	}
751     }
752     Tcl_MutexUnlock(&(tsdPtr->queueMutex));
753     return 0;
754 }
755 
756 /*
757  *----------------------------------------------------------------------
758  *
759  * Tcl_GetServiceMode --
760  *
761  *	This routine returns the current service mode of the notifier.
762  *
763  * Results:
764  *	Returns either TCL_SERVICE_ALL or TCL_SERVICE_NONE.
765  *
766  * Side effects:
767  *	None.
768  *
769  *----------------------------------------------------------------------
770  */
771 
772 int
Tcl_GetServiceMode(void)773 Tcl_GetServiceMode(void)
774 {
775     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
776 
777     return tsdPtr->serviceMode;
778 }
779 
780 /*
781  *----------------------------------------------------------------------
782  *
783  * Tcl_SetServiceMode --
784  *
785  *	This routine sets the current service mode of the tsdPtr->
786  *
787  * Results:
788  *	Returns the previous service mode.
789  *
790  * Side effects:
791  *	Invokes the notifier service mode hook function.
792  *
793  *----------------------------------------------------------------------
794  */
795 
796 int
Tcl_SetServiceMode(int mode)797 Tcl_SetServiceMode(
798     int mode)			/* New service mode: TCL_SERVICE_ALL or
799 				 * TCL_SERVICE_NONE */
800 {
801     int oldMode;
802     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
803 
804     oldMode = tsdPtr->serviceMode;
805     tsdPtr->serviceMode = mode;
806     Tcl_ServiceModeHook(mode);
807     return oldMode;
808 }
809 
810 /*
811  *----------------------------------------------------------------------
812  *
813  * Tcl_SetMaxBlockTime --
814  *
815  *	This function is invoked by event sources to tell the notifier how
816  *	long it may block the next time it blocks. The timePtr argument gives
817  *	a maximum time; the actual time may be less if some other event source
818  *	requested a smaller time.
819  *
820  * Results:
821  *	None.
822  *
823  * Side effects:
824  *	May reduce the length of the next sleep in the tsdPtr->
825  *
826  *----------------------------------------------------------------------
827  */
828 
829 void
Tcl_SetMaxBlockTime(const Tcl_Time * timePtr)830 Tcl_SetMaxBlockTime(
831     const Tcl_Time *timePtr)	/* Specifies a maximum elapsed time for the
832 				 * next blocking operation in the event
833 				 * tsdPtr-> */
834 {
835     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
836 
837     if (!tsdPtr->blockTimeSet || (timePtr->sec < tsdPtr->blockTime.sec)
838 	    || ((timePtr->sec == tsdPtr->blockTime.sec)
839 	    && (timePtr->usec < tsdPtr->blockTime.usec))) {
840 	tsdPtr->blockTime = *timePtr;
841 	tsdPtr->blockTimeSet = 1;
842     }
843 
844     /*
845      * If we are called outside an event source traversal, set the timeout
846      * immediately.
847      */
848 
849     if (!tsdPtr->inTraversal) {
850 	Tcl_SetTimer(&tsdPtr->blockTime);
851     }
852 }
853 
854 /*
855  *----------------------------------------------------------------------
856  *
857  * Tcl_DoOneEvent --
858  *
859  *	Process a single event of some sort. If there's no work to do, wait
860  *	for an event to occur, then process it.
861  *
862  * Results:
863  *	The return value is 1 if the function actually found an event to
864  *	process. If no processing occurred, then 0 is returned (this can
865  *	happen if the TCL_DONT_WAIT flag is set or if there are no event
866  *	handlers to wait for in the set specified by flags).
867  *
868  * Side effects:
869  *	May delay execution of process while waiting for an event, unless
870  *	TCL_DONT_WAIT is set in the flags argument. Event sources are invoked
871  *	to check for and queue events. Event handlers may produce arbitrary
872  *	side effects.
873  *
874  *----------------------------------------------------------------------
875  */
876 
877 int
Tcl_DoOneEvent(int flags)878 Tcl_DoOneEvent(
879     int flags)			/* Miscellaneous flag values: may be any
880 				 * combination of TCL_DONT_WAIT,
881 				 * TCL_WINDOW_EVENTS, TCL_FILE_EVENTS,
882 				 * TCL_TIMER_EVENTS, TCL_IDLE_EVENTS, or
883 				 * others defined by event sources. */
884 {
885     int result = 0, oldMode;
886     EventSource *sourcePtr;
887     Tcl_Time *timePtr;
888     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
889 
890     /*
891      * The first thing we do is to service any asynchronous event handlers.
892      */
893 
894     if (Tcl_AsyncReady()) {
895 	(void) Tcl_AsyncInvoke(NULL, 0);
896 	return 1;
897     }
898 
899     /*
900      * No event flags is equivalent to TCL_ALL_EVENTS.
901      */
902 
903     if ((flags & TCL_ALL_EVENTS) == 0) {
904 	flags |= TCL_ALL_EVENTS;
905     }
906 
907     /*
908      * Set the service mode to none so notifier event routines won't try to
909      * service events recursively.
910      */
911 
912     oldMode = tsdPtr->serviceMode;
913     tsdPtr->serviceMode = TCL_SERVICE_NONE;
914 
915     /*
916      * The core of this function is an infinite loop, even though we only
917      * service one event. The reason for this is that we may be processing
918      * events that don't do anything inside of Tcl.
919      */
920 
921     while (1) {
922 	/*
923 	 * If idle events are the only things to service, skip the main part
924 	 * of the loop and go directly to handle idle events (i.e. don't wait
925 	 * even if TCL_DONT_WAIT isn't set).
926 	 */
927 
928 	if ((flags & TCL_ALL_EVENTS) == TCL_IDLE_EVENTS) {
929 	    flags = TCL_IDLE_EVENTS | TCL_DONT_WAIT;
930 	    goto idleEvents;
931 	}
932 
933 	/*
934 	 * Ask Tcl to service a queued event, if there are any.
935 	 */
936 
937 	if (Tcl_ServiceEvent(flags)) {
938 	    result = 1;
939 	    break;
940 	}
941 
942 	/*
943 	 * If TCL_DONT_WAIT is set, be sure to poll rather than blocking,
944 	 * otherwise reset the block time to infinity.
945 	 */
946 
947 	if (flags & TCL_DONT_WAIT) {
948 	    tsdPtr->blockTime.sec = 0;
949 	    tsdPtr->blockTime.usec = 0;
950 	    tsdPtr->blockTimeSet = 1;
951 	} else {
952 	    tsdPtr->blockTimeSet = 0;
953 	}
954 
955 	/*
956 	 * Set up all the event sources for new events. This will cause the
957 	 * block time to be updated if necessary.
958 	 */
959 
960 	tsdPtr->inTraversal = 1;
961 	for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL;
962 		sourcePtr = sourcePtr->nextPtr) {
963 	    if (sourcePtr->setupProc) {
964 		sourcePtr->setupProc(sourcePtr->clientData, flags);
965 	    }
966 	}
967 	tsdPtr->inTraversal = 0;
968 
969 	if ((flags & TCL_DONT_WAIT) || tsdPtr->blockTimeSet) {
970 	    timePtr = &tsdPtr->blockTime;
971 	} else {
972 	    timePtr = NULL;
973 	}
974 
975 	/*
976 	 * Wait for a new event or a timeout. If Tcl_WaitForEvent returns -1,
977 	 * we should abort Tcl_DoOneEvent.
978 	 */
979 
980 	result = Tcl_WaitForEvent(timePtr);
981 	if (result < 0) {
982 	    result = 0;
983 	    break;
984 	}
985 
986 	/*
987 	 * Check all the event sources for new events.
988 	 */
989 
990 	for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL;
991 		sourcePtr = sourcePtr->nextPtr) {
992 	    if (sourcePtr->checkProc) {
993 		sourcePtr->checkProc(sourcePtr->clientData, flags);
994 	    }
995 	}
996 
997 	/*
998 	 * Check for events queued by the notifier or event sources.
999 	 */
1000 
1001 	if (Tcl_ServiceEvent(flags)) {
1002 	    result = 1;
1003 	    break;
1004 	}
1005 
1006 	/*
1007 	 * We've tried everything at this point, but nobody we know about had
1008 	 * anything to do. Check for idle events. If none, either quit or go
1009 	 * back to the top and try again.
1010 	 */
1011 
1012     idleEvents:
1013 	if (flags & TCL_IDLE_EVENTS) {
1014 	    if (TclServiceIdle()) {
1015 		result = 1;
1016 		break;
1017 	    }
1018 	}
1019 	if (flags & TCL_DONT_WAIT) {
1020 	    break;
1021 	}
1022 
1023 	/*
1024 	 * If Tcl_WaitForEvent has returned 1, indicating that one system
1025 	 * event has been dispatched (and thus that some Tcl code might have
1026 	 * been indirectly executed), we break out of the loop. We do this to
1027 	 * give VwaitCmd for instance a chance to check if that system event
1028 	 * had the side effect of changing the variable (so the vwait can
1029 	 * return and unwind properly).
1030 	 *
1031 	 * NB: We will process idle events if any first, because otherwise we
1032 	 *     might never do the idle events if the notifier always gets
1033 	 *     system events.
1034 	 */
1035 
1036 	if (result) {
1037 	    break;
1038 	}
1039     }
1040 
1041     tsdPtr->serviceMode = oldMode;
1042     return result;
1043 }
1044 
1045 /*
1046  *----------------------------------------------------------------------
1047  *
1048  * Tcl_ServiceAll --
1049  *
1050  *	This routine checks all of the event sources, processes events that
1051  *	are on the Tcl event queue, and then calls the any idle handlers.
1052  *	Platform specific notifier callbacks that generate events should call
1053  *	this routine before returning to the system in order to ensure that
1054  *	Tcl gets a chance to process the new events.
1055  *
1056  * Results:
1057  *	Returns 1 if an event or idle handler was invoked, else 0.
1058  *
1059  * Side effects:
1060  *	Anything that an event or idle handler may do.
1061  *
1062  *----------------------------------------------------------------------
1063  */
1064 
1065 int
Tcl_ServiceAll(void)1066 Tcl_ServiceAll(void)
1067 {
1068     int result = 0;
1069     EventSource *sourcePtr;
1070     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
1071 
1072     if (tsdPtr->serviceMode == TCL_SERVICE_NONE) {
1073 	return result;
1074     }
1075 
1076     /*
1077      * We need to turn off event servicing like we to in Tcl_DoOneEvent, to
1078      * avoid recursive calls.
1079      */
1080 
1081     tsdPtr->serviceMode = TCL_SERVICE_NONE;
1082 
1083     /*
1084      * Check async handlers first.
1085      */
1086 
1087     if (Tcl_AsyncReady()) {
1088 	(void) Tcl_AsyncInvoke(NULL, 0);
1089     }
1090 
1091     /*
1092      * Make a single pass through all event sources, queued events, and idle
1093      * handlers. Note that we wait to update the notifier timer until the end
1094      * so we can avoid multiple changes.
1095      */
1096 
1097     tsdPtr->inTraversal = 1;
1098     tsdPtr->blockTimeSet = 0;
1099 
1100     for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL;
1101 	    sourcePtr = sourcePtr->nextPtr) {
1102 	if (sourcePtr->setupProc) {
1103 	    sourcePtr->setupProc(sourcePtr->clientData, TCL_ALL_EVENTS);
1104 	}
1105     }
1106     for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL;
1107 	    sourcePtr = sourcePtr->nextPtr) {
1108 	if (sourcePtr->checkProc) {
1109 	    sourcePtr->checkProc(sourcePtr->clientData, TCL_ALL_EVENTS);
1110 	}
1111     }
1112 
1113     while (Tcl_ServiceEvent(0)) {
1114 	result = 1;
1115     }
1116     if (TclServiceIdle()) {
1117 	result = 1;
1118     }
1119 
1120     if (!tsdPtr->blockTimeSet) {
1121 	Tcl_SetTimer(NULL);
1122     } else {
1123 	Tcl_SetTimer(&tsdPtr->blockTime);
1124     }
1125     tsdPtr->inTraversal = 0;
1126     tsdPtr->serviceMode = TCL_SERVICE_ALL;
1127     return result;
1128 }
1129 
1130 /*
1131  *----------------------------------------------------------------------
1132  *
1133  * Tcl_ThreadAlert --
1134  *
1135  *	This function wakes up the notifier associated with the specified
1136  *	thread (if there is one).
1137  *
1138  * Results:
1139  *	None.
1140  *
1141  * Side effects:
1142  *	None.
1143  *
1144  *----------------------------------------------------------------------
1145  */
1146 
1147 void
Tcl_ThreadAlert(Tcl_ThreadId threadId)1148 Tcl_ThreadAlert(
1149     Tcl_ThreadId threadId)	/* Identifier for thread to use. */
1150 {
1151     ThreadSpecificData *tsdPtr;
1152 
1153     /*
1154      * Find the notifier associated with the specified thread. Note that we
1155      * need to hold the listLock while calling Tcl_AlertNotifier to avoid a
1156      * race condition where the specified thread might destroy its notifier.
1157      */
1158 
1159     Tcl_MutexLock(&listLock);
1160     for (tsdPtr = firstNotifierPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) {
1161 	if (tsdPtr->threadId == threadId) {
1162 	    Tcl_AlertNotifier(tsdPtr->clientData);
1163 	    break;
1164 	}
1165     }
1166     Tcl_MutexUnlock(&listLock);
1167 }
1168 
1169 /*
1170  *----------------------------------------------------------------------
1171  *
1172  * Tcl_InitNotifier --
1173  *
1174  *	Initializes the platform specific notifier state. Forwards to the
1175  *	platform implementation when the hook is not enabled.
1176  *
1177  * Results:
1178  *	Returns a handle to the notifier state for this thread..
1179  *
1180  * Side effects:
1181  *	None.
1182  *
1183  *----------------------------------------------------------------------
1184  */
1185 
1186 ClientData
Tcl_InitNotifier(void)1187 Tcl_InitNotifier(void)
1188 {
1189     if (tclNotifierHooks.initNotifierProc) {
1190 	return tclNotifierHooks.initNotifierProc();
1191     } else {
1192 	return TclpInitNotifier();
1193     }
1194 }
1195 
1196 /*
1197  *----------------------------------------------------------------------
1198  *
1199  * Tcl_FinalizeNotifier --
1200  *
1201  *	This function is called to cleanup the notifier state before a thread
1202  *	is terminated. Forwards to the platform implementation when the hook
1203  *	is not enabled.
1204  *
1205  * Results:
1206  *	None.
1207  *
1208  * Side effects:
1209  *	If no finalizeNotifierProc notifier hook exists, TclpFinalizeNotifier
1210  *	is called.
1211  *
1212  *----------------------------------------------------------------------
1213  */
1214 
1215 void
Tcl_FinalizeNotifier(ClientData clientData)1216 Tcl_FinalizeNotifier(
1217     ClientData clientData)
1218 {
1219     if (tclNotifierHooks.finalizeNotifierProc) {
1220 	tclNotifierHooks.finalizeNotifierProc(clientData);
1221     } else {
1222 	TclpFinalizeNotifier(clientData);
1223     }
1224 }
1225 
1226 /*
1227  *----------------------------------------------------------------------
1228  *
1229  * Tcl_AlertNotifier --
1230  *
1231  *	Wake up the specified notifier from any thread. This routine is called
1232  *	by the platform independent notifier code whenever the Tcl_ThreadAlert
1233  *	routine is called. This routine is guaranteed not to be called by Tcl
1234  *	on a given notifier after Tcl_FinalizeNotifier is called for that
1235  *	notifier.  This routine is typically called from a thread other than
1236  *	the notifier's thread.  Forwards to the platform implementation when
1237  *	the hook is not enabled.
1238  *
1239  * Results:
1240  *	None.
1241  *
1242  * Side effects:
1243  *	See the platform-specific implementations.
1244  *
1245  *----------------------------------------------------------------------
1246  */
1247 
1248 void
Tcl_AlertNotifier(ClientData clientData)1249 Tcl_AlertNotifier(
1250     ClientData clientData)	/* Pointer to thread data. */
1251 {
1252     if (tclNotifierHooks.alertNotifierProc) {
1253 	tclNotifierHooks.alertNotifierProc(clientData);
1254     } else {
1255 	TclpAlertNotifier(clientData);
1256     }
1257 }
1258 
1259 /*
1260  *----------------------------------------------------------------------
1261  *
1262  * Tcl_ServiceModeHook --
1263  *
1264  *	This function is invoked whenever the service mode changes.  Forwards
1265  *	to the platform implementation when the hook is not enabled.
1266  *
1267  * Results:
1268  *	None.
1269  *
1270  * Side effects:
1271  *	See the platform-specific implementations.
1272  *
1273  *----------------------------------------------------------------------
1274  */
1275 
1276 void
Tcl_ServiceModeHook(int mode)1277 Tcl_ServiceModeHook(
1278     int mode)			/* Either TCL_SERVICE_ALL, or
1279 				 * TCL_SERVICE_NONE. */
1280 {
1281     if (tclNotifierHooks.serviceModeHookProc) {
1282 	tclNotifierHooks.serviceModeHookProc(mode);
1283     } else {
1284 	TclpServiceModeHook(mode);
1285     }
1286 }
1287 
1288 /*
1289  *----------------------------------------------------------------------
1290  *
1291  * Tcl_SetTimer --
1292  *
1293  *	This function sets the current notifier timer value.  Forwards to the
1294  *	platform implementation when the hook is not enabled.
1295  *
1296  * Results:
1297  *	None.
1298  *
1299  * Side effects:
1300  *	See the platform-specific implementations.
1301  *
1302  *----------------------------------------------------------------------
1303  */
1304 
1305 void
Tcl_SetTimer(const Tcl_Time * timePtr)1306 Tcl_SetTimer(
1307     const Tcl_Time *timePtr)		/* Timeout value, may be NULL. */
1308 {
1309     if (tclNotifierHooks.setTimerProc) {
1310 	tclNotifierHooks.setTimerProc(timePtr);
1311     } else {
1312 	TclpSetTimer(timePtr);
1313     }
1314 }
1315 
1316 /*
1317  *----------------------------------------------------------------------
1318  *
1319  * Tcl_WaitForEvent --
1320  *
1321  *	This function is called by Tcl_DoOneEvent to wait for new events on
1322  *	the notifier's message queue.  If the block time is 0, then
1323  *	Tcl_WaitForEvent just polls without blocking.  Forwards to the
1324  *	platform implementation when the hook is not enabled.
1325  *
1326  * Results:
1327  *	Returns -1 if the wait would block forever, 1 if an out-of-loop source
1328  *	was processed (see platform-specific notes) and otherwise returns 0.
1329  *
1330  * Side effects:
1331  *	Queues file events that are detected by the notifier.
1332  *
1333  *----------------------------------------------------------------------
1334  */
1335 
1336 int
Tcl_WaitForEvent(const Tcl_Time * timePtr)1337 Tcl_WaitForEvent(
1338     const Tcl_Time *timePtr)		/* Maximum block time, or NULL. */
1339 {
1340     if (tclNotifierHooks.waitForEventProc) {
1341 	return tclNotifierHooks.waitForEventProc(timePtr);
1342     } else {
1343 	return TclpWaitForEvent(timePtr);
1344     }
1345 }
1346 
1347 /*
1348  *----------------------------------------------------------------------
1349  *
1350  * Tcl_CreateFileHandler --
1351  *
1352  *	This function registers a file descriptor handler with the notifier.
1353  *	Forwards to the platform implementation when the hook is not enabled.
1354  *
1355  *	This function is not defined on Windows. The OS API there is too
1356  *	different.
1357  *
1358  * Results:
1359  *	None.
1360  *
1361  * Side effects:
1362  *	Creates a new file handler structure.
1363  *
1364  *----------------------------------------------------------------------
1365  */
1366 
1367 #ifndef _WIN32
1368 void
Tcl_CreateFileHandler(int fd,int mask,Tcl_FileProc * proc,ClientData clientData)1369 Tcl_CreateFileHandler(
1370     int fd,			/* Handle of stream to watch. */
1371     int mask,			/* OR'ed combination of TCL_READABLE,
1372 				 * TCL_WRITABLE, and TCL_EXCEPTION: indicates
1373 				 * conditions under which proc should be
1374 				 * called. */
1375     Tcl_FileProc *proc,		/* Function to call for each selected
1376 				 * event. */
1377     ClientData clientData)	/* Arbitrary data to pass to proc. */
1378 {
1379     if (tclNotifierHooks.createFileHandlerProc) {
1380 	tclNotifierHooks.createFileHandlerProc(fd, mask, proc, clientData);
1381     } else {
1382 	TclpCreateFileHandler(fd, mask, proc, clientData);
1383     }
1384 }
1385 #endif /* !_WIN32 */
1386 
1387 /*
1388  *----------------------------------------------------------------------
1389  *
1390  * Tcl_DeleteFileHandler --
1391  *
1392  *	Cancel a previously-arranged callback arrangement for a file
1393  *	descriptor.  Forwards to the platform implementation when the hook is
1394  *	not enabled.
1395  *
1396  *	This function is not defined on Windows. The OS API there is too
1397  *	different.
1398  *
1399  * Results:
1400  *	None.
1401  *
1402  * Side effects:
1403  *	If a callback was previously registered on the file descriptor, remove
1404  *	it.
1405  *
1406  *----------------------------------------------------------------------
1407  */
1408 
1409 #ifndef _WIN32
1410 void
Tcl_DeleteFileHandler(int fd)1411 Tcl_DeleteFileHandler(
1412     int fd)			/* Stream id for which to remove callback
1413 				 * function. */
1414 {
1415     if (tclNotifierHooks.deleteFileHandlerProc) {
1416 	tclNotifierHooks.deleteFileHandlerProc(fd);
1417     } else {
1418 	TclpDeleteFileHandler(fd);
1419     }
1420 }
1421 #endif /* !_WIN32 */
1422 
1423 /*
1424  * Local Variables:
1425  * mode: c
1426  * c-basic-offset: 4
1427  * fill-column: 78
1428  * End:
1429  */
1430