aboutsummaryrefslogtreecommitdiffstats
path: root/Framework/MASShortcut+Monitoring.m
diff options
context:
space:
mode:
authorTomáš Znamenáček2014-08-05 11:13:13 +0200
committerTomáš Znamenáček2015-01-07 15:05:11 +0100
commit377b44220f2a4a8b7ffc3eda9e93cf073e8a74da (patch)
treeb83239fd741773451bd9a75480ebe5a276c7d885 /Framework/MASShortcut+Monitoring.m
parenta3a459b4e4e47bf18dccd5dc7f315389346e3d6c (diff)
downloadMASShortcut-377b44220f2a4a8b7ffc3eda9e93cf073e8a74da.tar.bz2
Repackaged the code as a framework and included the demo.
Packaging the code as a framework is mostly just a formality. It doesn’t really change much, it just turns the code into a regular component. What it does change is that the code now has its own Xcode settings, which could make compatibility easier in the long run. Including the demo in the main repository makes it easier to hack on the library, since you can try the changes immediately. It also shows how to bundle the framework into an app that uses it.
Diffstat (limited to 'Framework/MASShortcut+Monitoring.m')
-rw-r--r--Framework/MASShortcut+Monitoring.m165
1 files changed, 165 insertions, 0 deletions
diff --git a/Framework/MASShortcut+Monitoring.m b/Framework/MASShortcut+Monitoring.m
new file mode 100644
index 0000000..b6750b4
--- /dev/null
+++ b/Framework/MASShortcut+Monitoring.m
@@ -0,0 +1,165 @@
+#import "MASShortcut+Monitoring.h"
+
+NSMutableDictionary *MASRegisteredHotKeys(void);
+BOOL InstallCommonEventHandler(void);
+BOOL InstallHotkeyWithShortcut(MASShortcut *shortcut, UInt32 *outCarbonHotKeyID, EventHotKeyRef *outCarbonHotKey);
+void UninstallEventHandler(void);
+
+#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 (Monitoring)
+
++ (id)addGlobalHotkeyMonitorWithShortcut:(MASShortcut *)shortcut handler:(void (^)())handler
+{
+ NSString *monitor = [NSString stringWithFormat:@"%@", shortcut.description];
+ if ([MASRegisteredHotKeys() objectForKey:monitor]) return nil;
+
+ MASShortcutHotKey *hotKey = [[MASShortcutHotKey alloc] initWithShortcut:shortcut handler:handler];
+ if (hotKey == nil) return nil;
+
+ [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];
+
+ if (!InstallHotkeyWithShortcut(shortcut, &_carbonHotKeyID, &_carbonHotKey))
+ self = nil;
+ }
+ 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';
+
+BOOL InstallHotkeyWithShortcut(MASShortcut *shortcut, UInt32 *outCarbonHotKeyID, EventHotKeyRef *outCarbonHotKey)
+{
+ if ((shortcut == nil) || !InstallCommonEventHandler()) return NO;
+
+ static UInt32 sCarbonHotKeyID = 0;
+ EventHotKeyID hotKeyID = { .signature = kMASShortcutSignature, .id = ++ sCarbonHotKeyID };
+ EventHotKeyRef carbonHotKey = NULL;
+ if (RegisterEventHotKey(shortcut.carbonKeyCode, shortcut.carbonFlags, hotKeyID, GetEventDispatcherTarget(), kEventHotKeyExclusive, &carbonHotKey) != noErr) {
+ return NO;
+ }
+
+ if (outCarbonHotKeyID) *outCarbonHotKeyID = hotKeyID.id;
+ if (outCarbonHotKey) *outCarbonHotKey = carbonHotKey;
+ return YES;
+}
+
+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;
+ }
+}