aboutsummaryrefslogtreecommitdiff
path: root/AppKit/GTMCarbonEvent.m
diff options
context:
space:
mode:
Diffstat (limited to 'AppKit/GTMCarbonEvent.m')
-rw-r--r--AppKit/GTMCarbonEvent.m709
1 files changed, 709 insertions, 0 deletions
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
+
+
+