Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

759 lines (659 sloc) 22.254 kb
//
// 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 <AppKit/AppKit.h>
#import "GTMObjectSingleton.h"
#import "GTMDebugSelectorValidation.h"
#import "GTMTypeCasting.h"
// Wrapper for all the info we need about a hotkey that we can store in a
// Foundation storage class.
@interface GTMCarbonHotKey (GTMCarbonHotKeyPrivate)
// 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
// userInfo - user storage
// 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
userInfo:(id)userInfo
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;
- (void)setHotKeyRef:(EventHotKeyRef)ref;
@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;
}
#if GTM_SUPPORT_GC
- (void)finalize {
if (event_) {
ReleaseEvent(event_);
event_ = NULL;
}
[super finalize];
}
#endif
// releases our retained event
//
- (void)dealloc {
if (event_) {
ReleaseEvent(event_);
event_ = NULL;
}
[super dealloc];
}
// description utliity for debugging
//
- (NSString *)description {
// Use 8 bytes because stack protection gives us a warning if we use a
// smaller buffer.
char cls[8];
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
= GTM_STATIC_CAST(GTMCarbonEventHandler, inUserData);
// 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
#if (MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_5)
// Accidentally marked as !LP64 in the 10.5sdk, it's back in the 10.6 sdk.
// If you remove this decl, please remove it from GTMCarbonEventTest.m as well.
extern EventTargetRef GetApplicationEventTarget(void);
#endif // (MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_5)
@implementation GTMCarbonEventApplicationEventHandler
GTMOBJECT_SINGLETON_BOILERPLATE(GTMCarbonEventApplicationEventHandler,
sharedApplicationEventHandler);
- (EventTargetRef)eventTarget {
return GetApplicationEventTarget();
}
@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_ = [[NSMutableArray 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
// action should have the signature - (void)handler:(GTMCarbonEventDispatcherHandler *)handler
// userInfo - user storage
// 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.
- (GTMCarbonHotKey *)registerHotKey:(NSUInteger)keyCode
modifiers:(NSUInteger)cocoaModifiers
target:(id)target
action:(SEL)selector
userInfo:(id)userInfo
whenPressed:(BOOL)onKeyDown {
static UInt32 sCurrentID = 0;
GTMCarbonHotKey *newKey = nil;
EventHotKeyRef theRef = NULL;
EventHotKeyID keyID;
keyID.signature = kGTMCarbonFrameworkSignature;
keyID.id = ++sCurrentID;
newKey = [[[GTMCarbonHotKey alloc] initWithHotKey:keyID
target:target
action:selector
userInfo:userInfo
whenPressed:onKeyDown] autorelease];
require(newKey, CantCreateKey);
require_noerr_action(RegisterEventHotKey((UInt32)keyCode,
GTMCocoaToCarbonKeyModifiers(cocoaModifiers),
keyID,
[self eventTarget],
0,
&theRef),
CantRegisterHotKey, newKey = nil);
[newKey setHotKeyRef:theRef];
[hotkeys_ addObject:newKey];
CantRegisterHotKey:
CantCreateKey:
return newKey;
}
// Unregisters a hotkey previously registered with registerHotKey.
// Arguments:
// keyRef - the EventHotKeyRef to unregister
- (void)unregisterHotKey:(GTMCarbonHotKey *)keyRef {
check([hotkeys_ containsObject:keyRef]);
[[keyRef retain] autorelease];
[hotkeys_ removeObject:keyRef];
verify_noerr(UnregisterEventHotKey([keyRef hotKeyRef]));
}
// 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) {
GTMCarbonHotKey *hotkey;
GTM_FOREACH_OBJECT(hotkey, hotkeys_) {
if ([hotkey matchesHotKeyID:keyID]) {
EventKind kind = [event eventKind];
BOOL onKeyDown = [hotkey onKeyDown];
if ((kind == kEventHotKeyPressed && onKeyDown) ||
(kind == kEventHotKeyReleased && !onKeyDown)) {
handled = [hotkey sendAction];
}
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
// reference - hotkey reference
// target - object we are going to call when the hotkey is hit
// action - selector we are going to call on target
// userinfo - info for user
// 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
userInfo:(id)userInfo
whenPressed:(BOOL)onKeyDown {
if ((self = [super init])) {
if(!target || !selector) {
[self release];
return nil;
}
id_ = keyID;
target_ = [target retain];
userInfo_ = [userInfo retain];
selector_ = selector;
onKeyDown_ = onKeyDown;
GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(target,
selector,
@encode(void),
@encode(id),
NULL);
}
return self;
}
- (void)dealloc {
[target_ release];
[userInfo_ release];
[super dealloc];
}
- (NSUInteger)hash {
return (NSUInteger)hotKeyRef_;
}
- (BOOL)isEqual:(id)object {
return [object isMemberOfClass:[self class]]
&& (hotKeyRef_ == [object hotKeyRef]);
}
// 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 {
BOOL handled = NO;
@try {
[target_ performSelector:selector_ withObject:self];
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_;
}
- (id)userInfo {
return userInfo_;
}
- (EventHotKeyRef)hotKeyRef {
return hotKeyRef_;
}
- (void)setHotKeyRef:(EventHotKeyRef)ref {
hotKeyRef_ = ref;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@ %p> - ref %p signature %d id %d",
[self class], self, hotKeyRef_, id_.signature, id_.id];
}
@end
Jump to Line
Something went wrong with that request. Please try again.