diff options
| -rw-r--r-- | MASShortcut+UserDefaults.h | 1 | ||||
| -rw-r--r-- | MASShortcut+UserDefaults.m | 100 | ||||
| -rw-r--r-- | MASShortcut.h | 3 | ||||
| -rw-r--r-- | MASShortcut.m | 155 |
4 files changed, 172 insertions, 87 deletions
diff --git a/MASShortcut+UserDefaults.h b/MASShortcut+UserDefaults.h index 1b44885..0c7f14e 100644 --- a/MASShortcut+UserDefaults.h +++ b/MASShortcut+UserDefaults.h @@ -5,5 +5,4 @@ + (void)registerGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey handler:(void (^)())handler; + (void)unregisterGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey; - @end diff --git a/MASShortcut+UserDefaults.m b/MASShortcut+UserDefaults.m index 28fd633..38d3707 100644 --- a/MASShortcut+UserDefaults.m +++ b/MASShortcut+UserDefaults.m @@ -1,23 +1,20 @@ #import "MASShortcut+UserDefaults.h" -@interface MASShortcutHotKey : NSObject +@interface MASShortcutUserDefaultsHotKey : NSObject @property (nonatomic, readonly) NSString *userDefaultsKey; -@property (nonatomic, readonly, copy) void (^handler)(); -@property (nonatomic, readonly) EventHotKeyRef carbonHotKey; -@property (nonatomic, readonly) UInt32 carbonHotKeyID; +@property (nonatomic, copy) void (^handler)(); +@property (nonatomic, weak) id monitor; - (id)initWithUserDefaultsKey:(NSString *)userDefaultsKey handler:(void (^)())handler; -+ (void)uninstallEventHandler; - @end #pragma mark - @implementation MASShortcut (UserDefaults) -+ (NSMutableDictionary *)registeredHotKeys ++ (NSMutableDictionary *)registeredUserDefaultsHotKeys { static NSMutableDictionary *shared = nil; static dispatch_once_t onceToken; @@ -29,29 +26,25 @@ + (void)registerGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey handler:(void (^)())handler; { - MASShortcutHotKey *hotKey = [[MASShortcutHotKey alloc] initWithUserDefaultsKey:userDefaultsKey handler:handler]; - [[self registeredHotKeys] setObject:hotKey forKey:userDefaultsKey]; + MASShortcutUserDefaultsHotKey *hotKey = [[MASShortcutUserDefaultsHotKey alloc] initWithUserDefaultsKey:userDefaultsKey handler:handler]; + [[self registeredUserDefaultsHotKeys] setObject:hotKey forKey:userDefaultsKey]; } + (void)unregisterGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey { - NSMutableDictionary *registeredHotKeys = [self registeredHotKeys]; + NSMutableDictionary *registeredHotKeys = [self registeredUserDefaultsHotKeys]; [registeredHotKeys removeObjectForKey:userDefaultsKey]; - if (registeredHotKeys.count == 0) { - [MASShortcutHotKey uninstallEventHandler]; - } } @end #pragma mark - -@implementation MASShortcutHotKey +@implementation MASShortcutUserDefaultsHotKey -@synthesize carbonHotKeyID = _carbonHotKeyID; +@synthesize monitor = _monitor; @synthesize handler = _handler; @synthesize userDefaultsKey = _userDefaultsKey; -@synthesize carbonHotKey = _carbonHotKey; #pragma mark - @@ -71,88 +64,23 @@ - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:[NSUserDefaults standardUserDefaults]]; - [self uninstallExisitingHotKey]; + [MASShortcut removeGlobalHotkeyMonitor:self.monitor]; } #pragma mark - - (void)userDefaultsDidChange:(NSNotification *)note { - [self uninstallExisitingHotKey]; + [MASShortcut removeGlobalHotkeyMonitor:self.monitor]; [self installHotKeyFromUserDefaults]; } -#pragma mark - Carbon events - -static EventHandlerRef sEventHandler = NULL; - -+ (BOOL)installCommonEventHandler -{ - if (sEventHandler == NULL) { - EventTypeSpec hotKeyPressedSpec = { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed }; - OSStatus status = InstallEventHandler(GetEventDispatcherTarget(), CarbonCallback, 1, &hotKeyPressedSpec, NULL, &sEventHandler); - if (status != noErr) { - sEventHandler = NULL; - return NO; - } - } - return YES; -} - -+ (void)uninstallEventHandler -{ - if (sEventHandler) { - RemoveEventHandler(sEventHandler); - sEventHandler = NULL; - } -} - -#pragma mark - - -- (void)uninstallExisitingHotKey -{ - if (_carbonHotKey) { - UnregisterEventHotKey(_carbonHotKey); - _carbonHotKey = NULL; - } -} - -FourCharCode const kMASShortcutSignature = 'MASS'; - - (void)installHotKeyFromUserDefaults { NSData *data = [[NSUserDefaults standardUserDefaults] dataForKey:_userDefaultsKey]; MASShortcut *shortcut = [MASShortcut shortcutWithData:data]; - if ((shortcut == nil) || ![[self class] installCommonEventHandler]) return; - - static UInt32 sCarbonHotKeyID = 0; - _carbonHotKeyID = ++ sCarbonHotKeyID; - EventHotKeyID hotKeyID = { .signature = kMASShortcutSignature, .id = _carbonHotKeyID }; - if (RegisterEventHotKey(shortcut.carbonKeyCode, shortcut.carbonFlags, hotKeyID, GetEventDispatcherTarget(), kEventHotKeyExclusive, &_carbonHotKey) != noErr) { - _carbonHotKey = NULL; - } -} - -static OSStatus CarbonCallback(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData) -{ - if (GetEventClass(inEvent) != kEventClassKeyboard) return noErr; - - EventHotKeyID hotKeyID; - OSStatus status = GetEventParameter(inEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID), NULL, &hotKeyID); - if (status != noErr) return status; - - if (hotKeyID.signature != kMASShortcutSignature) return noErr; - - [[MASShortcut registeredHotKeys] enumerateKeysAndObjectsUsingBlock:^(id key, MASShortcutHotKey *hotKey, BOOL *stop) { - if (hotKeyID.id == hotKey.carbonHotKeyID) { - if (hotKey.handler) { - hotKey.handler(); - } - *stop = YES; - } - }]; - - return noErr; + if (shortcut == nil) return; + self.monitor = [MASShortcut addGlobalHotkeyMonitorWithShortcut:shortcut handler:self.handler]; } -@end
\ No newline at end of file +@end diff --git a/MASShortcut.h b/MASShortcut.h index 3c6c109..a5c8fa9 100644 --- a/MASShortcut.h +++ b/MASShortcut.h @@ -51,4 +51,7 @@ enum { - (BOOL)isTakenError:(NSError **)error; ++ (id)addGlobalHotkeyMonitorWithShortcut:(MASShortcut *)shortcut handler:(void (^)())handler; ++ (void)removeGlobalHotkeyMonitor:(id)monitor; + @end diff --git a/MASShortcut.m b/MASShortcut.m index ca5a1ae..fd9250b 100644 --- a/MASShortcut.m +++ b/MASShortcut.m @@ -3,6 +3,26 @@ NSString *const kMASShortcutKeyCode = @"KeyCode"; NSString *const kMASShortcutModifierFlags = @"ModifierFlags"; +NSMutableDictionary *MASRegisteredHotKeys(); +BOOL InstallCommonEventHandler(); +void UninstallEventHandler(); +void InstallHotkeyWithShortcut(MASShortcut *shortcut, UInt32 *outCarbonHotKeyID, EventHotKeyRef *outCarbonHotKey); + +#pragma mark - + +@interface MASShortcutHotKey : NSObject + +@property (nonatomic, readonly) MASShortcut *shortcut; +@property (nonatomic, readonly, copy) void (^handler)(); +@property (nonatomic, readonly) EventHotKeyRef carbonHotKey; +@property (nonatomic, readonly) UInt32 carbonHotKeyID; + +- (id)initWithShortcut:(MASShortcut *)shortcut handler:(void (^)())handler; + +@end + +#pragma mark - + @implementation MASShortcut { NSUInteger _keyCode; // NSNotFound if empty NSUInteger _modifierFlags; // 0 if empty @@ -305,4 +325,139 @@ NSString *const kMASShortcutModifierFlags = @"ModifierFlags"; return [self isKeyEquivalent:self.keyCodeStringForKeyEquivalent flags:self.modifierFlags takenInMenu:[NSApp mainMenu] error:outError]; } +#pragma mark - Global monitoring + ++ (id)addGlobalHotkeyMonitorWithShortcut:(MASShortcut *)shortcut handler:(void (^)())handler +{ + NSString *monitor = [NSString stringWithFormat:@"%p: %@", shortcut, shortcut.description]; + MASShortcutHotKey *hotKey = [[MASShortcutHotKey alloc] initWithShortcut:shortcut handler:handler]; + [MASRegisteredHotKeys() setObject:hotKey forKey:monitor]; + return monitor; +} + ++ (void)removeGlobalHotkeyMonitor:(id)monitor +{ + if (monitor == nil) return; + NSMutableDictionary *registeredHotKeys = MASRegisteredHotKeys(); + [registeredHotKeys removeObjectForKey:monitor]; + if (registeredHotKeys.count == 0) { + UninstallEventHandler(); + } +} + +@end + +#pragma mark - + +@implementation MASShortcutHotKey + +@synthesize carbonHotKeyID = _carbonHotKeyID; +@synthesize handler = _handler; +@synthesize shortcut = _shortcut; +@synthesize carbonHotKey = _carbonHotKey; + +#pragma mark - + +- (id)initWithShortcut:(MASShortcut *)shortcut handler:(void (^)())handler +{ + self = [super init]; + if (self) { + _shortcut = shortcut; + _handler = [handler copy]; + InstallHotkeyWithShortcut(shortcut, &_carbonHotKeyID, &_carbonHotKey); + } + return self; +} + +- (void)dealloc +{ + [self uninstallExisitingHotKey]; +} + +- (void)uninstallExisitingHotKey +{ + if (_carbonHotKey) { + UnregisterEventHotKey(_carbonHotKey); + _carbonHotKey = NULL; + } +} + @end + +#pragma mark - Carbon magic + +NSMutableDictionary *MASRegisteredHotKeys() +{ + static NSMutableDictionary *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [NSMutableDictionary dictionary]; + }); + return shared; +} + +#pragma mark - + +FourCharCode const kMASShortcutSignature = 'MASS'; + +void InstallHotkeyWithShortcut(MASShortcut *shortcut, UInt32 *outCarbonHotKeyID, EventHotKeyRef *outCarbonHotKey) +{ + if ((shortcut == nil) || !InstallCommonEventHandler()) return; + + static UInt32 sCarbonHotKeyID = 0; + EventHotKeyID hotKeyID = { .signature = kMASShortcutSignature, .id = ++ sCarbonHotKeyID }; + EventHotKeyRef carbonHotKey = NULL; + if (RegisterEventHotKey(shortcut.carbonKeyCode, shortcut.carbonFlags, hotKeyID, GetEventDispatcherTarget(), kEventHotKeyExclusive, &carbonHotKey) != noErr) { + carbonHotKey = NULL; + } + + if (outCarbonHotKeyID) *outCarbonHotKeyID = hotKeyID.id; + if (outCarbonHotKey) *outCarbonHotKey = carbonHotKey; +} + +static OSStatus CarbonCallback(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData) +{ + if (GetEventClass(inEvent) != kEventClassKeyboard) return noErr; + + EventHotKeyID hotKeyID; + OSStatus status = GetEventParameter(inEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID), NULL, &hotKeyID); + if (status != noErr) return status; + + if (hotKeyID.signature != kMASShortcutSignature) return noErr; + + [MASRegisteredHotKeys() enumerateKeysAndObjectsUsingBlock:^(id key, MASShortcutHotKey *hotKey, BOOL *stop) { + if (hotKeyID.id == hotKey.carbonHotKeyID) { + if (hotKey.handler) { + hotKey.handler(); + } + *stop = YES; + } + }]; + + return noErr; +} + +#pragma mark - + +static EventHandlerRef sEventHandler = NULL; + +BOOL InstallCommonEventHandler() +{ + if (sEventHandler == NULL) { + EventTypeSpec hotKeyPressedSpec = { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed }; + OSStatus status = InstallEventHandler(GetEventDispatcherTarget(), CarbonCallback, 1, &hotKeyPressedSpec, NULL, &sEventHandler); + if (status != noErr) { + sEventHandler = NULL; + return NO; + } + } + return YES; +} + +void UninstallEventHandler() +{ + if (sEventHandler) { + RemoveEventHandler(sEventHandler); + sEventHandler = NULL; + } +} |
