/* * tclMacNotify.c -- * * This file contains Macintosh-specific procedures for the notifier, * which is the lowest-level part of the Tcl event loop. This file * works together with ../generic/tclNotify.c. * * The Mac notifier only polls for system and OS events, so it is process * wide, rather than thread specific. However, this means that the convert * event proc will have to arbitrate which events go to which threads. * * Copyright (c) 1995-1996 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tclMacNotify.c,v 1.8.4.1 2003/03/21 03:24:08 dgp Exp $ */ #include "tclInt.h" #include "tclPort.h" #include "tclMac.h" #include "tclMacInt.h" #include #include #include #include #include #include /* * This is necessary to work around a bug in Apple's Universal header files * for the CFM68K libraries. */ #ifdef __CFM68K__ #undef GetEventQueue extern pascal QHdrPtr GetEventQueue(void) THREEWORDINLINE(0x2EBC, 0x0000, 0x014A); #pragma import list GetEventQueue #define GetEvQHdr() GetEventQueue() #endif /* * Need this for replacing Tcl_SetTimer and Tcl_WaitForEvent defined * in THIS file with ones defined in the stub table. */ extern TclStubs tclStubs; extern Tcl_NotifierProcs tclOriginalNotifier; /* * The follwing static indicates whether this module has been initialized. */ static int initialized = 0; /* * The following structure contains the state information for the * notifier module. */ static struct { int timerActive; /* 1 if timer is running. */ Tcl_Time timer; /* Time when next timer event is expected. */ int flags; /* OR'ed set of flags defined below. */ Point lastMousePosition; /* Last known mouse location. */ RgnHandle utilityRgn; /* Region used as the mouse region for * WaitNextEvent and the update region when * checking for events. */ Tcl_MacConvertEventPtr eventProcPtr; /* This pointer holds the address of the * function that will handle all incoming * Macintosh events. */ } notifier; /* * The following defines are used in the flags field of the notifier struct. */ #define NOTIFY_IDLE (1<<1) /* Tcl_ServiceIdle should be called. */ #define NOTIFY_TIMER (1<<2) /* Tcl_ServiceTimer should be called. */ /* * Prototypes for procedures that are referenced only in this file: */ static int HandleMacEvents _ANSI_ARGS_((void)); static void InitNotifier _ANSI_ARGS_((void)); static void NotifierExitHandler _ANSI_ARGS_(( ClientData clientData)); /* *---------------------------------------------------------------------- * * Tcl_InitNotifier -- * * Initializes the platform specific notifier state. There is no thread * specific platform notifier on the Mac, so this really doesn't do * anything. However, we need to return the ThreadID, since the generic * notifier hands this back to us in AlertThread. * * Results: * Returns the threadID for this thread. * * Side effects: * None. * *---------------------------------------------------------------------- */ ClientData Tcl_InitNotifier() { #ifdef TCL_THREADS ThreadID curThread; if (TclMacHaveThreads()) { GetCurrentThread(&curThread); return (ClientData) curThread; } else { return NULL; } #else return NULL; #endif } /* *---------------------------------------------------------------------- * * Tcl_FinalizeNotifier -- * * This function is called to cleanup the notifier state before * a thread is terminated. There is no platform thread specific * notifier, so this does nothing. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_FinalizeNotifier(clientData) ClientData clientData; /* Pointer to notifier data. */ { /* Nothing to do on the Mac */ } /* *---------------------------------------------------------------------- * * Tcl_AlertNotifier -- * * Wake up the specified notifier from any thread. This routine * is called by the platform independent notifier code whenever * the Tcl_ThreadAlert routine is called. This routine is * guaranteed not to be called on a given notifier after * Tcl_FinalizeNotifier is called for that notifier. * * Results: * None. * * Side effects: * Calls YieldToThread from this thread. * *---------------------------------------------------------------------- */ void Tcl_AlertNotifier(clientData) ClientData clientData; /* Pointer to thread data. */ { #ifdef TCL_THREADS if (TclMacHaveThreads()) { YieldToThread((ThreadID) clientData); } #endif } /* *---------------------------------------------------------------------- * * InitNotifier -- * * Initializes the notifier structure. Note - this function is never * used. * * Results: * None. * * Side effects: * Creates a new exit handler. * *---------------------------------------------------------------------- */ static void InitNotifier(void) { initialized = 1; memset(¬ifier, 0, sizeof(notifier)); Tcl_CreateExitHandler(NotifierExitHandler, NULL); } /* *---------------------------------------------------------------------- * * NotifierExitHandler -- * * This function is called to cleanup the notifier state before * Tcl is unloaded. This function is never used, since InitNotifier * isn't either. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void NotifierExitHandler( ClientData clientData) /* Not used. */ { initialized = 0; } /* *---------------------------------------------------------------------- * * HandleMacEvents -- * * This function checks for events from the Macintosh event queue. * * Results: * Returns 1 if event found, 0 otherwise. * * Side effects: * Pulls events off of the Mac event queue and then calls * convertEventProc. * *---------------------------------------------------------------------- */ static int HandleMacEvents(void) { EventRecord theEvent; int eventFound = 0, needsUpdate = 0; Point currentMouse; WindowRef windowRef; Rect mouseRect; /* * Check for mouse moved events. These events aren't placed on the * system event queue unless we call WaitNextEvent. */ GetGlobalMouseTcl(¤tMouse); if ((notifier.eventProcPtr != NULL) && !EqualPt(currentMouse, notifier.lastMousePosition)) { notifier.lastMousePosition = currentMouse; theEvent.what = nullEvent; if ((*notifier.eventProcPtr)(&theEvent) == true) { eventFound = 1; } } /* * Check for update events. Since update events aren't generated * until we call GetNextEvent, we may need to force a call to * GetNextEvent, even if the queue is empty. */ for (windowRef = FrontWindow(); windowRef != NULL; windowRef = GetNextWindow(windowRef)) { GetWindowUpdateRgn(windowRef, notifier.utilityRgn); if (!EmptyRgn(notifier.utilityRgn)) { needsUpdate = 1; break; } } /* * Process events from the OS event queue. */ while (needsUpdate || (GetEvQHdr()->qHead != NULL)) { GetGlobalMouseTcl(¤tMouse); SetRect(&mouseRect, currentMouse.h, currentMouse.v, currentMouse.h + 1, currentMouse.v + 1); RectRgn(notifier.utilityRgn, &mouseRect); WaitNextEvent(everyEvent, &theEvent, 5, notifier.utilityRgn); needsUpdate = 0; if ((notifier.eventProcPtr != NULL) && ((*notifier.eventProcPtr)(&theEvent) == true)) { eventFound = 1; } } return eventFound; } /* *---------------------------------------------------------------------- * * Tcl_SetTimer -- * * This procedure sets the current notifier timer value. The * notifier will ensure that Tcl_ServiceAll() is called after * the specified interval, even if no events have occurred. * * Results: * None. * * Side effects: * Replaces any previous timer. * *---------------------------------------------------------------------- */ void Tcl_SetTimer( Tcl_Time *timePtr) /* New value for interval timer. */ { /* * Allow the notifier to be hooked. This may not make sense * on the Mac, but mirrors the UNIX hook. */ if (tclStubs.tcl_SetTimer != tclOriginalNotifier.setTimerProc) { tclStubs.tcl_SetTimer(timePtr); return; } if (!timePtr) { notifier.timerActive = 0; } else { /* * Compute when the timer should fire. */ Tcl_GetTime(¬ifier.timer); notifier.timer.sec += timePtr->sec; notifier.timer.usec += timePtr->usec; if (notifier.timer.usec >= 1000000) { notifier.timer.usec -= 1000000; notifier.timer.sec += 1; } notifier.timerActive = 1; } } /* *---------------------------------------------------------------------- * * Tcl_ServiceModeHook -- * * This function is invoked whenever the service mode changes. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Tcl_ServiceModeHook(mode) int mode; /* Either TCL_SERVICE_ALL, or * TCL_SERVICE_NONE. */ { } /* *---------------------------------------------------------------------- * * Tcl_WaitForEvent -- * * This function is called by Tcl_DoOneEvent to wait for new * events on the message queue. If the block time is 0, then * Tcl_WaitForEvent just polls the event queue without blocking. * * Results: * Always returns 0. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_WaitForEvent( Tcl_Time *timePtr) /* Maximum block time. */ { int found; EventRecord macEvent; long sleepTime = 5; long ms; Point currentMouse; void * timerToken; Rect mouseRect; /* * Allow the notifier to be hooked. This may not make * sense on the Mac, but mirrors the UNIX hook. */ if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) { return tclStubs.tcl_WaitForEvent(timePtr); } /* * Compute the next timeout value. */ if (!timePtr) { ms = INT_MAX; } else { ms = (timePtr->sec * 1000) + (timePtr->usec / 1000); } timerToken = TclMacStartTimer((long) ms); /* * Poll the Mac event sources. This loop repeats until something * happens: a timeout, a socket event, mouse motion, or some other * window event. Note that we don't call WaitNextEvent if another * event is found to avoid context switches. This effectively gives * events coming in via WaitNextEvent a slightly lower priority. */ found = 0; if (notifier.utilityRgn == NULL) { notifier.utilityRgn = NewRgn(); } while (!found) { /* * Check for generated and queued events. */ if (HandleMacEvents()) { found = 1; } /* * Check for time out. */ if (!found && TclMacTimerExpired(timerToken)) { found = 1; } /* * Check for window events. We may receive a NULL event for * various reasons. 1) the timer has expired, 2) a mouse moved * event is occuring or 3) the os is giving us time for idle * events. Note that we aren't sharing the processor very * well here. We really ought to do a better job of calling * WaitNextEvent for time slicing purposes. */ if (!found) { /* * Set up mouse region so we will wake if the mouse is moved. * We do this by defining the smallest possible region around * the current mouse position. */ GetGlobalMouseTcl(¤tMouse); SetRect(&mouseRect, currentMouse.h, currentMouse.v, currentMouse.h + 1, currentMouse.v + 1); RectRgn(notifier.utilityRgn, &mouseRect); WaitNextEvent(everyEvent, &macEvent, sleepTime, notifier.utilityRgn); if (notifier.eventProcPtr != NULL) { if ((*notifier.eventProcPtr)(&macEvent) == true) { found = 1; } } } } TclMacRemoveTimer(timerToken); /* * Yield time to nay other thread at this point. If we find that the * apps thrash too switching between threads, we can put a timer here, * and only yield when the timer fires. */ if (TclMacHaveThreads()) { YieldToAnyThread(); } return 0; } /* *---------------------------------------------------------------------- * * Tcl_Sleep -- * * Delay execution for the specified number of milliseconds. This * is not a very good call to make. It will block the system - * you will not even be able to switch applications. * * Results: * None. * * Side effects: * Time passes. * *---------------------------------------------------------------------- */ void Tcl_Sleep( int ms) /* Number of milliseconds to sleep. */ { EventRecord dummy; void *timerToken; if (ms <= 0) { return; } timerToken = TclMacStartTimer((long) ms); while (1) { WaitNextEvent(0, &dummy, (ms / 16.66) + 1, NULL); if (TclMacHaveThreads()) { YieldToAnyThread(); } if (TclMacTimerExpired(timerToken)) { break; } } TclMacRemoveTimer(timerToken); } /* *---------------------------------------------------------------------- * * Tcl_MacSetEventProc -- * * This function sets the event handling procedure for the * application. This function will be passed all incoming Mac * events. This function usually controls the console or some * other entity like Tk. * * Results: * None. * * Side effects: * Changes the event handling function. * *---------------------------------------------------------------------- */ void Tcl_MacSetEventProc( Tcl_MacConvertEventPtr procPtr) { notifier.eventProcPtr = procPtr; }