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