blob: 429416e66ed40446bfe3f3167bd68450c454042c [file] [log] [blame]
Guido van Rossum54e20911997-09-28 05:52:41 +00001/*
2 * This is a modified version of tclNotify.c from Sun's Tcl 8.0
3 * distribution. The purpose of the modification is to provide an
4 * interface to the internals of the notifier that make it possible to
5 * write safe multi-threaded Python programs that use Tkinter.
6 *
7 * Original comments follow. The file license.terms from the Tcl 8.0
8 * distribution is contained in this directory, as required.
9 */
10
11/*
12 * tclNotify.c --
13 *
14 * This file implements the generic portion of the Tcl notifier.
15 * The notifier is lowest-level part of the event system. It
16 * manages an event queue that holds Tcl_Event structures. The
17 * platform specific portion of the notifier is defined in the
18 * tcl*Notify.c files in each platform directory.
19 *
20 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
21 *
22 * See the file "license.terms" for information on usage and redistribution
23 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
24 *
25 * SCCS: @(#) tclNotify.c 1.15 97/06/18 17:14:04
26 */
27
28#include "tclInt.h"
29#include "tclPort.h"
30
31/*
32 * The following static indicates whether this module has been initialized.
33 */
34
35static int initialized = 0;
36
37/*
38 * For each event source (created with Tcl_CreateEventSource) there
39 * is a structure of the following type:
40 */
41
42typedef struct EventSource {
43 Tcl_EventSetupProc *setupProc;
44 Tcl_EventCheckProc *checkProc;
45 ClientData clientData;
46 struct EventSource *nextPtr;
47} EventSource;
48
49/*
50 * The following structure keeps track of the state of the notifier.
51 * The first three elements keep track of the event queue. In addition to
52 * the first (next to be serviced) and last events in the queue, we keep
53 * track of a "marker" event. This provides a simple priority mechanism
54 * whereby events can be inserted at the front of the queue but behind all
55 * other high-priority events already in the queue (this is used for things
56 * like a sequence of Enter and Leave events generated during a grab in
57 * Tk).
58 */
59
60static struct {
61 Tcl_Event *firstEventPtr; /* First pending event, or NULL if none. */
62 Tcl_Event *lastEventPtr; /* Last pending event, or NULL if none. */
63 Tcl_Event *markerEventPtr; /* Last high-priority event in queue, or
64 * NULL if none. */
65 int serviceMode; /* One of TCL_SERVICE_NONE or
66 * TCL_SERVICE_ALL. */
67 int blockTimeSet; /* 0 means there is no maximum block
68 * time: block forever. */
69 Tcl_Time blockTime; /* If blockTimeSet is 1, gives the
70 * maximum elapsed time for the next block. */
71 int inTraversal; /* 1 if Tcl_SetMaxBlockTime is being
72 * called during an event source traversal. */
73 EventSource *firstEventSourcePtr;
74 /* Pointer to first event source in
75 * global list of event sources. */
76} notifier;
77
78/*
79 * Declarations for functions used in this file.
80 */
81
82static void InitNotifier _ANSI_ARGS_((void));
83static void NotifierExitHandler _ANSI_ARGS_((ClientData clientData));
84
85
86/*
87 *----------------------------------------------------------------------
88 *
89 * InitNotifier --
90 *
91 * This routine is called to initialize the notifier module.
92 *
93 * Results:
94 * None.
95 *
96 * Side effects:
97 * Creates an exit handler and initializes static data.
98 *
99 *----------------------------------------------------------------------
100 */
101
102static void
103InitNotifier()
104{
105 initialized = 1;
106 memset(&notifier, 0, sizeof(notifier));
107 notifier.serviceMode = TCL_SERVICE_NONE;
108 Tcl_CreateExitHandler(NotifierExitHandler, NULL);
109}
110
111/*
112 *----------------------------------------------------------------------
113 *
114 * NotifierExitHandler --
115 *
116 * This routine is called during Tcl finalization.
117 *
118 * Results:
119 * None.
120 *
121 * Side effects:
122 * Clears the notifier intialization flag.
123 *
124 *----------------------------------------------------------------------
125 */
126
127static void
128NotifierExitHandler(clientData)
129 ClientData clientData; /* Not used. */
130{
131 initialized = 0;
132}
133
134/*
135 *----------------------------------------------------------------------
136 *
137 * Tcl_CreateEventSource --
138 *
139 * This procedure is invoked to create a new source of events.
140 * The source is identified by a procedure that gets invoked
141 * during Tcl_DoOneEvent to check for events on that source
142 * and queue them.
143 *
144 *
145 * Results:
146 * None.
147 *
148 * Side effects:
149 * SetupProc and checkProc will be invoked each time that Tcl_DoOneEvent
150 * runs out of things to do. SetupProc will be invoked before
151 * Tcl_DoOneEvent calls select or whatever else it uses to wait
152 * for events. SetupProc typically calls functions like Tcl_WatchFile
153 * or Tcl_SetMaxBlockTime to indicate what to wait for.
154 *
155 * CheckProc is called after select or whatever operation was actually
156 * used to wait. It figures out whether anything interesting actually
157 * happened (e.g. by calling Tcl_FileReady), and then calls
158 * Tcl_QueueEvent to queue any events that are ready.
159 *
160 * Each of these procedures is passed two arguments, e.g.
161 * (*checkProc)(ClientData clientData, int flags));
162 * ClientData is the same as the clientData argument here, and flags
163 * is a combination of things like TCL_FILE_EVENTS that indicates
164 * what events are of interest: setupProc and checkProc use flags
165 * to figure out whether their events are relevant or not.
166 *
167 *----------------------------------------------------------------------
168 */
169
170void
171Tcl_CreateEventSource(setupProc, checkProc, clientData)
172 Tcl_EventSetupProc *setupProc; /* Procedure to invoke to figure out
173 * what to wait for. */
174 Tcl_EventCheckProc *checkProc; /* Procedure to call after waiting
175 * to see what happened. */
176 ClientData clientData; /* One-word argument to pass to
177 * setupProc and checkProc. */
178{
179 EventSource *sourcePtr;
180
181 if (!initialized) {
182 InitNotifier();
183 }
184
185 sourcePtr = (EventSource *) ckalloc(sizeof(EventSource));
186 sourcePtr->setupProc = setupProc;
187 sourcePtr->checkProc = checkProc;
188 sourcePtr->clientData = clientData;
189 sourcePtr->nextPtr = notifier.firstEventSourcePtr;
190 notifier.firstEventSourcePtr = sourcePtr;
191}
192
193/*
194 *----------------------------------------------------------------------
195 *
196 * Tcl_DeleteEventSource --
197 *
198 * This procedure is invoked to delete the source of events
199 * given by proc and clientData.
200 *
201 * Results:
202 * None.
203 *
204 * Side effects:
205 * The given event source is cancelled, so its procedure will
206 * never again be called. If no such source exists, nothing
207 * happens.
208 *
209 *----------------------------------------------------------------------
210 */
211
212void
213Tcl_DeleteEventSource(setupProc, checkProc, clientData)
214 Tcl_EventSetupProc *setupProc; /* Procedure to invoke to figure out
215 * what to wait for. */
216 Tcl_EventCheckProc *checkProc; /* Procedure to call after waiting
217 * to see what happened. */
218 ClientData clientData; /* One-word argument to pass to
219 * setupProc and checkProc. */
220{
221 EventSource *sourcePtr, *prevPtr;
222
223 for (sourcePtr = notifier.firstEventSourcePtr, prevPtr = NULL;
224 sourcePtr != NULL;
225 prevPtr = sourcePtr, sourcePtr = sourcePtr->nextPtr) {
226 if ((sourcePtr->setupProc != setupProc)
227 || (sourcePtr->checkProc != checkProc)
228 || (sourcePtr->clientData != clientData)) {
229 continue;
230 }
231 if (prevPtr == NULL) {
232 notifier.firstEventSourcePtr = sourcePtr->nextPtr;
233 } else {
234 prevPtr->nextPtr = sourcePtr->nextPtr;
235 }
236 ckfree((char *) sourcePtr);
237 return;
238 }
239}
240
241/*
242 *----------------------------------------------------------------------
243 *
244 * Tcl_QueueEvent --
245 *
246 * Insert an event into the Tk event queue at one of three
247 * positions: the head, the tail, or before a floating marker.
248 * Events inserted before the marker will be processed in
249 * first-in-first-out order, but before any events inserted at
250 * the tail of the queue. Events inserted at the head of the
251 * queue will be processed in last-in-first-out order.
252 *
253 * Results:
254 * None.
255 *
256 * Side effects:
257 * None.
258 *
259 *----------------------------------------------------------------------
260 */
261
262void
263Tcl_QueueEvent(evPtr, position)
264 Tcl_Event* evPtr; /* Event to add to queue. The storage
265 * space must have been allocated the caller
266 * with malloc (ckalloc), and it becomes
267 * the property of the event queue. It
268 * will be freed after the event has been
269 * handled. */
270 Tcl_QueuePosition position; /* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD,
271 * TCL_QUEUE_MARK. */
272{
273 if (!initialized) {
274 InitNotifier();
275 }
276
277 if (position == TCL_QUEUE_TAIL) {
278 /*
279 * Append the event on the end of the queue.
280 */
281
282 evPtr->nextPtr = NULL;
283 if (notifier.firstEventPtr == NULL) {
284 notifier.firstEventPtr = evPtr;
285 } else {
286 notifier.lastEventPtr->nextPtr = evPtr;
287 }
288 notifier.lastEventPtr = evPtr;
289 } else if (position == TCL_QUEUE_HEAD) {
290 /*
291 * Push the event on the head of the queue.
292 */
293
294 evPtr->nextPtr = notifier.firstEventPtr;
295 if (notifier.firstEventPtr == NULL) {
296 notifier.lastEventPtr = evPtr;
297 }
298 notifier.firstEventPtr = evPtr;
299 } else if (position == TCL_QUEUE_MARK) {
300 /*
301 * Insert the event after the current marker event and advance
302 * the marker to the new event.
303 */
304
305 if (notifier.markerEventPtr == NULL) {
306 evPtr->nextPtr = notifier.firstEventPtr;
307 notifier.firstEventPtr = evPtr;
308 } else {
309 evPtr->nextPtr = notifier.markerEventPtr->nextPtr;
310 notifier.markerEventPtr->nextPtr = evPtr;
311 }
312 notifier.markerEventPtr = evPtr;
313 if (evPtr->nextPtr == NULL) {
314 notifier.lastEventPtr = evPtr;
315 }
316 }
317}
318
319/*
320 *----------------------------------------------------------------------
321 *
322 * Tcl_DeleteEvents --
323 *
324 * Calls a procedure for each event in the queue and deletes those
325 * for which the procedure returns 1. Events for which the
326 * procedure returns 0 are left in the queue.
327 *
328 * Results:
329 * None.
330 *
331 * Side effects:
332 * Potentially removes one or more events from the event queue.
333 *
334 *----------------------------------------------------------------------
335 */
336
337void
338Tcl_DeleteEvents(proc, clientData)
339 Tcl_EventDeleteProc *proc; /* The procedure to call. */
340 ClientData clientData; /* type-specific data. */
341{
342 Tcl_Event *evPtr, *prevPtr, *hold;
343
344 if (!initialized) {
345 InitNotifier();
346 }
347
348 for (prevPtr = (Tcl_Event *) NULL, evPtr = notifier.firstEventPtr;
349 evPtr != (Tcl_Event *) NULL;
350 ) {
351 if ((*proc) (evPtr, clientData) == 1) {
352 if (notifier.firstEventPtr == evPtr) {
353 notifier.firstEventPtr = evPtr->nextPtr;
354 if (evPtr->nextPtr == (Tcl_Event *) NULL) {
355 notifier.lastEventPtr = (Tcl_Event *) NULL;
356 }
357 } else {
358 prevPtr->nextPtr = evPtr->nextPtr;
359 }
360 hold = evPtr;
361 evPtr = evPtr->nextPtr;
362 ckfree((char *) hold);
363 } else {
364 prevPtr = evPtr;
365 evPtr = evPtr->nextPtr;
366 }
367 }
368}
369
370/*
371 *----------------------------------------------------------------------
372 *
373 * Tcl_ServiceEvent --
374 *
375 * Process one event from the event queue, or invoke an
376 * asynchronous event handler.
377 *
378 * Results:
379 * The return value is 1 if the procedure actually found an event
380 * to process. If no processing occurred, then 0 is returned.
381 *
382 * Side effects:
383 * Invokes all of the event handlers for the highest priority
384 * event in the event queue. May collapse some events into a
385 * single event or discard stale events.
386 *
387 *----------------------------------------------------------------------
388 */
389
390int
391Tcl_ServiceEvent(flags)
392 int flags; /* Indicates what events should be processed.
393 * May be any combination of TCL_WINDOW_EVENTS
394 * TCL_FILE_EVENTS, TCL_TIMER_EVENTS, or other
395 * flags defined elsewhere. Events not
396 * matching this will be skipped for processing
397 * later. */
398{
399 Tcl_Event *evPtr, *prevPtr;
400 Tcl_EventProc *proc;
401
402 if (!initialized) {
403 InitNotifier();
404 }
405
406 /*
407 * Asynchronous event handlers are considered to be the highest
408 * priority events, and so must be invoked before we process events
409 * on the event queue.
410 */
411
412 if (Tcl_AsyncReady()) {
413 (void) Tcl_AsyncInvoke((Tcl_Interp *) NULL, 0);
414 return 1;
415 }
416
417 /*
418 * No event flags is equivalent to TCL_ALL_EVENTS.
419 */
420
421 if ((flags & TCL_ALL_EVENTS) == 0) {
422 flags |= TCL_ALL_EVENTS;
423 }
424
425 /*
426 * Loop through all the events in the queue until we find one
427 * that can actually be handled.
428 */
429
430 for (evPtr = notifier.firstEventPtr; evPtr != NULL;
431 evPtr = evPtr->nextPtr) {
432 /*
433 * Call the handler for the event. If it actually handles the
434 * event then free the storage for the event. There are two
435 * tricky things here, but stemming from the fact that the event
436 * code may be re-entered while servicing the event:
437 *
438 * 1. Set the "proc" field to NULL. This is a signal to ourselves
439 * that we shouldn't reexecute the handler if the event loop
440 * is re-entered.
441 * 2. When freeing the event, must search the queue again from the
442 * front to find it. This is because the event queue could
443 * change almost arbitrarily while handling the event, so we
444 * can't depend on pointers found now still being valid when
445 * the handler returns.
446 */
447
448 proc = evPtr->proc;
449 evPtr->proc = NULL;
450 if ((proc != NULL) && (*proc)(evPtr, flags)) {
451 if (notifier.firstEventPtr == evPtr) {
452 notifier.firstEventPtr = evPtr->nextPtr;
453 if (evPtr->nextPtr == NULL) {
454 notifier.lastEventPtr = NULL;
455 }
456 if (notifier.markerEventPtr == evPtr) {
457 notifier.markerEventPtr = NULL;
458 }
459 } else {
460 for (prevPtr = notifier.firstEventPtr;
461 prevPtr->nextPtr != evPtr; prevPtr = prevPtr->nextPtr) {
462 /* Empty loop body. */
463 }
464 prevPtr->nextPtr = evPtr->nextPtr;
465 if (evPtr->nextPtr == NULL) {
466 notifier.lastEventPtr = prevPtr;
467 }
468 if (notifier.markerEventPtr == evPtr) {
469 notifier.markerEventPtr = prevPtr;
470 }
471 }
472 ckfree((char *) evPtr);
473 return 1;
474 } else {
475 /*
476 * The event wasn't actually handled, so we have to restore
477 * the proc field to allow the event to be attempted again.
478 */
479
480 evPtr->proc = proc;
481 }
482
483 /*
484 * The handler for this event asked to defer it. Just go on to
485 * the next event.
486 */
487
488 continue;
489 }
490 return 0;
491}
492
493/*
494 *----------------------------------------------------------------------
495 *
496 * Tcl_GetServiceMode --
497 *
498 * This routine returns the current service mode of the notifier.
499 *
500 * Results:
501 * Returns either TCL_SERVICE_ALL or TCL_SERVICE_NONE.
502 *
503 * Side effects:
504 * None.
505 *
506 *----------------------------------------------------------------------
507 */
508
509int
510Tcl_GetServiceMode()
511{
512 if (!initialized) {
513 InitNotifier();
514 }
515
516 return notifier.serviceMode;
517}
518
519/*
520 *----------------------------------------------------------------------
521 *
522 * Tcl_SetServiceMode --
523 *
524 * This routine sets the current service mode of the notifier.
525 *
526 * Results:
527 * Returns the previous service mode.
528 *
529 * Side effects:
530 * None.
531 *
532 *----------------------------------------------------------------------
533 */
534
535int
536Tcl_SetServiceMode(mode)
537 int mode; /* New service mode: TCL_SERVICE_ALL or
538 * TCL_SERVICE_NONE */
539{
540 int oldMode;
541
542 if (!initialized) {
543 InitNotifier();
544 }
545
546 oldMode = notifier.serviceMode;
547 notifier.serviceMode = mode;
548 return oldMode;
549}
550
551/*
552 *----------------------------------------------------------------------
553 *
554 * Tcl_SetMaxBlockTime --
555 *
556 * This procedure is invoked by event sources to tell the notifier
557 * how long it may block the next time it blocks. The timePtr
558 * argument gives a maximum time; the actual time may be less if
559 * some other event source requested a smaller time.
560 *
561 * Results:
562 * None.
563 *
564 * Side effects:
565 * May reduce the length of the next sleep in the notifier.
566 *
567 *----------------------------------------------------------------------
568 */
569
570void
571Tcl_SetMaxBlockTime(timePtr)
572 Tcl_Time *timePtr; /* Specifies a maximum elapsed time for
573 * the next blocking operation in the
574 * event notifier. */
575{
576 if (!initialized) {
577 InitNotifier();
578 }
579
580 if (!notifier.blockTimeSet || (timePtr->sec < notifier.blockTime.sec)
581 || ((timePtr->sec == notifier.blockTime.sec)
582 && (timePtr->usec < notifier.blockTime.usec))) {
583 notifier.blockTime = *timePtr;
584 notifier.blockTimeSet = 1;
585 }
586
587 /*
588 * If we are called outside an event source traversal, set the
589 * timeout immediately.
590 */
591
592 if (!notifier.inTraversal) {
593 if (notifier.blockTimeSet) {
594 Tcl_SetTimer(&notifier.blockTime);
595 } else {
596 Tcl_SetTimer(NULL);
597 }
598 }
599}
600
601/*
602 *----------------------------------------------------------------------
603 *
604 * Tcl_DoOneEvent --
605 *
606 * Process a single event of some sort. If there's no work to
607 * do, wait for an event to occur, then process it.
608 *
609 * Results:
610 * The return value is 1 if the procedure actually found an event
611 * to process. If no processing occurred, then 0 is returned (this
612 * can happen if the TCL_DONT_WAIT flag is set or if there are no
613 * event handlers to wait for in the set specified by flags).
614 *
615 * Side effects:
616 * May delay execution of process while waiting for an event,
617 * unless TCL_DONT_WAIT is set in the flags argument. Event
618 * sources are invoked to check for and queue events. Event
619 * handlers may produce arbitrary side effects.
620 *
621 *----------------------------------------------------------------------
622 */
623
624int
625Tcl_DoOneEvent(flags)
626 int flags; /* Miscellaneous flag values: may be any
627 * combination of TCL_DONT_WAIT,
628 * TCL_WINDOW_EVENTS, TCL_FILE_EVENTS,
629 * TCL_TIMER_EVENTS, TCL_IDLE_EVENTS, or
630 * others defined by event sources. */
631{
632 int result = 0, oldMode;
633 EventSource *sourcePtr;
634 Tcl_Time *timePtr;
635
636 if (!initialized) {
637 InitNotifier();
638 }
639
640 /*
641 * The first thing we do is to service any asynchronous event
642 * handlers.
643 */
644
645 if (Tcl_AsyncReady()) {
646 (void) Tcl_AsyncInvoke((Tcl_Interp *) NULL, 0);
647 return 1;
648 }
649
650 /*
651 * No event flags is equivalent to TCL_ALL_EVENTS.
652 */
653
654 if ((flags & TCL_ALL_EVENTS) == 0) {
655 flags |= TCL_ALL_EVENTS;
656 }
657
658 /*
659 * Set the service mode to none so notifier event routines won't
660 * try to service events recursively.
661 */
662
663 oldMode = notifier.serviceMode;
664 notifier.serviceMode = TCL_SERVICE_NONE;
665
666 /*
667 * The core of this procedure is an infinite loop, even though
668 * we only service one event. The reason for this is that we
669 * may be processing events that don't do anything inside of Tcl.
670 */
671
672 while (1) {
673
674 /*
675 * If idle events are the only things to service, skip the
676 * main part of the loop and go directly to handle idle
677 * events (i.e. don't wait even if TCL_DONT_WAIT isn't set).
678 */
679
680 if ((flags & TCL_ALL_EVENTS) == TCL_IDLE_EVENTS) {
681 flags = TCL_IDLE_EVENTS|TCL_DONT_WAIT;
682 goto idleEvents;
683 }
684
685 /*
686 * Ask Tcl to service a queued event, if there are any.
687 */
688
689 if (Tcl_ServiceEvent(flags)) {
690 result = 1;
691 break;
692 }
693
694 /*
695 * If TCL_DONT_WAIT is set, be sure to poll rather than
696 * blocking, otherwise reset the block time to infinity.
697 */
698
699 if (flags & TCL_DONT_WAIT) {
700 notifier.blockTime.sec = 0;
701 notifier.blockTime.usec = 0;
702 notifier.blockTimeSet = 1;
703 } else {
704 notifier.blockTimeSet = 0;
705 }
706
707 /*
708 * Set up all the event sources for new events. This will
709 * cause the block time to be updated if necessary.
710 */
711
712 notifier.inTraversal = 1;
713 for (sourcePtr = notifier.firstEventSourcePtr; sourcePtr != NULL;
714 sourcePtr = sourcePtr->nextPtr) {
715 if (sourcePtr->setupProc) {
716 (sourcePtr->setupProc)(sourcePtr->clientData, flags);
717 }
718 }
719 notifier.inTraversal = 0;
720
721 if ((flags & TCL_DONT_WAIT) || notifier.blockTimeSet) {
722 timePtr = &notifier.blockTime;
723 } else {
724 timePtr = NULL;
725 }
726
727 /*
728 * Wait for a new event or a timeout. If Tcl_WaitForEvent
729 * returns -1, we should abort Tcl_DoOneEvent.
730 */
731
732 result = Tcl_WaitForEvent(timePtr);
733 if (result < 0) {
734 result = 0;
735 break;
736 }
737
738 /*
739 * Check all the event sources for new events.
740 */
741
742 for (sourcePtr = notifier.firstEventSourcePtr; sourcePtr != NULL;
743 sourcePtr = sourcePtr->nextPtr) {
744 if (sourcePtr->checkProc) {
745 (sourcePtr->checkProc)(sourcePtr->clientData, flags);
746 }
747 }
748
749 /*
750 * Check for events queued by the notifier or event sources.
751 */
752
753 if (Tcl_ServiceEvent(flags)) {
754 result = 1;
755 break;
756 }
757
758 /*
759 * We've tried everything at this point, but nobody we know
760 * about had anything to do. Check for idle events. If none,
761 * either quit or go back to the top and try again.
762 */
763
764 idleEvents:
765 if (flags & TCL_IDLE_EVENTS) {
766 if (TclServiceIdle()) {
767 result = 1;
768 break;
769 }
770 }
771 if (flags & TCL_DONT_WAIT) {
772 break;
773 }
774 }
775
776 notifier.serviceMode = oldMode;
777 return result;
778}
779
780/*
781 *----------------------------------------------------------------------
782 *
783 * Tcl_ServiceAll --
784 *
785 * This routine checks all of the event sources, processes
786 * events that are on the Tcl event queue, and then calls the
787 * any idle handlers. Platform specific notifier callbacks that
788 * generate events should call this routine before returning to
789 * the system in order to ensure that Tcl gets a chance to
790 * process the new events.
791 *
792 * Results:
793 * Returns 1 if an event or idle handler was invoked, else 0.
794 *
795 * Side effects:
796 * Anything that an event or idle handler may do.
797 *
798 *----------------------------------------------------------------------
799 */
800
801int
802Tcl_ServiceAll()
803{
804 int result = 0;
805 EventSource *sourcePtr;
806
807 if (!initialized) {
808 InitNotifier();
809 }
810
811 if (notifier.serviceMode == TCL_SERVICE_NONE) {
812 return result;
813 }
814
815 /*
816 * We need to turn off event servicing like we to in Tcl_DoOneEvent,
817 * to avoid recursive calls.
818 */
819
820 notifier.serviceMode = TCL_SERVICE_NONE;
821
822 /*
823 * Check async handlers first.
824 */
825
826 if (Tcl_AsyncReady()) {
827 (void) Tcl_AsyncInvoke((Tcl_Interp *) NULL, 0);
828 }
829
830 /*
831 * Make a single pass through all event sources, queued events,
832 * and idle handlers. Note that we wait to update the notifier
833 * timer until the end so we can avoid multiple changes.
834 */
835
836 notifier.inTraversal = 1;
837 notifier.blockTimeSet = 0;
838
839 for (sourcePtr = notifier.firstEventSourcePtr; sourcePtr != NULL;
840 sourcePtr = sourcePtr->nextPtr) {
841 if (sourcePtr->setupProc) {
842 (sourcePtr->setupProc)(sourcePtr->clientData, TCL_ALL_EVENTS);
843 }
844 }
845 for (sourcePtr = notifier.firstEventSourcePtr; sourcePtr != NULL;
846 sourcePtr = sourcePtr->nextPtr) {
847 if (sourcePtr->checkProc) {
848 (sourcePtr->checkProc)(sourcePtr->clientData, TCL_ALL_EVENTS);
849 }
850 }
851
852 while (Tcl_ServiceEvent(0)) {
853 result = 1;
854 }
855 if (TclServiceIdle()) {
856 result = 1;
857 }
858
859 if (!notifier.blockTimeSet) {
860 Tcl_SetTimer(NULL);
861 } else {
862 Tcl_SetTimer(&notifier.blockTime);
863 }
864 notifier.inTraversal = 0;
865 notifier.serviceMode = TCL_SERVICE_ALL;
866 return result;
867}
868
869/*
870 *----------------------------------------------------------------------
871 *
872 * PyTcl_WaitUntilEvent --
873 *
874 * New function to wait until a Tcl event is ready without
875 * actually handling the event. This is different than
876 * TclWaitForEvent(): that function doesn't call the event
877 * check routines, which is necessary for our purpose.
878 * We also can't use Tcl_DoOneEvent(TCL_DONT_WAIT), since that
879 * does too much: it handles the event. We want the *handling*
880 * of the event to be done with the Python lock held, but the
881 * *waiting* with the lock released.
882 *
883 * Since the event administration is not exported, our only
884 * choice is to use a modified copy of the file tclNotify.c,
885 * containing this additional function that makes the desired
886 * functionality available. It is mostly a stripped down version
887 * of the code in Tcl_DoOneEvent().
888 *
889 * This requires that you link with a static version of the Tcl
890 * library. On Windows/Mac, a custom compilation of Tcl may be
891 * required (I haven't tried this yet).
892 *
893 *----------------------------------------------------------------------
894 */
895
896int
897PyTcl_WaitUntilEvent()
898{
899 int flags = TCL_ALL_EVENTS;
900 int result = 0, oldMode;
901 EventSource *sourcePtr;
902 Tcl_Time *timePtr;
903
904 if (!initialized) {
905 InitNotifier();
906 }
907
908 /*
909 * The first thing we do is to service any asynchronous event
910 * handlers.
911 */
912
913 if (Tcl_AsyncReady())
914 return 1;
915
916 /*
917 * Set the service mode to none so notifier event routines won't
918 * try to service events recursively.
919 */
920
921 oldMode = notifier.serviceMode;
922 notifier.serviceMode = TCL_SERVICE_NONE;
923
924 notifier.blockTimeSet = 0;
925
926 /*
927 * Set up all the event sources for new events. This will
928 * cause the block time to be updated if necessary.
929 */
930
931 notifier.inTraversal = 1;
932 for (sourcePtr = notifier.firstEventSourcePtr; sourcePtr != NULL;
933 sourcePtr = sourcePtr->nextPtr) {
934 if (sourcePtr->setupProc) {
935 (sourcePtr->setupProc)(sourcePtr->clientData, flags);
936 }
937 }
938 notifier.inTraversal = 0;
939
940 timePtr = NULL;
941
942 /*
943 * Wait for a new event or a timeout. If Tcl_WaitForEvent
944 * returns -1, we should abort Tcl_DoOneEvent.
945 */
946
947 result = Tcl_WaitForEvent(timePtr);
948 if (result < 0)
949 return 0;
950
951 /*
952 * Check all the event sources for new events.
953 */
954
955 for (sourcePtr = notifier.firstEventSourcePtr; sourcePtr != NULL;
956 sourcePtr = sourcePtr->nextPtr) {
957 if (sourcePtr->checkProc) {
958 (sourcePtr->checkProc)(sourcePtr->clientData, flags);
959 }
960 }
961
962 notifier.serviceMode = oldMode;
963 return result;
964}