/* * tkMacOSXCarbonEvents.c -- * * This file implements functions that register for and handle * various Carbon Events and Timers. Most carbon events of interest * to TkAqua are processed in a handler registered on the dispatcher * event target so that we get first crack at them before HIToolbox * dispatchers/processes them further. * As some events are sent directly to the focus or app event target * and not dispatched normally, we also register a handler on the * application event target. * * Copyright 2001, Apple Computer, Inc. * Copyright (c) 2005-2007 Daniel A. Steffen * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * * The following terms apply to all files originating from Apple * Computer, Inc. ("Apple") and associated with the software * unless explicitly disclaimed in individual files. * * * Apple hereby grants permission to use, copy, modify, * distribute, and license this software and its documentation * for any purpose, provided that existing copyright notices are * retained in all copies and that this notice is included * verbatim in any distributions. No written agreement, license, * or royalty fee is required for any of the authorized * uses. Modifications to this software may be copyrighted by * their authors and need not follow the licensing terms * described here, provided that the new terms are clearly * indicated on the first page of each file where they apply. * * * IN NO EVENT SHALL APPLE, THE AUTHORS OR DISTRIBUTORS OF THE * SOFTWARE BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, * INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF * THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, * EVEN IF APPLE OR THE AUTHORS HAVE BEEN ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. APPLE, THE AUTHORS AND * DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS * SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND APPLE,THE * AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * * GOVERNMENT USE: If you are acquiring this software on behalf * of the U.S. government, the Government shall have only * "Restricted Rights" in the software and related documentation * as defined in the Federal Acquisition Regulations (FARs) in * Clause 52.227.19 (c) (2). If you are acquiring the software * on behalf of the Department of Defense, the software shall be * classified as "Commercial Computer Software" and the * Government shall have only "Restricted Rights" as defined in * Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the * foregoing, the authors grant the U.S. Government and others * acting in its behalf permission to use and distribute the * software in accordance with the terms specified in this * license. * * RCS: @(#) $Id: tkMacOSXCarbonEvents.c,v 1.3.2.18 2007/11/09 06:26:54 das Exp $ */ #include "tkMacOSXPrivate.h" #include "tkMacOSXEvent.h" #include "tkMacOSXDebug.h" /* #ifdef TK_MAC_DEBUG #define TK_MAC_DEBUG_CARBON_EVENTS #endif */ /* * Declarations of functions used only in this file: */ static OSStatus CarbonEventHandlerProc(EventHandlerCallRef callRef, EventRef event, void *userData); static OSStatus InstallStandardApplicationEventHandler(void); static void CarbonTimerProc(EventLoopTimerRef timer, void *userData); /* * Static data used by several functions in this file: */ static EventLoopTimerRef carbonTimer = NULL; static int carbonTimerEnabled = 0; static EventHandlerUPP carbonEventHandlerUPP = NULL; static Tcl_Interp *carbonEventInterp = NULL; static int inTrackingLoop = 0; #if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 /* * For InstallStandardApplicationEventHandler(): */ static jmp_buf exitRaelJmpBuf; static void ExitRaelEventHandlerProc(EventHandlerCallRef callRef, EventRef event, void *userData) __attribute__ ((__noreturn__)); #endif /* *---------------------------------------------------------------------- * * CarbonEventHandlerProc -- * * This procedure is the handler for all registered CarbonEvents. * * Results: * OS status code. * * Side effects: * Dispatches CarbonEvents. * *---------------------------------------------------------------------- */ static OSStatus CarbonEventHandlerProc( EventHandlerCallRef callRef, EventRef event, void *userData) { OSStatus err = eventNotHandledErr; TkMacOSXEvent macEvent; MacEventStatus eventStatus; macEvent.eventRef = event; macEvent.eClass = GetEventClass(event); macEvent.eKind = GetEventKind(event); macEvent.interp = (Tcl_Interp *) userData; macEvent.callRef = callRef; bzero(&eventStatus, sizeof(eventStatus)); #ifdef TK_MAC_DEBUG_CARBON_EVENTS if (!(macEvent.eClass == kEventClassMouse && ( macEvent.eKind == kEventMouseMoved || macEvent.eKind == kEventMouseDragged))) { TkMacOSXDbgMsg("Started handling %s", TkMacOSXCarbonEventToAscii(event)); TkMacOSXInitNamedDebugSymbol(HIToolbox, void, _DebugPrintEvent, EventRef inEvent); if (_DebugPrintEvent) { /* * Carbon-internal event debugging (c.f. Technote 2124) */ _DebugPrintEvent(event); } } #endif /* TK_MAC_DEBUG_CARBON_EVENTS */ TkMacOSXProcessEvent(&macEvent,&eventStatus); if (eventStatus.stopProcessing) { err = noErr; } #ifdef TK_MAC_DEBUG_CARBON_EVENTS if (macEvent.eKind != kEventMouseMoved && macEvent.eKind != kEventMouseDragged) { TkMacOSXDbgMsg("Finished handling %s: %s handled", TkMacOSXCarbonEventToAscii(event), eventStatus.stopProcessing ? " " : "not"); } #endif /* TK_MAC_DEBUG_CARBON_EVENTS */ return err; } /* *---------------------------------------------------------------------- * * TkMacOSXInitCarbonEvents -- * * This procedure initializes all CarbonEvent handlers. * * Results: * None. * * Side effects: * Handlers for Carbon Events are registered. * *---------------------------------------------------------------------- */ MODULE_SCOPE void TkMacOSXInitCarbonEvents( Tcl_Interp *interp) { const EventTypeSpec dispatcherEventTypes[] = { {kEventClassKeyboard, kEventRawKeyDown}, {kEventClassKeyboard, kEventRawKeyRepeat}, {kEventClassKeyboard, kEventRawKeyUp}, {kEventClassKeyboard, kEventRawKeyModifiersChanged}, {kEventClassKeyboard, kEventRawKeyRepeat}, }; const EventTypeSpec applicationEventTypes[] = { {kEventClassMenu, kEventMenuBeginTracking}, {kEventClassMenu, kEventMenuEndTracking}, {kEventClassMenu, kEventMenuOpening}, {kEventClassMenu, kEventMenuTargetItem}, {kEventClassCommand, kEventCommandProcess}, {kEventClassCommand, kEventCommandUpdateStatus}, {kEventClassApplication, kEventAppActivated}, {kEventClassApplication, kEventAppDeactivated}, {kEventClassApplication, kEventAppQuit}, {kEventClassApplication, kEventAppHidden}, {kEventClassApplication, kEventAppShown}, {kEventClassApplication, kEventAppAvailableWindowBoundsChanged}, {kEventClassAppearance, kEventAppearanceScrollBarVariantChanged}, }; carbonEventHandlerUPP = NewEventHandlerUPP(CarbonEventHandlerProc); carbonEventInterp = interp; ChkErr(InstallStandardApplicationEventHandler); ChkErr(InstallEventHandler, GetEventDispatcherTarget(), carbonEventHandlerUPP, GetEventTypeCount(dispatcherEventTypes), dispatcherEventTypes, (void *) carbonEventInterp, NULL); ChkErr(InstallEventHandler, GetApplicationEventTarget(), carbonEventHandlerUPP, GetEventTypeCount(applicationEventTypes), applicationEventTypes, (void *) carbonEventInterp, NULL); #ifdef TK_MAC_DEBUG_CARBON_EVENTS TkMacOSXInitNamedDebugSymbol(HIToolbox, void, _TraceEventByName, CFStringRef); if (_TraceEventByName) { /* Carbon-internal event debugging (c.f. Technote 2124) */ _TraceEventByName(CFSTR("kEventRawKeyDown")); _TraceEventByName(CFSTR("kEventRawKeyRepeat")); _TraceEventByName(CFSTR("kEventRawKeyUp")); _TraceEventByName(CFSTR("kEventRawKeyModifiersChanged")); _TraceEventByName(CFSTR("kEventRawKeyRepeat")); _TraceEventByName(CFSTR("kEventMenuBeginTracking")); _TraceEventByName(CFSTR("kEventMenuEndTracking")); _TraceEventByName(CFSTR("kEventMenuOpening")); _TraceEventByName(CFSTR("kEventMenuTargetItem")); _TraceEventByName(CFSTR("kEventCommandProcess")); _TraceEventByName(CFSTR("kEventCommandUpdateStatus")); _TraceEventByName(CFSTR("kEventAppActivated")); _TraceEventByName(CFSTR("kEventAppDeactivated")); _TraceEventByName(CFSTR("kEventAppQuit")); _TraceEventByName(CFSTR("kEventAppHidden")); _TraceEventByName(CFSTR("kEventAppShown")); _TraceEventByName(CFSTR("kEventAppAvailableWindowBoundsChanged")); _TraceEventByName(CFSTR("kEventAppearanceScrollBarVariantChanged")); _TraceEventByName(CFSTR("kEventMouseDown")); _TraceEventByName(CFSTR("kEventMouseUp")); #if 0 _TraceEventByName(CFSTR("kEventMouseMoved")); _TraceEventByName(CFSTR("kEventMouseDragged")); #endif _TraceEventByName(CFSTR("kEventMouseWheelMoved")); _TraceEventByName(CFSTR("kEventMouseScroll")); _TraceEventByName(CFSTR("kEventWindowActivated")); _TraceEventByName(CFSTR("kEventWindowDeactivated")); _TraceEventByName(CFSTR("kEventWindowUpdate")); _TraceEventByName(CFSTR("kEventWindowExpanding")); _TraceEventByName(CFSTR("kEventWindowBoundsChanged")); _TraceEventByName(CFSTR("kEventWindowDragStarted")); _TraceEventByName(CFSTR("kEventWindowDragCompleted")); _TraceEventByName(CFSTR("kEventWindowConstrain")); _TraceEventByName(CFSTR("kEventWindowGetRegion")); _TraceEventByName(CFSTR("kEventWindowDrawContent")); } #endif /* TK_MAC_DEBUG_CARBON_EVENTS */ } /* *---------------------------------------------------------------------- * * TkMacOSXInstallWindowCarbonEventHandler -- * * This procedure installs our window CarbonEvent handler. * * Results: * None. * * Side effects: * Handler for Carbon Events is registered. * *---------------------------------------------------------------------- */ MODULE_SCOPE void TkMacOSXInstallWindowCarbonEventHandler( Tcl_Interp *interp, WindowRef window) { const EventTypeSpec windowEventTypes[] = { {kEventClassMouse, kEventMouseDown}, {kEventClassMouse, kEventMouseUp}, {kEventClassMouse, kEventMouseMoved}, {kEventClassMouse, kEventMouseDragged}, {kEventClassMouse, kEventMouseWheelMoved}, {kEventClassWindow, kEventWindowActivated}, {kEventClassWindow, kEventWindowDeactivated}, {kEventClassWindow, kEventWindowUpdate}, {kEventClassWindow, kEventWindowExpanding}, {kEventClassWindow, kEventWindowBoundsChanged}, {kEventClassWindow, kEventWindowDragStarted}, {kEventClassWindow, kEventWindowDragCompleted}, {kEventClassWindow, kEventWindowConstrain}, {kEventClassWindow, kEventWindowGetRegion}, {kEventClassWindow, kEventWindowDrawContent}, }; ChkErr(InstallEventHandler, GetWindowEventTarget(window), carbonEventHandlerUPP, GetEventTypeCount(windowEventTypes), windowEventTypes, (void *) (interp ? interp : carbonEventInterp), NULL); } /* *---------------------------------------------------------------------- * * InstallStandardApplicationEventHandler -- * * This procedure installs the carbon standard application event * handler. * * Results: * OS status code. * * Side effects: * Standard handlers for application Carbon Events are registered. * *---------------------------------------------------------------------- */ static OSStatus InstallStandardApplicationEventHandler(void) { OSStatus err = memFullErr; TK_IF_HI_TOOLBOX(5, /* * The approach below does not work correctly in Leopard, it leads to * crashes in [NSView unlockFocus] whenever HIToolbox uses Cocoa (Help * menu, Nav Services, Color Picker). While it is now possible to * install the standard app handler with InstallStandardEventHandler(), * to fully replicate RAEL the standard menubar event handler also needs * to be installed. Unfortunately there appears to be no public API to * obtain the menubar event target. As a workaround, for now we resort * to calling the HIToolbox-internal GetMenuBarEventTarget() directly * (symbol acquired via TkMacOSXInitNamedDebugSymbol() from HIToolbox * version 343, may not exist in later versions). */ err = ChkErr(InstallStandardEventHandler, GetApplicationEventTarget()); TkMacOSXInitNamedDebugSymbol(HIToolbox, EventTargetRef, GetMenuBarEventTarget, void); if (GetMenuBarEventTarget) { ChkErr(InstallStandardEventHandler, GetMenuBarEventTarget()); } else { TkMacOSXDbgMsg("Unable to install standard menubar event handler"); } ) TK_ELSE_HI_TOOLBOX (5, /* * This is a hack to workaround missing Carbon API to install the * standard application event handler (InstallStandardEventHandler() * does not work on the application target). The only way to install the * standard app handler is to call RunApplicationEventLoop(), but since * we are running our own event loop, we'll immediately need to break * out of RAEL again: we do this via longjmp out of the * ExitRaelEventHandlerProc event handler called first off from RAEL by * posting a high priority dummy event. This workaround is derived from * a similar approach in Technical Q&A 1061. */ enum { kExitRaelEvent = 'ExiT' }; const EventTypeSpec exitRaelEventType = { kExitRaelEvent, kExitRaelEvent }; EventHandlerUPP exitRaelEventHandler; EventHandlerRef exitRaelEventHandlerRef = NULL; EventRef exitRaelEvent = NULL; exitRaelEventHandler = NewEventHandlerUPP( (EventHandlerProcPtr) ExitRaelEventHandlerProc); if (exitRaelEventHandler) { err = ChkErr(InstallEventHandler, GetEventDispatcherTarget(), exitRaelEventHandler, 1, &exitRaelEventType, NULL, &exitRaelEventHandlerRef); } if (err == noErr) { err = ChkErr(CreateEvent, NULL, kExitRaelEvent, kExitRaelEvent, GetCurrentEventTime(), kEventAttributeNone, &exitRaelEvent); } if (err == noErr) { err = ChkErr(PostEventToQueue, GetMainEventQueue(), exitRaelEvent, kEventPriorityHigh); } if (err == noErr) { if (!setjmp(exitRaelJmpBuf)) { RunApplicationEventLoop(); /* * This point should never be reached! */ Tcl_Panic("RunApplicationEventLoop exited !"); } } if (exitRaelEvent) { ReleaseEvent(exitRaelEvent); } if (exitRaelEventHandlerRef) { RemoveEventHandler(exitRaelEventHandlerRef); } if (exitRaelEventHandler) { DisposeEventHandlerUPP(exitRaelEventHandler); } ) TK_ENDIF return err; } #if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 /* *---------------------------------------------------------------------- * * ExitRaelEventHandlerProc -- * * This procedure is the dummy event handler used to break out of * RAEL via longjmp, it is called as the first ever event handler * in RAEL by posting a high priority dummy event. * * Results: * None. Never returns ! * * Side effects: * longjmp back to InstallStandardApplicationEventHandler(). * *---------------------------------------------------------------------- */ static void ExitRaelEventHandlerProc( EventHandlerCallRef callRef, EventRef event, void *userData) { longjmp(exitRaelJmpBuf, 1); } #endif /* *---------------------------------------------------------------------- * * TkMacOSXRunTclEventLoop -- * * Process a limited number of tcl events. * * Results: * Returns 1 if events were handled and 0 otherwise. * * Side effects: * Runs the Tcl event loop. * *---------------------------------------------------------------------- */ MODULE_SCOPE int TkMacOSXRunTclEventLoop(void) { int i = 4, result = 0; /* Avoid starving main event loop: process at most 4 events. */ while(--i && Tcl_ServiceAll()) { result = 1; } return result; } /* *---------------------------------------------------------------------- * * CarbonTimerProc -- * * This procedure is the carbon timer handler that runs the tcl * event loop periodically. * * Results: * None. * * Side effects: * Runs the Tcl event loop. * *---------------------------------------------------------------------- */ static void CarbonTimerProc( EventLoopTimerRef timer, void *userData) { if(carbonTimerEnabled > 0 && TkMacOSXRunTclEventLoop()) { #ifdef TK_MAC_DEBUG_CARBON_EVENTS TkMacOSXDbgMsg("Processed tcl events from carbon timer"); #endif /* TK_MAC_DEBUG_CARBON_EVENTS */ } } /* *---------------------------------------------------------------------- * * TkMacOSXStartTclEventLoopCarbonTimer -- * * This procedure installs (if necessary) and starts a carbon * event timer that runs the tcl event loop periodically. * It should be called whenever a nested carbon event loop might * run by HIToolbox (e.g. during mouse tracking) to ensure that * tcl events continue to be processed. * * Results: * OS status code. * * Side effects: * Carbon event timer is installed and started. * *---------------------------------------------------------------------- */ MODULE_SCOPE OSStatus TkMacOSXStartTclEventLoopCarbonTimer(void) { OSStatus err = noErr; if (++carbonTimerEnabled > 0) { if(!carbonTimer) { EventLoopTimerUPP timerUPP = NewEventLoopTimerUPP(CarbonTimerProc); err = ChkErr(InstallEventLoopTimer, GetMainEventLoop(), 5 * kEventDurationMillisecond, 5 * kEventDurationMillisecond, timerUPP, NULL, &carbonTimer); } else { err = ChkErr(SetEventLoopTimerNextFireTime, carbonTimer, 5 * kEventDurationMillisecond); } } return err; } /* *---------------------------------------------------------------------- * * TkMacOSXStopTclEventLoopCarbonTimer -- * * This procedure stops the carbon event timer started by * TkMacOSXStartTclEventLoopCarbonTimer(). * * Results: * OS status code. * * Side effects: * Carbon event timer is stopped. * *---------------------------------------------------------------------- */ MODULE_SCOPE OSStatus TkMacOSXStopTclEventLoopCarbonTimer(void) { OSStatus err = noErr; if (--carbonTimerEnabled == 0) { if(carbonTimer) { err = ChkErr(SetEventLoopTimerNextFireTime, carbonTimer, kEventDurationForever); } } return err; } /* *---------------------------------------------------------------------- * * TkMacOSXTrackingLoop -- * * Call with 1 before entering a mouse tracking loop (e.g. window * resizing or menu tracking) to enable tcl event processing but * disable carbon event processing (except for update events) * during the loop, and with 0 after exiting the loop to reset. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ MODULE_SCOPE void TkMacOSXTrackingLoop(int tracking) { static int previousServiceMode = TCL_SERVICE_NONE; if (tracking) { inTrackingLoop++; previousServiceMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); TkMacOSXStartTclEventLoopCarbonTimer(); #ifdef TK_MAC_DEBUG_CARBON_EVENTS TkMacOSXDbgMsg("Entering tracking loop"); #endif /* TK_MAC_DEBUG_CARBON_EVENTS */ } else { TkMacOSXStopTclEventLoopCarbonTimer(); previousServiceMode = Tcl_SetServiceMode(previousServiceMode); inTrackingLoop--; #ifdef TK_MAC_DEBUG_CARBON_EVENTS TkMacOSXDbgMsg("Exiting tracking loop"); #endif /* TK_MAC_DEBUG_CARBON_EVENTS */ } } /* *---------------------------------------------------------------------- * * TkMacOSXReceiveAndDispatchEvent -- * * This receives a carbon event and sends it to the carbon event * dispatcher. * * Results: * Mac OS status * * Side effects: * This receives and dispatches the next Carbon event. * *---------------------------------------------------------------------- */ MODULE_SCOPE OSStatus TkMacOSXReceiveAndDispatchEvent(void) { static EventTargetRef targetRef = NULL; int numEventTypes = 0; const EventTypeSpec *eventTypes = NULL; EventRef eventRef; OSStatus err; const EventTypeSpec trackingEventTypes[] = { {'dniw', kEventWindowUpdate}, {kEventClassWindow, kEventWindowUpdate}, }; if (inTrackingLoop > 0) { eventTypes = trackingEventTypes; numEventTypes = GetEventTypeCount(trackingEventTypes); } /* * This is a poll, since we have already counted the events coming * into this routine, and are guaranteed to have one waiting. */ err = ReceiveNextEvent(numEventTypes, eventTypes, kEventDurationNoWait, true, &eventRef); if (err == noErr) { #ifdef TK_MAC_DEBUG_CARBON_EVENTS UInt32 kind = GetEventKind(eventRef); if (kind != kEventMouseMoved && kind != kEventMouseDragged) { TkMacOSXDbgMsg("Dispatching %s", TkMacOSXCarbonEventToAscii(eventRef)); TkMacOSXInitNamedDebugSymbol(HIToolbox, void, _DebugPrintEvent, EventRef inEvent); if (_DebugPrintEvent) { /* Carbon-internal event debugging (c.f. Technote 2124) */ _DebugPrintEvent(eventRef); } } #endif /* TK_MAC_DEBUG_CARBON_EVENTS */ if (!targetRef) { targetRef = GetEventDispatcherTarget(); } TkMacOSXStartTclEventLoopCarbonTimer(); err = SendEventToEventTarget(eventRef, targetRef); TkMacOSXStopTclEventLoopCarbonTimer(); if (err != noErr && err != eventLoopTimedOutErr && err != eventNotHandledErr) { TkMacOSXDbgMsg("SendEventToEventTarget(%s) failed: %ld", TkMacOSXCarbonEventToAscii(eventRef), err); } ReleaseEvent(eventRef); } else if (err != eventLoopTimedOutErr) { TkMacOSXDbgMsg("ReceiveNextEvent failed: %ld", err); } return err; }