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.
7 * Original comments follow. The file license.terms from the Tcl 8.0
8 * distribution is contained in this directory, as required.
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.
20 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
22 * See the file "license.terms" for information on usage and redistribution
23 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
25 * SCCS: @(#) tclNotify.c 1.15 97/06/18 17:14:04
32 * The following static indicates whether this module has been initialized.
35 static int initialized
= 0;
38 * For each event source (created with Tcl_CreateEventSource) there
39 * is a structure of the following type:
42 typedef struct EventSource
{
43 Tcl_EventSetupProc
*setupProc
;
44 Tcl_EventCheckProc
*checkProc
;
45 ClientData clientData
;
46 struct EventSource
*nextPtr
;
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
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
65 int serviceMode
; /* One of TCL_SERVICE_NONE or
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. */
79 * Declarations for functions used in this file.
82 static void InitNotifier
_ANSI_ARGS_((void));
83 static void NotifierExitHandler
_ANSI_ARGS_((ClientData clientData
));
87 *----------------------------------------------------------------------
91 * This routine is called to initialize the notifier module.
97 * Creates an exit handler and initializes static data.
99 *----------------------------------------------------------------------
106 memset(¬ifier
, 0, sizeof(notifier
));
107 notifier
.serviceMode
= TCL_SERVICE_NONE
;
108 Tcl_CreateExitHandler(NotifierExitHandler
, NULL
);
112 *----------------------------------------------------------------------
114 * NotifierExitHandler --
116 * This routine is called during Tcl finalization.
122 * Clears the notifier intialization flag.
124 *----------------------------------------------------------------------
128 NotifierExitHandler(clientData
)
129 ClientData clientData
; /* Not used. */
135 *----------------------------------------------------------------------
137 * Tcl_CreateEventSource --
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
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.
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.
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.
167 *----------------------------------------------------------------------
171 Tcl_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. */
179 EventSource
*sourcePtr
;
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
;
194 *----------------------------------------------------------------------
196 * Tcl_DeleteEventSource --
198 * This procedure is invoked to delete the source of events
199 * given by proc and clientData.
205 * The given event source is cancelled, so its procedure will
206 * never again be called. If no such source exists, nothing
209 *----------------------------------------------------------------------
213 Tcl_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. */
221 EventSource
*sourcePtr
, *prevPtr
;
223 for (sourcePtr
= notifier
.firstEventSourcePtr
, prevPtr
= NULL
;
225 prevPtr
= sourcePtr
, sourcePtr
= sourcePtr
->nextPtr
) {
226 if ((sourcePtr
->setupProc
!= setupProc
)
227 || (sourcePtr
->checkProc
!= checkProc
)
228 || (sourcePtr
->clientData
!= clientData
)) {
231 if (prevPtr
== NULL
) {
232 notifier
.firstEventSourcePtr
= sourcePtr
->nextPtr
;
234 prevPtr
->nextPtr
= sourcePtr
->nextPtr
;
236 ckfree((char *) sourcePtr
);
242 *----------------------------------------------------------------------
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.
259 *----------------------------------------------------------------------
263 Tcl_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
270 Tcl_QueuePosition position
; /* One of TCL_QUEUE_TAIL, TCL_QUEUE_HEAD,
277 if (position
== TCL_QUEUE_TAIL
) {
279 * Append the event on the end of the queue.
282 evPtr
->nextPtr
= NULL
;
283 if (notifier
.firstEventPtr
== NULL
) {
284 notifier
.firstEventPtr
= evPtr
;
286 notifier
.lastEventPtr
->nextPtr
= evPtr
;
288 notifier
.lastEventPtr
= evPtr
;
289 } else if (position
== TCL_QUEUE_HEAD
) {
291 * Push the event on the head of the queue.
294 evPtr
->nextPtr
= notifier
.firstEventPtr
;
295 if (notifier
.firstEventPtr
== NULL
) {
296 notifier
.lastEventPtr
= evPtr
;
298 notifier
.firstEventPtr
= evPtr
;
299 } else if (position
== TCL_QUEUE_MARK
) {
301 * Insert the event after the current marker event and advance
302 * the marker to the new event.
305 if (notifier
.markerEventPtr
== NULL
) {
306 evPtr
->nextPtr
= notifier
.firstEventPtr
;
307 notifier
.firstEventPtr
= evPtr
;
309 evPtr
->nextPtr
= notifier
.markerEventPtr
->nextPtr
;
310 notifier
.markerEventPtr
->nextPtr
= evPtr
;
312 notifier
.markerEventPtr
= evPtr
;
313 if (evPtr
->nextPtr
== NULL
) {
314 notifier
.lastEventPtr
= evPtr
;
320 *----------------------------------------------------------------------
322 * Tcl_DeleteEvents --
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.
332 * Potentially removes one or more events from the event queue.
334 *----------------------------------------------------------------------
338 Tcl_DeleteEvents(proc
, clientData
)
339 Tcl_EventDeleteProc
*proc
; /* The procedure to call. */
340 ClientData clientData
; /* type-specific data. */
342 Tcl_Event
*evPtr
, *prevPtr
, *hold
;
348 for (prevPtr
= (Tcl_Event
*) NULL
, evPtr
= notifier
.firstEventPtr
;
349 evPtr
!= (Tcl_Event
*) NULL
;
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
;
358 prevPtr
->nextPtr
= evPtr
->nextPtr
;
361 evPtr
= evPtr
->nextPtr
;
362 ckfree((char *) hold
);
365 evPtr
= evPtr
->nextPtr
;
371 *----------------------------------------------------------------------
373 * Tcl_ServiceEvent --
375 * Process one event from the event queue, or invoke an
376 * asynchronous event handler.
379 * The return value is 1 if the procedure actually found an event
380 * to process. If no processing occurred, then 0 is returned.
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.
387 *----------------------------------------------------------------------
391 Tcl_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
399 Tcl_Event
*evPtr
, *prevPtr
;
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.
412 if (Tcl_AsyncReady()) {
413 (void) Tcl_AsyncInvoke((Tcl_Interp
*) NULL
, 0);
418 * No event flags is equivalent to TCL_ALL_EVENTS.
421 if ((flags
& TCL_ALL_EVENTS
) == 0) {
422 flags
|= TCL_ALL_EVENTS
;
426 * Loop through all the events in the queue until we find one
427 * that can actually be handled.
430 for (evPtr
= notifier
.firstEventPtr
; evPtr
!= NULL
;
431 evPtr
= evPtr
->nextPtr
) {
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:
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
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.
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
;
456 if (notifier
.markerEventPtr
== evPtr
) {
457 notifier
.markerEventPtr
= NULL
;
460 for (prevPtr
= notifier
.firstEventPtr
;
461 prevPtr
->nextPtr
!= evPtr
; prevPtr
= prevPtr
->nextPtr
) {
462 /* Empty loop body. */
464 prevPtr
->nextPtr
= evPtr
->nextPtr
;
465 if (evPtr
->nextPtr
== NULL
) {
466 notifier
.lastEventPtr
= prevPtr
;
468 if (notifier
.markerEventPtr
== evPtr
) {
469 notifier
.markerEventPtr
= prevPtr
;
472 ckfree((char *) evPtr
);
476 * The event wasn't actually handled, so we have to restore
477 * the proc field to allow the event to be attempted again.
484 * The handler for this event asked to defer it. Just go on to
494 *----------------------------------------------------------------------
496 * Tcl_GetServiceMode --
498 * This routine returns the current service mode of the notifier.
501 * Returns either TCL_SERVICE_ALL or TCL_SERVICE_NONE.
506 *----------------------------------------------------------------------
516 return notifier
.serviceMode
;
520 *----------------------------------------------------------------------
522 * Tcl_SetServiceMode --
524 * This routine sets the current service mode of the notifier.
527 * Returns the previous service mode.
532 *----------------------------------------------------------------------
536 Tcl_SetServiceMode(mode
)
537 int mode
; /* New service mode: TCL_SERVICE_ALL or
538 * TCL_SERVICE_NONE */
546 oldMode
= notifier
.serviceMode
;
547 notifier
.serviceMode
= mode
;
552 *----------------------------------------------------------------------
554 * Tcl_SetMaxBlockTime --
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.
565 * May reduce the length of the next sleep in the notifier.
567 *----------------------------------------------------------------------
571 Tcl_SetMaxBlockTime(timePtr
)
572 Tcl_Time
*timePtr
; /* Specifies a maximum elapsed time for
573 * the next blocking operation in the
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;
588 * If we are called outside an event source traversal, set the
589 * timeout immediately.
592 if (!notifier
.inTraversal
) {
593 if (notifier
.blockTimeSet
) {
594 Tcl_SetTimer(¬ifier
.blockTime
);
602 *----------------------------------------------------------------------
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.
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).
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.
621 *----------------------------------------------------------------------
625 Tcl_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. */
632 int result
= 0, oldMode
;
633 EventSource
*sourcePtr
;
641 * The first thing we do is to service any asynchronous event
645 if (Tcl_AsyncReady()) {
646 (void) Tcl_AsyncInvoke((Tcl_Interp
*) NULL
, 0);
651 * No event flags is equivalent to TCL_ALL_EVENTS.
654 if ((flags
& TCL_ALL_EVENTS
) == 0) {
655 flags
|= TCL_ALL_EVENTS
;
659 * Set the service mode to none so notifier event routines won't
660 * try to service events recursively.
663 oldMode
= notifier
.serviceMode
;
664 notifier
.serviceMode
= TCL_SERVICE_NONE
;
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.
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).
680 if ((flags
& TCL_ALL_EVENTS
) == TCL_IDLE_EVENTS
) {
681 flags
= TCL_IDLE_EVENTS
|TCL_DONT_WAIT
;
686 * Ask Tcl to service a queued event, if there are any.
689 if (Tcl_ServiceEvent(flags
)) {
695 * If TCL_DONT_WAIT is set, be sure to poll rather than
696 * blocking, otherwise reset the block time to infinity.
699 if (flags
& TCL_DONT_WAIT
) {
700 notifier
.blockTime
.sec
= 0;
701 notifier
.blockTime
.usec
= 0;
702 notifier
.blockTimeSet
= 1;
704 notifier
.blockTimeSet
= 0;
708 * Set up all the event sources for new events. This will
709 * cause the block time to be updated if necessary.
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
);
719 notifier
.inTraversal
= 0;
721 if ((flags
& TCL_DONT_WAIT
) || notifier
.blockTimeSet
) {
722 timePtr
= ¬ifier
.blockTime
;
728 * Wait for a new event or a timeout. If Tcl_WaitForEvent
729 * returns -1, we should abort Tcl_DoOneEvent.
732 result
= Tcl_WaitForEvent(timePtr
);
739 * Check all the event sources for new events.
742 for (sourcePtr
= notifier
.firstEventSourcePtr
; sourcePtr
!= NULL
;
743 sourcePtr
= sourcePtr
->nextPtr
) {
744 if (sourcePtr
->checkProc
) {
745 (sourcePtr
->checkProc
)(sourcePtr
->clientData
, flags
);
750 * Check for events queued by the notifier or event sources.
753 if (Tcl_ServiceEvent(flags
)) {
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.
765 if (flags
& TCL_IDLE_EVENTS
) {
766 if (TclServiceIdle()) {
771 if (flags
& TCL_DONT_WAIT
) {
776 notifier
.serviceMode
= oldMode
;
781 *----------------------------------------------------------------------
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.
793 * Returns 1 if an event or idle handler was invoked, else 0.
796 * Anything that an event or idle handler may do.
798 *----------------------------------------------------------------------
805 EventSource
*sourcePtr
;
811 if (notifier
.serviceMode
== TCL_SERVICE_NONE
) {
816 * We need to turn off event servicing like we to in Tcl_DoOneEvent,
817 * to avoid recursive calls.
820 notifier
.serviceMode
= TCL_SERVICE_NONE
;
823 * Check async handlers first.
826 if (Tcl_AsyncReady()) {
827 (void) Tcl_AsyncInvoke((Tcl_Interp
*) NULL
, 0);
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.
836 notifier
.inTraversal
= 1;
837 notifier
.blockTimeSet
= 0;
839 for (sourcePtr
= notifier
.firstEventSourcePtr
; sourcePtr
!= NULL
;
840 sourcePtr
= sourcePtr
->nextPtr
) {
841 if (sourcePtr
->setupProc
) {
842 (sourcePtr
->setupProc
)(sourcePtr
->clientData
, TCL_ALL_EVENTS
);
845 for (sourcePtr
= notifier
.firstEventSourcePtr
; sourcePtr
!= NULL
;
846 sourcePtr
= sourcePtr
->nextPtr
) {
847 if (sourcePtr
->checkProc
) {
848 (sourcePtr
->checkProc
)(sourcePtr
->clientData
, TCL_ALL_EVENTS
);
852 while (Tcl_ServiceEvent(0)) {
855 if (TclServiceIdle()) {
859 if (!notifier
.blockTimeSet
) {
862 Tcl_SetTimer(¬ifier
.blockTime
);
864 notifier
.inTraversal
= 0;
865 notifier
.serviceMode
= TCL_SERVICE_ALL
;
870 *----------------------------------------------------------------------
872 * PyTcl_WaitUntilEvent --
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.
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().
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).
893 *----------------------------------------------------------------------
897 PyTcl_WaitUntilEvent()
899 int flags
= TCL_ALL_EVENTS
;
900 int result
= 0, oldMode
;
901 EventSource
*sourcePtr
;
909 * The first thing we do is to service any asynchronous event
913 if (Tcl_AsyncReady())
917 * Set the service mode to none so notifier event routines won't
918 * try to service events recursively.
921 oldMode
= notifier
.serviceMode
;
922 notifier
.serviceMode
= TCL_SERVICE_NONE
;
924 notifier
.blockTimeSet
= 0;
927 * Set up all the event sources for new events. This will
928 * cause the block time to be updated if necessary.
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
);
938 notifier
.inTraversal
= 0;
943 * Wait for a new event or a timeout. If Tcl_WaitForEvent
944 * returns -1, we should abort Tcl_DoOneEvent.
947 result
= Tcl_WaitForEvent(timePtr
);
952 * Check all the event sources for new events.
955 for (sourcePtr
= notifier
.firstEventSourcePtr
; sourcePtr
!= NULL
;
956 sourcePtr
= sourcePtr
->nextPtr
) {
957 if (sourcePtr
->checkProc
) {
958 (sourcePtr
->checkProc
)(sourcePtr
->clientData
, flags
);
962 notifier
.serviceMode
= oldMode
;