OSDN Git Service

2010-01-06 Tristan Gingold <gingold@adacore.com>
[pf3gnuchains/pf3gnuchains3x.git] / tcl / mac / tclMacNotify.c
1 /* 
2  * tclMacNotify.c --
3  *
4  *      This file contains Macintosh-specific procedures for the notifier,
5  *      which is the lowest-level part of the Tcl event loop.  This file
6  *      works together with ../generic/tclNotify.c.
7  *
8  *      The Mac notifier only polls for system and OS events, so it is process
9  *      wide, rather than thread specific.  However, this means that the convert
10  *      event proc will have to arbitrate which events go to which threads.
11  *
12  * Copyright (c) 1995-1996 Sun Microsystems, Inc.
13  *
14  * See the file "license.terms" for information on usage and redistribution
15  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
16  *
17  * RCS: @(#) $Id$
18  */
19
20 #include "tclInt.h"
21 #include "tclPort.h"
22 #include "tclMac.h"
23 #include "tclMacInt.h"
24 #include <signal.h>
25 #include <Events.h>
26 #include <LowMem.h>
27 #include <Processes.h>
28 #include <Timer.h>
29 #include <Threads.h>
30
31
32 /* 
33  * This is necessary to work around a bug in Apple's Universal header files
34  * for the CFM68K libraries.
35  */
36
37 #ifdef __CFM68K__
38 #undef GetEventQueue
39 extern pascal QHdrPtr GetEventQueue(void)
40  THREEWORDINLINE(0x2EBC, 0x0000, 0x014A);
41 #pragma import list GetEventQueue
42 #define GetEvQHdr() GetEventQueue()
43 #endif
44
45 /*
46  * Need this for replacing Tcl_SetTimer and Tcl_WaitForEvent defined 
47  * in THIS file with ones defined in the stub table.
48  */
49  
50 extern TclStubs tclStubs;
51
52 /*
53  * The follwing static indicates whether this module has been initialized.
54  */
55
56 static int initialized = 0;
57
58 /*
59  * The following structure contains the state information for the
60  * notifier module.
61  */
62
63 static struct {
64     int timerActive;            /* 1 if timer is running. */
65     Tcl_Time timer;             /* Time when next timer event is expected. */
66     int flags;                  /* OR'ed set of flags defined below. */
67     Point lastMousePosition;    /* Last known mouse location. */
68     RgnHandle utilityRgn;       /* Region used as the mouse region for
69                                  * WaitNextEvent and the update region when
70                                  * checking for events. */   
71     Tcl_MacConvertEventPtr eventProcPtr;
72                                 /* This pointer holds the address of the
73                                  * function that will handle all incoming
74                                  * Macintosh events. */
75 } notifier;
76
77 /*
78  * The following defines are used in the flags field of the notifier struct.
79  */
80
81 #define NOTIFY_IDLE     (1<<1)  /* Tcl_ServiceIdle should be called. */
82 #define NOTIFY_TIMER    (1<<2)  /* Tcl_ServiceTimer should be called. */
83
84 /*
85  * Prototypes for procedures that are referenced only in this file:
86  */
87
88 static int              HandleMacEvents _ANSI_ARGS_((void));
89 static void             InitNotifier _ANSI_ARGS_((void));
90 static void             NotifierExitHandler _ANSI_ARGS_((
91                             ClientData clientData));
92 \f
93 /*
94  *----------------------------------------------------------------------
95  *
96  * Tcl_InitNotifier --
97  *
98  *      Initializes the platform specific notifier state.  There is no thread
99  *      specific platform notifier on the Mac, so this really doesn't do 
100  *      anything.  However, we need to return the ThreadID, since the generic
101  *      notifier hands this back to us in AlertThread.
102  *
103  * Results:
104  *      Returns the threadID for this thread.  
105  *
106  * Side effects:
107  *      None.
108  *
109  *----------------------------------------------------------------------
110  */
111
112 ClientData
113 Tcl_InitNotifier()
114 {
115     
116 #ifdef TCL_THREADS
117     ThreadID curThread;
118     if (TclMacHaveThreads()) {
119         GetCurrentThread(&curThread);
120         return (ClientData) curThread;
121     } else {
122         return NULL;
123     }
124 #else
125     return NULL;
126 #endif
127
128 }
129 \f
130 /*
131  *----------------------------------------------------------------------
132  *
133  * Tcl_FinalizeNotifier --
134  *
135  *      This function is called to cleanup the notifier state before
136  *      a thread is terminated.  There is no platform thread specific
137  *      notifier, so this does nothing.
138  *
139  * Results:
140  *      None.
141  *
142  * Side effects:
143  *      None.
144  *
145  *----------------------------------------------------------------------
146  */
147
148 void
149 Tcl_FinalizeNotifier(clientData)
150     ClientData clientData;      /* Pointer to notifier data. */
151 {
152     /* Nothing to do on the Mac */
153 }
154 \f
155 /*
156  *----------------------------------------------------------------------
157  *
158  * Tcl_AlertNotifier --
159  *
160  *      Wake up the specified notifier from any thread. This routine
161  *      is called by the platform independent notifier code whenever
162  *      the Tcl_ThreadAlert routine is called.  This routine is
163  *      guaranteed not to be called on a given notifier after
164  *      Tcl_FinalizeNotifier is called for that notifier.
165  *
166  * Results:
167  *      None.
168  *
169  * Side effects:
170  *      Calls YieldToThread from this thread.
171  *
172  *----------------------------------------------------------------------
173  */
174
175 void
176 Tcl_AlertNotifier(clientData)
177     ClientData clientData;      /* Pointer to thread data. */
178 {
179
180 #ifdef TCL_THREADS
181     if (TclMacHaveThreads()) {
182         YieldToThread((ThreadID) clientData);
183     }
184 #endif
185
186 }
187 \f
188 /*
189  *----------------------------------------------------------------------
190  *
191  * InitNotifier --
192  *
193  *      Initializes the notifier structure.  Note - this function is never
194  *      used.
195  *
196  * Results:
197  *      None.
198  *
199  * Side effects:
200  *      Creates a new exit handler.
201  *
202  *----------------------------------------------------------------------
203  */
204
205 static void
206 InitNotifier(void)
207 {
208     initialized = 1;
209     memset(&notifier, 0, sizeof(notifier));
210     Tcl_CreateExitHandler(NotifierExitHandler, NULL);
211 }
212 \f
213 /*
214  *----------------------------------------------------------------------
215  *
216  * NotifierExitHandler --
217  *
218  *      This function is called to cleanup the notifier state before
219  *      Tcl is unloaded.  This function is never used, since InitNotifier
220  *      isn't either.
221  *
222  * Results:
223  *      None.
224  *
225  * Side effects:
226  *      None.
227  *
228  *----------------------------------------------------------------------
229  */
230
231 static void
232 NotifierExitHandler(
233     ClientData clientData)      /* Not used. */
234 {
235     initialized = 0;
236 }
237 \f
238 /*
239  *----------------------------------------------------------------------
240  *
241  * HandleMacEvents --
242  *
243  *      This function checks for events from the Macintosh event queue.
244  *
245  * Results:
246  *      Returns 1 if event found, 0 otherwise.
247  *
248  * Side effects:
249  *      Pulls events off of the Mac event queue and then calls
250  *      convertEventProc.
251  *
252  *----------------------------------------------------------------------
253  */
254
255 static int
256 HandleMacEvents(void)
257 {
258     EventRecord theEvent;
259     int eventFound = 0, needsUpdate = 0;
260     Point currentMouse;
261     WindowRef windowRef;
262     Rect mouseRect;
263
264     /*
265      * Check for mouse moved events.  These events aren't placed on the
266      * system event queue unless we call WaitNextEvent.
267      */
268
269     GetGlobalMouseTcl(&currentMouse);
270     if ((notifier.eventProcPtr != NULL) &&
271             !EqualPt(currentMouse, notifier.lastMousePosition)) {
272         notifier.lastMousePosition = currentMouse;
273         theEvent.what = nullEvent;
274         if ((*notifier.eventProcPtr)(&theEvent) == true) {
275             eventFound = 1;
276         }
277     }
278
279     /*
280      * Check for update events.  Since update events aren't generated
281      * until we call GetNextEvent, we may need to force a call to
282      * GetNextEvent, even if the queue is empty.
283      */
284
285     for (windowRef = FrontWindow(); windowRef != NULL;
286             windowRef = GetNextWindow(windowRef)) {
287         GetWindowUpdateRgn(windowRef, notifier.utilityRgn);
288         if (!EmptyRgn(notifier.utilityRgn)) {
289             needsUpdate = 1;
290             break;
291         }
292     }
293     
294     /*
295      * Process events from the OS event queue.
296      */
297
298     while (needsUpdate || (GetEvQHdr()->qHead != NULL)) {
299         GetGlobalMouseTcl(&currentMouse);
300         SetRect(&mouseRect, currentMouse.h, currentMouse.v,
301                 currentMouse.h + 1, currentMouse.v + 1);
302         RectRgn(notifier.utilityRgn, &mouseRect);
303         
304         WaitNextEvent(everyEvent, &theEvent, 5, notifier.utilityRgn);
305         needsUpdate = 0;
306         if ((notifier.eventProcPtr != NULL)
307                 && ((*notifier.eventProcPtr)(&theEvent) == true)) {
308             eventFound = 1;
309         }
310     }
311     
312     return eventFound;
313 }
314 \f
315 /*
316  *----------------------------------------------------------------------
317  *
318  * Tcl_SetTimer --
319  *
320  *      This procedure sets the current notifier timer value.  The
321  *      notifier will ensure that Tcl_ServiceAll() is called after
322  *      the specified interval, even if no events have occurred.
323  *
324  * Results:
325  *      None.
326  *
327  * Side effects:
328  *      Replaces any previous timer.
329  *
330  *----------------------------------------------------------------------
331  */
332
333 void
334 Tcl_SetTimer(
335     Tcl_Time *timePtr)          /* New value for interval timer. */
336 {
337     /*
338      * Allow the notifier to be hooked.  This may not make sense
339      * on the Mac, but mirrors the UNIX hook.
340      */
341
342     if (tclStubs.tcl_SetTimer != Tcl_SetTimer) {
343         tclStubs.tcl_SetTimer(timePtr);
344         return;
345     }
346
347     if (!timePtr) {
348         notifier.timerActive = 0;
349     } else {
350         /*
351          * Compute when the timer should fire.
352          */
353         
354         Tcl_GetTime(&notifier.timer);
355         notifier.timer.sec += timePtr->sec;
356         notifier.timer.usec += timePtr->usec;
357         if (notifier.timer.usec >= 1000000) {
358             notifier.timer.usec -= 1000000;
359             notifier.timer.sec += 1;
360         }
361         notifier.timerActive = 1;
362     }
363 }
364 \f
365 /*
366  *----------------------------------------------------------------------
367  *
368  * Tcl_ServiceModeHook --
369  *
370  *      This function is invoked whenever the service mode changes.
371  *
372  * Results:
373  *      None.
374  *
375  * Side effects:
376  *      None.
377  *
378  *----------------------------------------------------------------------
379  */
380
381 void
382 Tcl_ServiceModeHook(mode)
383     int mode;                   /* Either TCL_SERVICE_ALL, or
384                                  * TCL_SERVICE_NONE. */
385 {
386 }
387 \f
388 /*
389  *----------------------------------------------------------------------
390  *
391  * Tcl_WaitForEvent --
392  *
393  *      This function is called by Tcl_DoOneEvent to wait for new
394  *      events on the message queue.  If the block time is 0, then
395  *      Tcl_WaitForEvent just polls the event queue without blocking.
396  *
397  * Results:
398  *      Always returns 0.
399  *
400  * Side effects:
401  *      None.
402  *
403  *----------------------------------------------------------------------
404  */
405
406 int
407 Tcl_WaitForEvent(
408     Tcl_Time *timePtr)          /* Maximum block time. */
409 {
410     int found;
411     EventRecord macEvent;
412     long sleepTime = 5;
413     long ms;
414     Point currentMouse;
415     void * timerToken;
416     Rect mouseRect;
417
418     /*
419      * Allow the notifier to be hooked.  This may not make
420      * sense on the Mac, but mirrors the UNIX hook.
421      */
422
423     if (tclStubs.tcl_WaitForEvent != Tcl_WaitForEvent) {
424         return tclStubs.tcl_WaitForEvent(timePtr);
425     }
426
427     /*
428      * Compute the next timeout value.
429      */
430
431     if (!timePtr) {
432         ms = INT_MAX;
433     } else {
434         ms = (timePtr->sec * 1000) + (timePtr->usec / 1000);
435     }
436     timerToken = TclMacStartTimer((long) ms);
437    
438     /*
439      * Poll the Mac event sources.  This loop repeats until something
440      * happens: a timeout, a socket event, mouse motion, or some other
441      * window event.  Note that we don't call WaitNextEvent if another
442      * event is found to avoid context switches.  This effectively gives
443      * events coming in via WaitNextEvent a slightly lower priority.
444      */
445
446     found = 0;
447     if (notifier.utilityRgn == NULL) {
448         notifier.utilityRgn = NewRgn();
449     }
450
451     while (!found) {
452         /*
453          * Check for generated and queued events.
454          */
455
456         if (HandleMacEvents()) {
457             found = 1;
458         }
459
460         /*
461          * Check for time out.
462          */
463
464         if (!found && TclMacTimerExpired(timerToken)) {
465             found = 1;
466         }
467
468         /*
469          * Check for window events.  We may receive a NULL event for
470          * various reasons. 1) the timer has expired, 2) a mouse moved
471          * event is occuring or 3) the os is giving us time for idle
472          * events.  Note that we aren't sharing the processor very
473          * well here.  We really ought to do a better job of calling
474          * WaitNextEvent for time slicing purposes.
475          */
476
477         if (!found) {
478             /*
479              * Set up mouse region so we will wake if the mouse is moved.
480              * We do this by defining the smallest possible region around
481              * the current mouse position.
482              */
483
484             GetGlobalMouseTcl(&currentMouse);
485             SetRect(&mouseRect, currentMouse.h, currentMouse.v,
486                     currentMouse.h + 1, currentMouse.v + 1);
487             RectRgn(notifier.utilityRgn, &mouseRect);
488         
489             WaitNextEvent(everyEvent, &macEvent, sleepTime,
490                     notifier.utilityRgn);
491
492             if (notifier.eventProcPtr != NULL) {
493                 if ((*notifier.eventProcPtr)(&macEvent) == true) {
494                     found = 1;
495                 }
496             }
497         }
498     }
499     TclMacRemoveTimer(timerToken);
500     
501     /*
502      * Yield time to nay other thread at this point.  If we find that the
503      * apps thrash too switching between threads, we can put a timer here,
504      * and only yield when the timer fires.
505      */
506      
507     if (TclMacHaveThreads()) {
508         YieldToAnyThread();
509     }
510     
511     return 0;
512 }
513 \f
514 /*
515  *----------------------------------------------------------------------
516  *
517  * Tcl_Sleep --
518  *
519  *      Delay execution for the specified number of milliseconds.  This
520  *      is not a very good call to make.  It will block the system -
521  *      you will not even be able to switch applications.
522  *
523  * Results:
524  *      None.
525  *
526  * Side effects:
527  *      Time passes.
528  *
529  *----------------------------------------------------------------------
530  */
531
532 void
533 Tcl_Sleep(
534     int ms)                     /* Number of milliseconds to sleep. */
535 {
536     EventRecord dummy;
537     void *timerToken;
538     
539     if (ms <= 0) {
540         return;
541     }
542     
543     timerToken = TclMacStartTimer((long) ms);
544     while (1) {
545         WaitNextEvent(0, &dummy, (ms / 16.66) + 1, NULL);
546         if (TclMacHaveThreads()) {
547             YieldToAnyThread();
548         }
549         if (TclMacTimerExpired(timerToken)) {
550             break;
551         }
552     }
553     TclMacRemoveTimer(timerToken);
554 }
555 \f
556 /*
557  *----------------------------------------------------------------------
558  *
559  * Tcl_MacSetEventProc --
560  *
561  *      This function sets the event handling procedure for the 
562  *      application.  This function will be passed all incoming Mac
563  *      events.  This function usually controls the console or some
564  *      other entity like Tk.
565  *
566  * Results:
567  *      None.
568  *
569  * Side effects:
570  *      Changes the event handling function.
571  *
572  *----------------------------------------------------------------------
573  */
574
575 void
576 Tcl_MacSetEventProc(
577     Tcl_MacConvertEventPtr procPtr)
578 {
579     notifier.eventProcPtr = procPtr;
580 }