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