diff options
Diffstat (limited to 'DDHotKeyCenter.m')
| -rw-r--r-- | DDHotKeyCenter.m | 480 | 
1 files changed, 244 insertions, 236 deletions
| diff --git a/DDHotKeyCenter.m b/DDHotKeyCenter.m index 465baac..a5ae678 100644 --- a/DDHotKeyCenter.m +++ b/DDHotKeyCenter.m @@ -1,7 +1,7 @@  /*   DDHotKey -- DDHotKeyCenter.m - Copyright (c) 2010, Dave DeLong <http://www.davedelong.com> + Copyright (c) 2012, Dave DeLong <http://www.davedelong.com>   Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. @@ -12,302 +12,310 @@  #import <Carbon/Carbon.h>  #import <objc/runtime.h> +#if __has_feature(objc_arc) + +#define DDHK_HAS_ARC 1 +#define DDHK_RETAIN(_o) (_o) +#define DDHK_RELEASE(_o) +#define DDHK_AUTORELEASE(_o) (_o) + +#else + +#define DDHK_HAS_ARC 0 +#define DDHK_RETAIN(_o) [(_o) retain] +#define DDHK_RELEASE(_o) [(_o) release] +#define DDHK_AUTORELEASE(_o) [(_o) autorelease] + +#endif +  #pragma mark Private Global Declarations -static NSMutableSet * _registeredHotKeys = nil; -static UInt32 _nextHotKeyID = 1; -OSStatus dd_hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void * userData); +OSStatus dd_hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData);  UInt32 dd_translateModifierFlags(NSUInteger flags); -NSString* dd_stringifyModifierFlags(NSUInteger flags);  #pragma mark DDHotKey -@implementation DDHotKey +@interface DDHotKey () -- (id) target { return nil; } -- (SEL) action { return nil; } -- (id) object { return nil; } -- (unsigned short) keyCode { return 0; } -- (NSUInteger) modifierFlags { return 0; } +@property (nonatomic, retain) NSValue *hotKeyRef; +@property (nonatomic) UInt32 hotKeyID; -#if NS_BLOCKS_AVAILABLE -- (DDHotKeyTask) task { return nil; } -#endif +@end -- (NSUInteger) hash { -	return [self keyCode] + [self modifierFlags]; +@implementation DDHotKey { +    id _target; +    SEL _action; +    id _object; +     +    unsigned short _keyCode; +    NSUInteger _modifierFlags; +    DDHotKeyTask _task;  } -- (BOOL) isEqual:(id)object { -	BOOL equal = NO; -	if ([object isKindOfClass:[DDHotKey class]]) { -		equal = ([object keyCode] == [self keyCode]); -		equal &= ([object modifierFlags] == [self modifierFlags]); -	} -	return equal; +- (void) dealloc { +    [[DDHotKeyCenter sharedHotKeyCenter] unregisterHotKey:self]; +#if !DDHK_HAS_ARC +    DDHK_RELEASE(_target); _target = nil; +    DDHK_RELEASE(_object); _object = nil; +    DDHK_RELEASE(_hotKeyRef); _hotKeyRef = nil; +    DDHK_RELEASE(_task); _task = nil; +    [super dealloc]; +#endif  } -- (NSString *) description { -	NSString * flags = dd_stringifyModifierFlags([self modifierFlags]); -	NSString * invokes = @"(block)"; -	if ([self target] != nil && [self action] != nil) { -		invokes = [NSString stringWithFormat:@"[%@ %@]", [self target], NSStringFromSelector([self action])]; -	} -	return [NSString stringWithFormat:@"%@\n\t(key: %hu\n\tflags: %@\n\tinvokes: %@)", [super description], [self keyCode], flags, invokes]; +- (void)_setTarget:(id)target { +    if (target != _target) { +        DDHK_RELEASE(_target); +        _target = DDHK_RETAIN(target); +    }  } -@end - -@interface _DDHotKey : DDHotKey { -	@private -	id target; -	SEL action; -	id object; -	 -#if NS_BLOCKS_AVAILABLE -	DDHotKeyTask task; -#endif -	 -	unsigned short keyCode; -	NSUInteger modifierFlags; -	UInt32 hotKeyID; -	NSValue * hotKeyRef; +- (void)_setAction:(SEL)action { +    _action = action;  } -@property (nonatomic, retain) id target; -@property (nonatomic) SEL action; -@property (nonatomic, retain) id object; -@property (nonatomic) unsigned short keyCode; -@property (nonatomic) NSUInteger modifierFlags; -@property (nonatomic) UInt32 hotKeyID; -@property (nonatomic, retain) NSValue * hotKeyRef; +- (void)_setObject:(id)object { +    if (object != _object) { +        DDHK_RELEASE(_object); +        _object = DDHK_RETAIN(object); +    } +} -#if NS_BLOCKS_AVAILABLE -@property (nonatomic, copy) DDHotKeyTask task; -#endif +- (void)_setKeyCode:(unsigned short)keyCode { +    _keyCode = keyCode; +} -- (void) invokeWithEvent:(NSEvent *)event; -- (BOOL) registerHotKey; -- (void) unregisterHotKey; +- (void)_setModifierFlags:(NSUInteger)modifierFlags { +    _modifierFlags = modifierFlags; +} -@end +- (void)_setTask:(DDHotKeyTask)task { +    DDHK_RELEASE(_task); +    _task = [task copy]; +} -@implementation _DDHotKey +- (NSUInteger)hash { +    return [self keyCode] ^ [self modifierFlags]; +} -@synthesize target, action, object, keyCode, modifierFlags, hotKeyID, hotKeyRef; -#if NS_BLOCKS_AVAILABLE -@synthesize task; -#endif +- (BOOL)isEqual:(id)object { +    BOOL equal = NO; +    if ([object isKindOfClass:[DDHotKey class]]) { +        equal = ([object keyCode] == [self keyCode]); +        equal &= ([object modifierFlags] == [self modifierFlags]); +    } +    return equal; +} -- (Class) class { return [DDHotKey class]; } +- (NSString *)description { +    NSMutableArray *bits = [NSMutableArray array]; +    if ((_modifierFlags & NSControlKeyMask) > 0) { [bits addObject:@"NSControlKeyMask"]; } +    if ((_modifierFlags & NSCommandKeyMask) > 0) { [bits addObject:@"NSCommandKeyMask"]; } +    if ((_modifierFlags & NSShiftKeyMask) > 0) { [bits addObject:@"NSShiftKeyMask"]; } +    if ((_modifierFlags & NSAlternateKeyMask) > 0) { [bits addObject:@"NSAlternateKeyMask"]; } +     +    NSString *flags = [NSString stringWithFormat:@"(%@)", [bits componentsJoinedByString:@" | "]]; +    NSString *invokes = @"(block)"; +    if ([self target] != nil && [self action] != nil) { +        invokes = [NSString stringWithFormat:@"[%@ %@]", [self target], NSStringFromSelector([self action])]; +    } +    return [NSString stringWithFormat:@"%@\n\t(key: %hu\n\tflags: %@\n\tinvokes: %@)", [super description], [self keyCode], flags, invokes]; +} -- (void) invokeWithEvent:(NSEvent *)event { -	if (target != nil && action != nil && [target respondsToSelector:action]) { -		[target performSelector:action withObject:event withObject:object]; -	} -#if NS_BLOCKS_AVAILABLE -	else if (task != nil) { -		task(event); -	} -#endif +- (void)invokeWithEvent:(NSEvent *)event { +    if (_target != nil && _action != nil && [_target respondsToSelector:_action]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +        [_target performSelector:_action withObject:event withObject:_object]; +#pragma clang diagnostic pop +    } else if (_task != nil) { +        _task(event); +    }  } -- (NSString *) actionString { -	return NSStringFromSelector(action); +- (NSString *)actionString { +    return NSStringFromSelector(_action);  } -- (BOOL) registerHotKey { -	EventHotKeyID keyID; -	keyID.signature = 'htk1'; -	keyID.id = _nextHotKeyID; -	 -	EventHotKeyRef carbonHotKey; -	UInt32 flags = dd_translateModifierFlags(modifierFlags); -	OSStatus err = RegisterEventHotKey(keyCode, flags, keyID, GetEventDispatcherTarget(), 0, &carbonHotKey); -	 -	//error registering hot key -	if (err != 0) { return NO; } -	 -	NSValue * refValue = [NSValue valueWithPointer:carbonHotKey]; -	[self setHotKeyRef:refValue]; -	[self setHotKeyID:_nextHotKeyID]; -	 -	_nextHotKeyID++; -	 -	return YES; +@end + +#pragma mark DDHotKeyCenter + +static DDHotKeyCenter *center = nil; + +@implementation DDHotKeyCenter { +    NSMutableSet *_registeredHotKeys; +    UInt32 _nextHotKeyID;  } -- (void) unregisterHotKey { -	EventHotKeyRef carbonHotKey = (EventHotKeyRef)[hotKeyRef pointerValue]; -	UnregisterEventHotKey(carbonHotKey); -	[self setHotKeyRef:nil]; ++ (id)sharedHotKeyCenter { +    static dispatch_once_t onceToken; +    dispatch_once(&onceToken, ^{ +        center = [[DDHotKeyCenter alloc] _init]; +    }); +    return center;  } -- (void) dealloc { -	[target release], target = nil; -	[object release], object = nil; -	if (hotKeyRef != nil) { -		[self unregisterHotKey]; -		[hotKeyRef release], hotKeyRef = nil; -	} -	[super dealloc]; ++ (id)allocWithZone:(NSZone *)zone { +    if (center == nil) { +        return [super allocWithZone:zone]; +    } +    return DDHK_RETAIN(center);  } -@end +- (id)_init { +    self = [super init]; +    if (self) { +        _registeredHotKeys = [[NSMutableSet alloc] init]; +        _nextHotKeyID = 1; +    } +    return self; +} -#pragma mark DDHotKeyCenter +- (NSSet *)hotKeysMatchingPredicate:(NSPredicate *)predicate { +    return [_registeredHotKeys filteredSetUsingPredicate:predicate]; +} -@implementation DDHotKeyCenter - -+ (void) initialize { -	if (self == [DDHotKeyCenter class] && _registeredHotKeys == nil) { -		_registeredHotKeys = [[NSMutableSet alloc] init]; -		_nextHotKeyID = 1; -		EventTypeSpec eventSpec; -		eventSpec.eventClass = kEventClassKeyboard; -		eventSpec.eventKind = kEventHotKeyReleased; -		InstallApplicationEventHandler(&dd_hotKeyHandler, 1, &eventSpec, NULL, NULL); -	} +- (BOOL)hasRegisteredHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags { +    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"keyCode = %hu AND modifierFlags = %lu", keyCode, flags]; +    return ([[self hotKeysMatchingPredicate:predicate] count] > 0);  } -- (NSSet *) hotKeysMatchingPredicate:(NSPredicate *)predicate { -	return [_registeredHotKeys filteredSetUsingPredicate:predicate]; +- (BOOL)_registerHotKey:(DDHotKey *)hotKey { +    BOOL success = NO; +     +    EventHotKeyID keyID; +    keyID.signature = 'htk1'; +    keyID.id = _nextHotKeyID; +     +    EventHotKeyRef carbonHotKey; +    UInt32 flags = dd_translateModifierFlags([hotKey modifierFlags]); +    OSStatus err = RegisterEventHotKey([hotKey keyCode], flags, keyID, GetEventDispatcherTarget(), 0, &carbonHotKey); +     +    //error registering hot key +    if (err != 0) { return NO; } +     +    NSValue *refValue = [NSValue valueWithPointer:carbonHotKey]; +    [hotKey setHotKeyRef:refValue]; +    [hotKey setHotKeyID:_nextHotKeyID]; +     +    _nextHotKeyID++; +    [_registeredHotKeys addObject:hotKey]; +     +    return success;  } -- (BOOL) hasRegisteredHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags { -	NSPredicate * predicate = [NSPredicate predicateWithFormat:@"keyCode = %hu AND modifierFlags = %lu", keyCode, flags]; -	return ([[self hotKeysMatchingPredicate:predicate] count] > 0); +- (void)unregisterHotKey:(DDHotKey *)hotKey { +    NSValue *hotKeyRef = [hotKey hotKeyRef]; +    if (hotKeyRef) { +        EventHotKeyRef carbonHotKey = (EventHotKeyRef)[hotKeyRef pointerValue]; +        UnregisterEventHotKey(carbonHotKey); +        [hotKey setHotKeyRef:nil]; +         +        [_registeredHotKeys removeObject:hotKey]; +    }  } -#if NS_BLOCKS_AVAILABLE -- (BOOL) registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task { -	//we can't add a new hotkey if something already has this combo -	if ([self hasRegisteredHotKeyWithKeyCode:keyCode modifierFlags:flags]) { return NO; } -	 -	_DDHotKey * newHotKey = [[_DDHotKey alloc] init]; -	[newHotKey setTask:task]; -	[newHotKey setKeyCode:keyCode]; -	[newHotKey setModifierFlags:flags]; -	 -	BOOL success = [newHotKey registerHotKey]; -	if (success) { -		[_registeredHotKeys addObject:newHotKey]; -	} -	 -	[newHotKey release]; -	return success; +- (BOOL)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task { +    //we can't add a new hotkey if something already has this combo +    if ([self hasRegisteredHotKeyWithKeyCode:keyCode modifierFlags:flags]) { return NO; } +     +    DDHotKey *newHotKey = DDHK_AUTORELEASE([[DDHotKey alloc] init]); +    [newHotKey _setTask:task]; +    [newHotKey _setKeyCode:keyCode]; +    [newHotKey _setModifierFlags:flags]; +     +    return [self _registerHotKey:newHotKey];  } -#endif -- (BOOL) registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags target:(id)target action:(SEL)action object:(id)object { -	//we can't add a new hotkey if something already has this combo -	if ([self hasRegisteredHotKeyWithKeyCode:keyCode modifierFlags:flags]) { return NO; } -	 -	//build the hotkey object: -	_DDHotKey * newHotKey = [[_DDHotKey alloc] init]; -	[newHotKey setTarget:target]; -	[newHotKey setAction:action]; -	[newHotKey setObject:object]; -	[newHotKey setKeyCode:keyCode]; -	[newHotKey setModifierFlags:flags]; -	 -	BOOL success = [newHotKey registerHotKey]; -	if (success) { -		[_registeredHotKeys addObject:newHotKey]; -	} -	 -	[newHotKey release]; -	return success; +- (BOOL)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags target:(id)target action:(SEL)action object:(id)object { +    //we can't add a new hotkey if something already has this combo +    if ([self hasRegisteredHotKeyWithKeyCode:keyCode modifierFlags:flags]) { return NO; } +     +    //build the hotkey object: +    DDHotKey *newHotKey = DDHK_AUTORELEASE([[DDHotKey alloc] init]); +    [newHotKey _setTarget:target]; +    [newHotKey _setAction:action]; +    [newHotKey _setObject:object]; +    [newHotKey _setKeyCode:keyCode]; +    [newHotKey _setModifierFlags:flags]; +    return [self _registerHotKey:newHotKey];  } -- (void) unregisterHotKeysMatchingPredicate:(NSPredicate *)predicate { -	//explicitly unregister the hotkey, since relying on the unregistration in -dealloc can be problematic -	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; -	NSSet * matches = [self hotKeysMatchingPredicate:predicate]; -	[_registeredHotKeys minusSet:matches]; -	[matches makeObjectsPerformSelector:@selector(unregisterHotKey)]; -	[pool release]; +- (void)unregisterHotKeysMatchingPredicate:(NSPredicate *)predicate { +    //explicitly unregister the hotkey, since relying on the unregistration in -dealloc can be problematic +    @autoreleasepool { +        NSSet *matches = [self hotKeysMatchingPredicate:predicate]; +        for (DDHotKey *hotKey in matches) { +            [self unregisterHotKey:hotKey]; +        } +    }  } -- (void) unregisterHotKey:(DDHotKey *)hotKey { -	if (object_getClass(hotKey) == [_DDHotKey class]) { -		_DDHotKey * key = (_DDHotKey *)hotKey; -		[_registeredHotKeys removeObject:key]; -		[key unregisterHotKey]; -	} else { -		[NSException raise:NSInvalidArgumentException format:@"Invalid hotkey"]; -	} +- (void)unregisterHotKeysWithTarget:(id)target { +    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"target = %@", target]; +    [self unregisterHotKeysMatchingPredicate:predicate];  } -- (void) unregisterHotKeysWithTarget:(id)target { -	NSPredicate * predicate = [NSPredicate predicateWithFormat:@"target = %@", target]; -	[self unregisterHotKeysMatchingPredicate:predicate]; +- (void)unregisterHotKeysWithTarget:(id)target action:(SEL)action { +    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"target = %@ AND actionString = %@", target, NSStringFromSelector(action)]; +    [self unregisterHotKeysMatchingPredicate:predicate];  } -- (void) unregisterHotKeysWithTarget:(id)target action:(SEL)action { -	NSPredicate * predicate = [NSPredicate predicateWithFormat:@"target = %@ AND actionString = %@", target, NSStringFromSelector(action)]; -	[self unregisterHotKeysMatchingPredicate:predicate]; +- (void)unregisterHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags { +    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"keyCode = %hu AND modifierFlags = %lu", keyCode, flags]; +    [self unregisterHotKeysMatchingPredicate:predicate];  } -- (void) unregisterHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags { -	NSPredicate * predicate = [NSPredicate predicateWithFormat:@"keyCode = %hu AND modifierFlags = %lu", keyCode, flags]; -	[self unregisterHotKeysMatchingPredicate:predicate]; +- (void)unregisterAllHotKeys { +    NSSet *keys = [_registeredHotKeys copy]; +    for (DDHotKey *key in keys) { +        [self unregisterHotKey:key]; +    } +    DDHK_RELEASE(keys);  } -- (NSSet *) registeredHotKeys { -	return [self hotKeysMatchingPredicate:[NSPredicate predicateWithFormat:@"hotKeyRef != NULL"]]; +- (NSSet *)registeredHotKeys { +    return [self hotKeysMatchingPredicate:[NSPredicate predicateWithFormat:@"hotKeyRef != NULL"]];  }  @end -OSStatus dd_hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void * userData) { -	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; -	 -	EventHotKeyID hotKeyID; -	GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID),NULL,&hotKeyID); -	 -	UInt32 keyID = hotKeyID.id; -	 -	NSSet * matchingHotKeys = [_registeredHotKeys filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"hotKeyID = %u", keyID]]; -	if ([matchingHotKeys count] > 1) { NSLog(@"ERROR!"); } -	_DDHotKey * matchingHotKey = [matchingHotKeys anyObject]; -	 -	NSEvent * event = [NSEvent eventWithEventRef:theEvent]; -	NSEvent * keyEvent = [NSEvent keyEventWithType:NSKeyUp  -										  location:[event locationInWindow]  -									 modifierFlags:[event modifierFlags] -										 timestamp:[event timestamp]  -									  windowNumber:-1  -										   context:nil  -										characters:@""  -					   charactersIgnoringModifiers:@""  -										 isARepeat:NO  -										   keyCode:[matchingHotKey keyCode]]; - -	[matchingHotKey invokeWithEvent:keyEvent]; -	 -	[pool release]; -	 -	return noErr; +OSStatus dd_hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) { +    @autoreleasepool { +        EventHotKeyID hotKeyID; +        GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID),NULL,&hotKeyID); +         +        UInt32 keyID = hotKeyID.id; +         +        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"hotKeyID = %u", keyID]; +        NSSet *matchingHotKeys = [[DDHotKeyCenter sharedHotKeyCenter] hotKeysMatchingPredicate:predicate]; +        if ([matchingHotKeys count] > 1) { NSLog(@"ERROR!"); } +        DDHotKey *matchingHotKey = [matchingHotKeys anyObject]; +         +        NSEvent *event = [NSEvent eventWithEventRef:theEvent]; +        NSEvent *keyEvent = [NSEvent keyEventWithType:NSKeyUp +                                             location:[event locationInWindow] +                                        modifierFlags:[event modifierFlags] +                                            timestamp:[event timestamp] +                                         windowNumber:-1 +                                              context:nil +                                           characters:@"" +                          charactersIgnoringModifiers:@"" +                                            isARepeat:NO +                                              keyCode:[matchingHotKey keyCode]]; +         +        [matchingHotKey invokeWithEvent:keyEvent]; +    } +     +    return noErr;  }  UInt32 dd_translateModifierFlags(NSUInteger flags) { -	UInt32 newFlags = 0; -	if ((flags & NSControlKeyMask) > 0) { newFlags |= controlKey; } -	if ((flags & NSCommandKeyMask) > 0) { newFlags |= cmdKey; } -	if ((flags & NSShiftKeyMask) > 0) { newFlags |= shiftKey; } -	if ((flags & NSAlternateKeyMask) > 0) { newFlags |= optionKey; } -	return newFlags; -} - -NSString* dd_stringifyModifierFlags(NSUInteger flags) { -	NSMutableArray * bits = [NSMutableArray array]; -	if ((flags & NSControlKeyMask) > 0) { [bits addObject:@"NSControlKeyMask"]; } -	if ((flags & NSCommandKeyMask) > 0) { [bits addObject:@"NSCommandKeyMask"]; } -	if ((flags & NSShiftKeyMask) > 0) { [bits addObject:@"NSShiftKeyMask"]; } -	if ((flags & NSAlternateKeyMask) > 0) { [bits addObject:@"NSAlternateKeyMask"]; } -	if ([bits count] > 0) { -		return [NSString stringWithFormat:@"(%@)", [bits componentsJoinedByString:@" | "]]; -	} -	return @"ERROR: No valid flags"; +    UInt32 newFlags = 0; +    if ((flags & NSControlKeyMask) > 0) { newFlags |= controlKey; } +    if ((flags & NSCommandKeyMask) > 0) { newFlags |= cmdKey; } +    if ((flags & NSShiftKeyMask) > 0) { newFlags |= shiftKey; } +    if ((flags & NSAlternateKeyMask) > 0) { newFlags |= optionKey; } +    return newFlags;  } | 
