From ad739e6004b5da9898be76c82afb865238870aff Mon Sep 17 00:00:00 2001 From: Denis Stanishevskiy Date: Thu, 16 Nov 2017 02:07:01 +0300 Subject: [PATCH 1/2] Support for an action on shortcut key up --- Demo/AppDelegate.m | 11 +++++++++++ Framework/MASHotKey.h | 1 + Framework/MASShortcutBinder.h | 8 ++++++++ Framework/MASShortcutBinder.m | 14 +++++++++++++- Framework/MASShortcutMonitor.h | 7 +++++++ Framework/MASShortcutMonitor.m | 19 ++++++++++++++++--- 6 files changed, 56 insertions(+), 4 deletions(-) diff --git a/Demo/AppDelegate.m b/Demo/AppDelegate.m index 1f31c08..12ee7eb 100644 --- a/Demo/AppDelegate.m +++ b/Demo/AppDelegate.m @@ -57,6 +57,15 @@ - (void)playShortcutFeedback }); } +- (void)playShortcutUpFeedback +{ + [[NSSound soundNamed:@"Tink"] play]; + [_feedbackTextField setStringValue:NSLocalizedString(@"Shortcut unpressed!", @"Feedback that’s displayed when user presses the sample shortcut.")]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [_feedbackTextField setStringValue:@""]; + }); +} + // Handle changes in user defaults. We have to check keyPath here to see which of the // two checkboxes was changed. This is not very elegant, in practice you could use something // like https://github.com/facebook/KVOController with a nicer API. @@ -80,6 +89,8 @@ - (void) setCustomShortcutEnabled: (BOOL) enabled if (enabled) { [[MASShortcutBinder sharedBinder] bindShortcutWithDefaultsKey:MASCustomShortcutKey toAction:^{ [self playShortcutFeedback]; + } onKeyUp:^{ + [self playShortcutUpFeedback]; }]; } else { [[MASShortcutBinder sharedBinder] breakBindingWithDefaultsKey:MASCustomShortcutKey]; diff --git a/Framework/MASHotKey.h b/Framework/MASHotKey.h index 1d267e4..38b606c 100644 --- a/Framework/MASHotKey.h +++ b/Framework/MASHotKey.h @@ -6,6 +6,7 @@ extern FourCharCode const MASHotKeySignature; @property(readonly) UInt32 carbonID; @property(copy) dispatch_block_t action; +@property(copy) dispatch_block_t actionUp; + (instancetype) registeredHotKeyWithShortcut: (MASShortcut*) shortcut; diff --git a/Framework/MASShortcutBinder.h b/Framework/MASShortcutBinder.h index e7406de..a3dc607 100644 --- a/Framework/MASShortcutBinder.h +++ b/Framework/MASShortcutBinder.h @@ -47,6 +47,14 @@ */ - (void) bindShortcutWithDefaultsKey: (NSString*) defaultsKeyName toAction: (dispatch_block_t) action; +/** + Binds given actions to a shortcut stored under the given defaults key. + + In other words, no matter what shortcut you store under the given key, + pressing it will always trigger the given action. + */ +- (void) bindShortcutWithDefaultsKey: (NSString*) defaultsKeyName toAction: (dispatch_block_t) action onKeyUp: (dispatch_block_t) actionUp; + /** Disconnect the binding between user defaults and action. diff --git a/Framework/MASShortcutBinder.m b/Framework/MASShortcutBinder.m index 4c0d7f9..e30b6d0 100644 --- a/Framework/MASShortcutBinder.m +++ b/Framework/MASShortcutBinder.m @@ -3,6 +3,7 @@ @interface MASShortcutBinder () @property(strong) NSMutableDictionary *actions; +@property(strong) NSMutableDictionary *actionsUp; @property(strong) NSMutableDictionary *shortcuts; @end @@ -14,6 +15,7 @@ - (id) init { self = [super init]; [self setActions:[NSMutableDictionary dictionary]]; + [self setActionsUp:[NSMutableDictionary dictionaryWithCapacity:0]]; [self setShortcuts:[NSMutableDictionary dictionary]]; [self setShortcutMonitor:[MASShortcutMonitor sharedMonitor]]; [self setBindingOptions:@{NSValueTransformerNameBindingOption: NSKeyedUnarchiveFromDataTransformerName}]; @@ -40,12 +42,21 @@ + (instancetype) sharedBinder #pragma mark Registration - (void) bindShortcutWithDefaultsKey: (NSString*) defaultsKeyName toAction: (dispatch_block_t) action +{ + [self bindShortcutWithDefaultsKey:defaultsKeyName toAction:action onKeyUp:nil]; +} + +- (void) bindShortcutWithDefaultsKey: (NSString*) defaultsKeyName toAction: (dispatch_block_t) action onKeyUp: (dispatch_block_t) actionUp { NSAssert([defaultsKeyName rangeOfString:@"."].location == NSNotFound, @"Illegal character in binding name (“.”), please see http://git.io/x5YS."); NSAssert([defaultsKeyName rangeOfString:@" "].location == NSNotFound, @"Illegal character in binding name (“ ”), please see http://git.io/x5YS."); [_actions setObject:[action copy] forKey:defaultsKeyName]; + if (actionUp) + [_actionsUp setObject:[actionUp copy] forKey:defaultsKeyName]; + else + [_actionsUp removeObjectForKey:defaultsKeyName]; [self bind:defaultsKeyName toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:[@"values." stringByAppendingString:defaultsKeyName] @@ -57,6 +68,7 @@ - (void) breakBindingWithDefaultsKey: (NSString*) defaultsKeyName [_shortcutMonitor unregisterShortcut:[_shortcuts objectForKey:defaultsKeyName]]; [_shortcuts removeObjectForKey:defaultsKeyName]; [_actions removeObjectForKey:defaultsKeyName]; + [_actionsUp removeObjectForKey:defaultsKeyName]; [self unbind:defaultsKeyName]; } @@ -115,7 +127,7 @@ - (void) setValue: (id) value forUndefinedKey: (NSString*) key // Bind new shortcut [_shortcuts setObject:newShortcut forKey:key]; - [_shortcutMonitor registerShortcut:newShortcut withAction:[_actions objectForKey:key]]; + [_shortcutMonitor registerShortcut:newShortcut withAction:[_actions objectForKey:key] onKeyUp:[_actionsUp objectForKey:key]]; } @end diff --git a/Framework/MASShortcutMonitor.h b/Framework/MASShortcutMonitor.h index dc3d458..1bc1729 100644 --- a/Framework/MASShortcutMonitor.h +++ b/Framework/MASShortcutMonitor.h @@ -19,6 +19,13 @@ It may burn your house or cut your fingers. You have been warned. */ - (BOOL) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action; +/** + Register a shortcut along with an actions. + + Attempting to insert an already registered shortcut probably won’t work. + It may burn your house or cut your fingers. You have been warned. + */ +- (BOOL) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action onKeyUp: (dispatch_block_t) actionUp; - (BOOL) isShortcutRegistered: (MASShortcut*) shortcut; - (void) unregisterShortcut: (MASShortcut*) shortcut; diff --git a/Framework/MASShortcutMonitor.m b/Framework/MASShortcutMonitor.m index fce8022..c745172 100644 --- a/Framework/MASShortcutMonitor.m +++ b/Framework/MASShortcutMonitor.m @@ -16,9 +16,14 @@ - (instancetype) init { self = [super init]; [self setHotKeys:[NSMutableDictionary dictionary]]; - EventTypeSpec hotKeyPressedSpec = { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed }; + EventTypeSpec eventTypeSpecs[2] = { + { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed }, + { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyReleased } + }; + OSStatus status = InstallEventHandler(GetEventDispatcherTarget(), MASCarbonEventCallback, - 1, &hotKeyPressedSpec, (__bridge void*)self, &_eventHandlerRef); + 2, eventTypeSpecs, + (__bridge void*)self, &_eventHandlerRef); if (status != noErr) { return nil; } @@ -46,10 +51,16 @@ + (instancetype) sharedMonitor #pragma mark Registration - (BOOL) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action +{ + [self registerShortcut:shortcut withAction:action onKeyUp:nil]; +} + +- (BOOL) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action onKeyUp: (dispatch_block_t) actionUp { MASHotKey *hotKey = [MASHotKey registeredHotKeyWithShortcut:shortcut]; if (hotKey) { [hotKey setAction:action]; + [hotKey setActionUp:actionUp]; [_hotKeys setObject:hotKey forKey:shortcut]; return YES; } else { @@ -91,7 +102,9 @@ - (void) handleEvent: (EventRef) event [_hotKeys enumerateKeysAndObjectsUsingBlock:^(MASShortcut *shortcut, MASHotKey *hotKey, BOOL *stop) { if (hotKeyID.id == [hotKey carbonID]) { if ([hotKey action]) { - dispatch_async(dispatch_get_main_queue(), [hotKey action]); + dispatch_block_t action = (GetEventKind(event) == kEventHotKeyReleased) ? [hotKey actionUp] : [hotKey action]; + if (action) + dispatch_async(dispatch_get_main_queue(), action); } *stop = YES; } From 73f5aa98f6c6caeba6653eac568d54dc6afb1fa3 Mon Sep 17 00:00:00 2001 From: Denis Stanishevskiy Date: Thu, 16 Nov 2017 16:19:10 +0300 Subject: [PATCH 2/2] Uncommited change fix --- Framework/MASShortcutMonitor.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/MASShortcutMonitor.m b/Framework/MASShortcutMonitor.m index c745172..505ebf2 100644 --- a/Framework/MASShortcutMonitor.m +++ b/Framework/MASShortcutMonitor.m @@ -52,7 +52,7 @@ + (instancetype) sharedMonitor - (BOOL) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action { - [self registerShortcut:shortcut withAction:action onKeyUp:nil]; + return [self registerShortcut:shortcut withAction:action onKeyUp:nil]; } - (BOOL) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action onKeyUp: (dispatch_block_t) actionUp