Skip to content
This repository was archived by the owner on Mar 5, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Demo/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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];
Expand Down
1 change: 1 addition & 0 deletions Framework/MASHotKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
8 changes: 8 additions & 0 deletions Framework/MASShortcutBinder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
14 changes: 13 additions & 1 deletion Framework/MASShortcutBinder.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

@interface MASShortcutBinder ()
@property(strong) NSMutableDictionary *actions;
@property(strong) NSMutableDictionary *actionsUp;
@property(strong) NSMutableDictionary *shortcuts;
@end

Expand All @@ -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}];
Expand All @@ -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]
Expand All @@ -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];
}

Expand Down Expand Up @@ -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
7 changes: 7 additions & 0 deletions Framework/MASShortcutMonitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 16 additions & 3 deletions Framework/MASShortcutMonitor.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -46,10 +51,16 @@ + (instancetype) sharedMonitor
#pragma mark Registration

- (BOOL) registerShortcut: (MASShortcut*) shortcut withAction: (dispatch_block_t) action
{
return [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 {
Expand Down Expand Up @@ -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;
}
Expand Down