From 2e8516354aacef064d01425808da06d2cdcb4791 Mon Sep 17 00:00:00 2001 From: "thomasvl@gmail.com" Date: Fri, 12 Dec 2008 15:24:34 +0000 Subject: - GTMStackTrace works on 10.5+ (and iPhone) using NSThread to build the call stack. - Added GTM_EXTERN that makes it easier to mix and match objc and objc++ code. - Added GTMHotKeysTextField for display and editing of hot key settings. - Added GTMCarbonEvent for dealing with Carbon Events and HotKeys in a ObjC like way. - Backported the Atomic Barrier Swap functions for Objective C back to Tiger. - Added a variety of new functions to GTMUnitTestingUtilities for checking if the screensaver is in the way, waiting on user events, and generating keystrokes. - If you are using any Carbon routines that log (DebugStr, AssertMacros.h) and use GTMUnitTestDevLog, the log routines now go through _GTMDevLog so that they can be caught in GTMUnitTestDevLog and verified like any _GTMDevLog calls you may make. For an example of this in action see GTMCarbonEventTest.m. - Added GTMFileSystemKQueue. It provides a simple wrapper for kqueuing something in the file system and tracking changes to it. - RunIPhoneUnitTest.sh now cleans up the user home directory and creates a documents directory within it, used when requesting a NSDocumentDirectory. - Added GTMNSFileManager+Carbon which contains routines for path <-> Alias conversion and path <-> FSRef conversion. - Added GTMNSArray+Merge for merging one array into another with or without a custom merging function, returning a new array with the merged contents. --- AppKit/GTMCarbonEvent.m | 709 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 709 insertions(+) create mode 100644 AppKit/GTMCarbonEvent.m (limited to 'AppKit/GTMCarbonEvent.m') diff --git a/AppKit/GTMCarbonEvent.m b/AppKit/GTMCarbonEvent.m new file mode 100644 index 0000000..270a078 --- /dev/null +++ b/AppKit/GTMCarbonEvent.m @@ -0,0 +1,709 @@ +// +// GTMCarbonEvent.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMCarbonEvent.h" +#import "GTMObjectSingleton.h" +#import "GTMDebugSelectorValidation.h" + +// Wrapper for all the info we need about a hotkey that we can store in a +// Foundation storage class. We expecct selector to have this signature: +// - (void)hitHotKey:sender; +@interface GTMCarbonHotKey : NSObject { + @private + EventHotKeyID id_; // EventHotKeyID for this hotkey. + id target_; // Object we are going to call when the hotkey is hit + SEL selector_; // Selector we are going to call on target_ + BOOL onKeyDown_; // Do we do it on key down or on key up? +} + +// Create a HotKey record +// Arguments: +// keyID - id of the hotkey +// target - object we are going to call when the hotkey is hit +// action - selector we are going to call on target +// whenPressed - do we do it on key down or key up? +// Returns: +// a hotkey record, or nil on failure +- (id)initWithHotKey:(EventHotKeyID)keyID + target:(id)target + action:(SEL)selector + whenPressed:(BOOL)onKeyDown; + +// Does this record match key |keyID| +// Arguments: +// keyID - the id to match against +// Returns: +// Yes if we match this key id +- (BOOL)matchesHotKeyID:(EventHotKeyID)keyID; + +// Make target perform selector +// Returns: +// Yes if handled +- (BOOL)sendAction:(id)sender; + +// Do we do it on key down or key up? +// Returns: +// Yes if on keydown +- (BOOL)onKeyDown; + +@end + +@implementation GTMCarbonEvent + +// Create an event of class |inClass| and kind |inKind| +// +// Returns: +// Autoreleased GTMCarbonEvent +// ++ (id)eventWithClass:(UInt32)inClass kind:(UInt32)kind { + return [[[[self class] alloc] initWithClass:inClass kind:kind] autorelease]; +} + + +// Create an event based on |event|. Retains |event|. +// +// Returns: +// Autoreleased GTMCarbonEvent +// ++ (id)eventWithEvent:(EventRef)event { + return [[[[self class] alloc] initWithEvent:event] autorelease]; +} + + +// Create an event based on the event currently being handled. +// +// Returns: +// Autoreleased GTMCarbonEvent +// ++ (id)currentEvent { + return [[self class] eventWithEvent:GetCurrentEvent()]; +} + + +// Create an event of class |inClass| and kind |inKind| +// +// Returns: +// GTMCarbonEvent +// +- (id)initWithClass:(UInt32)inClass kind:(UInt32)kind { + if ((self = [super init])) { + verify_noerr(CreateEvent(kCFAllocatorDefault, inClass, kind, + 0, kEventAttributeNone, &event_)); + } + return self; +} + + +// Create an event based on |event|. Retains |event|. +// +// Returns: +// GTMCarbonEvent +// +- (id)initWithEvent:(EventRef)event { + if ((self = [super init])) { + if (event) { + event_ = RetainEvent(event); + } + } + return self; +} + + +// This does a proper event copy, but ignores the |zone|. No way to do a copy +// of an event into a specific zone. +// +// Arguments: +// zone - the zone to copy to +// Returns: +// the copied event. nil on failure +- (id)copyWithZone:(NSZone *)zone { + GTMCarbonEvent *carbonEvent = nil; + EventRef newEvent = CopyEvent([self event]); + if (newEvent) { + carbonEvent = [[[self class] allocWithZone:zone] initWithEvent:newEvent]; + ReleaseEvent(newEvent); + } + return carbonEvent; +} + +- (void)finalize { + if (event_) { + ReleaseEvent(event_); + event_ = NULL; + } + [super finalize]; +} + +// releases our retained event +// +- (void)dealloc { + if (event_) { + ReleaseEvent(event_); + event_ = NULL; + } + [super dealloc]; +} + +// description utliity for debugging +// +- (NSString *)description { + char cls[5]; + UInt32 kind; + + // Need everything bigendian if we are printing out the class as a "string" + *((UInt32 *)cls) = CFSwapInt32HostToBig([self eventClass]); + kind = [self eventKind]; + cls[4] = 0; + return [NSString stringWithFormat:@"GTMCarbonEvent '%s' %d", cls, kind]; +} + + +// Get the event's class. +// +// Returns: +// event class +// +- (UInt32)eventClass { + return GetEventClass(event_); +} + + +// Get the event's kind. +// +// Returns: +// event kind +// +- (UInt32)eventKind { + return GetEventKind(event_); +} + + +// Set the event's time. +// +// Arguments: +// time - the time you want associated with the event +// +- (void)setTime:(EventTime)eventTime { + verify_noerr(SetEventTime(event_, eventTime)); +} + + +// Get the event's time. +// +// Returns: +// the time associated with the event +// +- (EventTime)time { + return GetEventTime(event_); +} + + +// Get the event's eventref for passing to other carbon functions. +// +// Returns: +// the event ref associated with the event +// +- (EventRef)event { + return event_; +} + + +// Sends event to an event target with options +// +// Arguments: +// target - target to send event to. +// options - options to send event. See SendEventToEventTargetWithOptions +// for details. +// +// Returns: +// OSStatus value. +// +- (OSStatus)sendToTarget:(GTMCarbonEventHandler *)target + options:(OptionBits)options { + return SendEventToEventTargetWithOptions(event_, + [target eventTarget], options); +} + +// Post event to an event queue. +// +// Arguments: +// queue - queue to post it to. +// priority - priority to post it with +// +// Returns: +// OSStatus value. +// +- (OSStatus)postToQueue:(EventQueueRef)queue priority:(EventPriority)priority { + return PostEventToQueue(queue, event_, priority); +} + + +// Post event to current queue with standard priority. +// +- (void)postToCurrentQueue { + verify_noerr([self postToQueue:GetCurrentEventQueue() + priority:kEventPriorityStandard]); +} + + +// Post event to main queue with standard priority. +// +- (void)postToMainQueue { + verify_noerr([self postToQueue:GetMainEventQueue() + priority:kEventPriorityStandard]); +} + + +// Sets (or adds) a parameter to an event. Try not to use this function +// directly. Look at the PARAM_TEMPLATE_DECL/DEFN macros below. +// +// Arguments: +// name - the parameter name. +// type - the parameter type. +// size - the size of the data that |data| points to. +// data - pointer to the data you want to set the parameter to. +// +- (void)setParameterNamed:(EventParamName)name + type:(EventParamType)type + size:(ByteCount)size + data:(const void *)data { + verify_noerr(SetEventParameter(event_, name, type, size, data)); +} + + +// Gets a parameter from an event. Try not to use this function +// directly. Look at the PARAM_TEMPLATE_DECL/DEFN macros below. +// +// Arguments: +// name - the parameter name. +// type - the parameter type. +// size - the size of the data that |data| points to. +// data - pointer to the buffer that you want to fill with your data. +// +// Returns: +// YES is parameter is retrieved successfully. NO if parameter doesn't exist. +// +- (BOOL)getParameterNamed:(EventParamName)name + type:(EventParamType)type + size:(ByteCount)size + data:(void *)data { + OSStatus status = GetEventParameter(event_, name, type, + NULL, size, NULL, data); + return status == noErr; +} + + +// Gets a the size of a parameter from an event. +// +// Arguments: +// name - the parameter name. +// type - the parameter type. +// +// Returns: +// The size of the buffer required to hold the parameter. 0 if parameter +// doesn't exist. +// +- (ByteCount)sizeOfParameterNamed:(EventParamName)name + type:(EventParamType)type { + ByteCount size = 0; + verify_noerr(GetEventParameter(event_, name, type, NULL, 0, &size, NULL)); + return size; +} + +@end + +@implementation GTMCarbonEvent (GTMCarbonEventGettersAndSetters) +GTM_PARAM_TEMPLATE_DEFN(UInt32) +GTM_PARAM_TEMPLATE_DEFN(EventHotKeyID) +@end + +UInt32 GTMCocoaToCarbonKeyModifiers(NSUInteger inCocoaModifiers) { + UInt32 carbModifiers = 0; + if (inCocoaModifiers & NSAlphaShiftKeyMask) carbModifiers |= alphaLock; + if (inCocoaModifiers & NSShiftKeyMask) carbModifiers |= shiftKey; + if (inCocoaModifiers & NSControlKeyMask) carbModifiers |= controlKey; + if (inCocoaModifiers & NSAlternateKeyMask) carbModifiers |= optionKey; + if (inCocoaModifiers & NSCommandKeyMask) carbModifiers |= cmdKey; + return carbModifiers; +} + +NSUInteger GTMCarbonToCocoaKeyModifiers(UInt32 inCarbonModifiers) { + NSUInteger nsModifiers = 0; + if (inCarbonModifiers & alphaLock) nsModifiers |= NSAlphaShiftKeyMask; + if (inCarbonModifiers & shiftKey) nsModifiers |= NSShiftKeyMask; + if (inCarbonModifiers & controlKey) nsModifiers |= NSControlKeyMask; + if (inCarbonModifiers & optionKey) nsModifiers |= NSAlternateKeyMask; + if (inCarbonModifiers & cmdKey) nsModifiers |= NSCommandKeyMask; + return nsModifiers; +} + +const OSType kGTMCarbonFrameworkSignature = 'GTM '; + +@implementation GTMCarbonEventHandler + +// Does our delegate respond to eventHandler:receivedEvent:handler: +// +// Returns: +// YES if delegate responds to eventHandler:receivedEvent:handler: +- (BOOL) delegateRespondsToHandleEvent { + return delegateRespondsToHandleEvent_; +} + +// Registers the event handler to listen for |events|. +// +// Arguments: +// events - an array of EventTypeSpec. The events to register for. +// count - the number of EventTypeSpecs in events. +// +- (void)registerForEvents:(const EventTypeSpec *)events count:(size_t)count { + verify_noerr(AddEventTypesToHandler([self eventHandler], count, events)); +} + +// Causes the event handler to stop listening for |events|. +// +// Arguments: +// events - an array of EventTypeSpec. The events to register for. +// count - the number of EventTypeSpecs in events. +// +- (void)unregisterForEvents:(const EventTypeSpec *)events count:(size_t)count { + verify_noerr(RemoveEventTypesFromHandler([self eventHandler], count, events)); +} + +// To be overridden by subclasses to respond to events. All subclasses should +// call [super handleEvent:handler:] if they don't handle the event themselves. +// +// Arguments: +// event - the event to be handled +// handler - the call ref in case you want to call CallNextEventHandler +// in your method +// Returns: +// OSStatus - usually either noErr or eventNotHandledErr +// +- (OSStatus)handleEvent:(GTMCarbonEvent *)event + handler:(EventHandlerCallRef)handler { + OSStatus status = eventNotHandledErr; + require(event, CantUseParams); + require(handler, CantUseParams); + require([event event], CantUseParams); + status = CallNextEventHandler(handler, [event event]); +CantUseParams: + return status; +} + +// To be overridden by subclasses to return the event target for the class. +// GTMCarbonEventHandler's implementation returns NULL. +// +// Returns: +// The event target ref. +// +- (EventTargetRef)eventTarget { + // Defaults implementation needs to be overridden + return NULL; +} + +// C callback for our registered EventHandlerUPP. +// +// Arguments: +// inHandler - handler given to us from Carbon Event system +// inEvent - the event we are handling +// inUserData - refcon that we gave to the carbon event system. Is a +// GTMCarbonEventHandler in disguise. +// Returns: +// status of event handler +// +static OSStatus EventHandler(EventHandlerCallRef inHandler, + EventRef inEvent, + void *inUserData) { + GTMCarbonEvent *event = [GTMCarbonEvent eventWithEvent:inEvent]; + GTMCarbonEventHandler *handler= (GTMCarbonEventHandler *)inUserData; + check([handler isKindOfClass:[GTMCarbonEventHandler class]]); + + // First check to see if our delegate cares about this event. If the delegate + // handles it (i.e responds to it and does not return eventNotHandledErr) we + // do not pass it on to default handling. + OSStatus status = eventNotHandledErr; + if ([handler delegateRespondsToHandleEvent]) { + status = [[handler delegate] gtm_eventHandler:handler + receivedEvent:event + handler:inHandler]; + } + if (status == eventNotHandledErr) { + status = [handler handleEvent:event handler:inHandler]; + } + return status; +} + +// Gets the underlying EventHandlerRef for that this class wraps. +// +// Returns: +// The EventHandlerRef this class wraps. +// +- (EventHandlerRef)eventHandler { + if (!eventHandler_) { + static EventHandlerUPP sHandlerProc = NULL; + if ( sHandlerProc == NULL ) { + sHandlerProc = NewEventHandlerUPP(EventHandler); + } + verify_noerr(InstallEventHandler([self eventTarget], + sHandlerProc, 0, + NULL, self, &eventHandler_)); + } + return eventHandler_; +} + +// Gets the delegate for the handler +// +// Returns: +// the delegate +- (id)delegate { + return delegate_; +} + +// Sets the delegate for the handler and caches whether it responds to +// the eventHandler:receivedEvent:handler: selector for performance purposes. +// +// Arguments: +// delegate - the delegate for the handler +- (void)setDelegate:(id)delegate { + delegate_ = delegate; + SEL selector = @selector(gtm_eventHandler:receivedEvent:handler:); + delegateRespondsToHandleEvent_ = [delegate respondsToSelector:selector]; +} + +@end + +@implementation GTMCarbonEventMonitorHandler + +GTMOBJECT_SINGLETON_BOILERPLATE(GTMCarbonEventMonitorHandler, + sharedEventMonitorHandler); + +- (EventTargetRef)eventTarget { + return GetEventMonitorTarget(); +} + +@end + +@implementation GTMCarbonEventDispatcherHandler + +GTMOBJECT_SINGLETON_BOILERPLATE(GTMCarbonEventDispatcherHandler, + sharedEventDispatcherHandler); + +// Register for the events we handle, and set up the dictionaries we need +// to keep track of the hotkeys and commands that we handle. +// Returns: +// GTMCarbonApplication or nil on failure +- (id)init { + if ((self = [super init])) { + static EventTypeSpec events[] = { + { kEventClassKeyboard, kEventHotKeyPressed }, + { kEventClassKeyboard, kEventHotKeyReleased }, + }; + [self registerForEvents:events count:GetEventTypeCount(events)]; + hotkeys_ = [[NSMutableDictionary alloc] initWithCapacity:0]; + } + return self; +} + +// COV_NF_START +// Singleton, we never get released. Just here for completeness. +- (void)dealloc { + [hotkeys_ release]; + [super dealloc]; +} +// COV_NF_END + +- (EventTargetRef)eventTarget { + return GetEventDispatcherTarget(); +} + +// Registers a hotkey. When the hotkey is executed by the user, target will be +// called with selector. +// Arguments: +// keyCode - the virtual keycode of the hotkey +// cocoaModifiers - the modifiers that need to be used with |keyCode|. NB +// that these are cocoa modifiers, so NSCommandKeyMask etc. +// target - instance that will get |action| called when the hotkey fires +// action - the method to call on |target| when the hotkey fires +// onKeyDown - is YES, the hotkey fires on the keydown (usual) otherwise +// it fires on the key up. +// Returns: +// a EventHotKeyRef that you can use with other Carbon functions, or for +// unregistering the hotkey. Note that all hotkeys are unregistered +// automatically when an app quits. Will be NULL on failure. +- (EventHotKeyRef)registerHotKey:(NSUInteger)keyCode + modifiers:(NSUInteger)cocoaModifiers + target:(id)target + action:(SEL)selector + whenPressed:(BOOL)onKeyDown { + static UInt32 sCurrentID = 0; + + EventHotKeyRef theRef = NULL; + EventHotKeyID keyID; + keyID.signature = kGTMCarbonFrameworkSignature; + keyID.id = ++sCurrentID; + GTMCarbonHotKey *newKey = [[[GTMCarbonHotKey alloc] initWithHotKey:keyID + target:target + action:selector + whenPressed:onKeyDown] + autorelease]; + require(newKey, CantCreateKey); + require_noerr(RegisterEventHotKey((UInt32)keyCode, + GTMCocoaToCarbonKeyModifiers(cocoaModifiers), + keyID, + [self eventTarget], + 0, + &theRef), CantRegisterHotkey); + + + [hotkeys_ setObject:newKey forKey:[NSValue valueWithPointer:theRef]]; +CantCreateKey: +CantRegisterHotkey: + return theRef; +} + +// Unregisters a hotkey previously registered with registerHotKey. +// Arguments: +// keyRef - the EventHotKeyRef to unregister +- (void)unregisterHotKey:(EventHotKeyRef)keyRef { + NSValue *key = [NSValue valueWithPointer:keyRef]; + check([hotkeys_ objectForKey:key] != nil); + [hotkeys_ removeObjectForKey:key]; + verify_noerr(UnregisterEventHotKey(keyRef)); +} + +// A hotkey has been hit. See if it is one of ours, and if so fire it. +// Arguments: +// event - the hotkey even that was received +// Returns: +// Yes if handled. +- (BOOL)handleHotKeyEvent:(GTMCarbonEvent *)event { + EventHotKeyID keyID; + BOOL handled = [event getEventHotKeyIDParameterNamed:kEventParamDirectObject + data:&keyID]; + if (handled) { + NSEnumerator *dictEnumerator = [hotkeys_ objectEnumerator]; + GTMCarbonHotKey *hotkey; + while ((hotkey = [dictEnumerator nextObject])) { + if ([hotkey matchesHotKeyID:keyID]) { + EventKind kind = [event eventKind]; + BOOL onKeyDown = [hotkey onKeyDown]; + if ((kind == kEventHotKeyPressed && onKeyDown) || + (kind == kEventHotKeyReleased && !onKeyDown)) { + handled = [hotkey sendAction:self]; + } + break; + } + } + } + return handled; +} + +// Currently we handle hotkey and command events here. If we get one of them +// we dispatch them off to the handlers above. Otherwise we just call up to +// super. +// Arguments: +// event - the event to check +// handler - the handler call ref +// Returns: +// OSStatus +- (OSStatus)handleEvent:(GTMCarbonEvent *)event + handler:(EventHandlerCallRef)handler { + OSStatus theStatus = eventNotHandledErr; + if ([event eventClass] == kEventClassKeyboard) { + EventKind kind = [event eventKind]; + if (kind == kEventHotKeyPressed || kind == kEventHotKeyReleased) { + theStatus = [self handleHotKeyEvent:event] ? noErr : eventNotHandledErr; + } + } + // We didn't handle it, maybe somebody upstairs will. + if (theStatus == eventNotHandledErr) { + theStatus = [super handleEvent:event handler:handler]; + } + return theStatus; +} + +@end + +@implementation GTMCarbonHotKey + +// Init a HotKey record. In debug version make sure that the selector we are +// passed matches what we expect. ( +// Arguments: +// keyID - id of the hotkey +// target - object we are going to call when the hotkey is hit +// action - selector we are going to call on target +// whenPressed - do we do it on key down or key up? +// Returns: +// a hotkey record, or nil on failure +- (id)initWithHotKey:(EventHotKeyID)keyID + target:(id)target + action:(SEL)selector + whenPressed:(BOOL)onKeyDown { + if ((self = [super init])) { + if(!target || !selector) { + [self release]; + return nil; + } + id_ = keyID; + target_ = [target retain]; + selector_ = selector; + onKeyDown_ = onKeyDown; + GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(target, + selector, + @encode(void), + @encode(id), + NULL); + } + return self; +} + +- (void)dealloc { + [target_ release]; + [super dealloc]; +} + +// Does this record match key |keyID| +// Arguments: +// keyID - the id to match against +// Returns: +// Yes if we match this key id +- (BOOL)matchesHotKeyID:(EventHotKeyID)keyID { + return (id_.signature == keyID.signature) && (id_.id == keyID.id); +} + +- (BOOL)sendAction:(id)sender { + BOOL handled = NO; + @try { + [target_ performSelector:selector_ withObject:sender]; + handled = YES; + } + @catch (NSException * e) { + handled = NO; + _GTMDevLog(@"Exception fired in hotkey: %@ (%@)", [e name], [e reason]); + } // COV_NF_LINE + return handled; +} + +- (BOOL)onKeyDown { + return onKeyDown_; +} + +@end + + + -- cgit v1.2.3