aboutsummaryrefslogtreecommitdiffstats
path: root/Framework/MASShortcutMonitor.m
diff options
context:
space:
mode:
authorTomáš Znamenáček2014-08-06 18:05:43 +0200
committerTomáš Znamenáček2015-01-07 15:39:39 +0100
commit444d1bccb9770738fa4ea40383c23f44a55089c2 (patch)
treea4f06e82263b751685e51ee3e1479fd9eebb1d16 /Framework/MASShortcutMonitor.m
parentd8efb0755ab99ef60411f77fa4b635f466973d0f (diff)
downloadMASShortcut-444d1bccb9770738fa4ea40383c23f44a55089c2.tar.bz2
Refactored the shortcut dispatcher and bindings to user defaults.
This is a big change that was hard to split into smaller commits. There’s now a new class to bind shortcuts to actions, a new class to bind user defaults’ keys to actions, and a new way to associate user defaults with the recorder control (MASShortcutView). I have also updated the demo app to go with the changes. The new class to associate shortcuts with actions is called MASShortcutMonitor. It wraps the Carbon hotkey magic and offers a simple interface to add a shortcut along with a block that should be run when the shortcut is pressed. It’s the lowest-level interface. Since the usual requirement is to store the shortcuts into user defaults, there’s also a higher-level interface offered by the MASShortcutBinder class. That takes a defaults key and associates it with a block. When the shortcut stored under the defaults key changes, the binder automatically switches to the new shortcut. The class is a wrapper built atop of the previous one, the MASShortcutMonitor – it simply adds, updates and removes shortcuts as the user defaults change. I have removed the special user defaults integration code from the recorder control (MASShortcutView) and replaced it with a small Cocoa Bindings shim. This means that in order to keep the recorder control in sync with the defaults you just have to call the usual bind:toObject:withKeyPath:options: method, like this: [_shortcutView bind:MASShortcutBinding toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath[@"values.ExampleDefaultsKey" options:@{NSValueTransformerNameBindingOption:NSKeyedUnarchiveFromDataTransformerName}]; That’s more verbose than the previous solution, but it’s much cleaner and can be swept under a convenience call if needed. I might also add a dictionaryValue property later that would make it possible to bind the value to user defaults directly, without a transformer, and would enable backward compatibility with Shortcut Recorder.
Diffstat (limited to 'Framework/MASShortcutMonitor.m')
-rw-r--r--Framework/MASShortcutMonitor.m84
1 files changed, 84 insertions, 0 deletions
diff --git a/Framework/MASShortcutMonitor.m b/Framework/MASShortcutMonitor.m
new file mode 100644
index 0000000..f1c7e9c
--- /dev/null
+++ b/Framework/MASShortcutMonitor.m
@@ -0,0 +1,84 @@
+#import "MASShortcutMonitor.h"
+#import "MASHotKey.h"
+
+@interface MASShortcutMonitor ()
+@property(assign) EventHandlerRef eventHandlerRef;
+@property(strong) NSMutableDictionary *hotKeys;
+@end
+
+static OSStatus MASCarbonEventCallback(EventHandlerCallRef, EventRef, void*);
+
+@implementation MASShortcutMonitor
+
+- (instancetype) init
+{
+ self = [super init];
+ [self setHotKeys:[NSMutableDictionary dictionary]];
+ EventTypeSpec hotKeyPressedSpec = { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed };
+ OSStatus status = InstallEventHandler(GetEventDispatcherTarget(), MASCarbonEventCallback,
+ 1, &hotKeyPressedSpec, (__bridge void*)self, &_eventHandlerRef);
+ if (status != noErr) {
+ return nil;
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ if (_eventHandlerRef) {
+ RemoveEventHandler(_eventHandlerRef);
+ _eventHandlerRef = NULL;
+ }
+}
+
+#pragma mark Registration
+
+- (void) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action
+{
+ MASHotKey *hotKey = [MASHotKey registeredHotKeyWithShortcut:shortcut];
+ [hotKey setAction:action];
+ [_hotKeys setObject:hotKey forKey:shortcut];
+}
+
+- (void) unregisterShortcut: (MASShortcut*) shortcut
+{
+ [_hotKeys removeObjectForKey:shortcut];
+}
+
+- (BOOL) isShortcutRegistered: (MASShortcut*) shortcut
+{
+ return !![_hotKeys objectForKey:shortcut];
+}
+
+#pragma mark Event Handling
+
+- (void) handleEvent: (EventRef) event
+{
+ if (GetEventClass(event) != kEventClassKeyboard) {
+ return;
+ }
+
+ EventHotKeyID hotKeyID;
+ OSStatus status = GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID), NULL, &hotKeyID);
+ if (status != noErr || hotKeyID.signature != MASHotKeySignature) {
+ return;
+ }
+
+ [_hotKeys enumerateKeysAndObjectsUsingBlock:^(MASShortcut *shortcut, MASHotKey *hotKey, BOOL *stop) {
+ if (hotKeyID.id == [hotKey carbonID]) {
+ if ([hotKey action]) {
+ dispatch_async(dispatch_get_main_queue(), [hotKey action]);
+ }
+ *stop = YES;
+ }
+ }];
+}
+
+@end
+
+static OSStatus MASCarbonEventCallback(EventHandlerCallRef _, EventRef event, void *context)
+{
+ MASShortcutMonitor *dispatcher = (__bridge id)context;
+ [dispatcher handleEvent:event];
+ return noErr;
+}