8 Commits

Author SHA1 Message Date
Vadim Shpakovski 4099b62587 Isolates monitoring code from data. 2012-09-19 12:43:39 +03:00
Vadim Shpakovski 243aee3038 Adds support for direct monitoring shortcuts. 2012-09-19 12:36:53 +03:00
Vadim Shpakovski 723eeb4346 Improves API. 2012-09-19 08:57:26 +03:00
Vadim Shpakovski b1f90fa096 Merge. 2012-09-19 01:50:15 +03:00
Vadim Shpakovski 08717ff81e Merge remote-tracking branch 'origin/master' 2012-09-19 01:49:24 +03:00
Vadim Shpakovski da2e1af574 Adds a new appearance and fixes bugs related to key equivalents. 2012-09-19 01:44:49 +03:00
Vadim Shpakovski dddb527bfb Fixes the issue with setting associated User Defaults key twice. 2012-09-19 00:56:51 +03:00
Vadim Shpakovski e01e9075e5 Improves textured view appearance. 2012-09-03 16:23:21 +03:00
9 changed files with 250 additions and 95 deletions
+8
View File
@@ -0,0 +1,8 @@
#import "MASShortcut.h"
@interface MASShortcut (Monitoring)
+ (id)addGlobalHotkeyMonitorWithShortcut:(MASShortcut *)shortcut handler:(void (^)())handler;
+ (void)removeGlobalHotkeyMonitor:(id)monitor;
@end
+158
View File
@@ -0,0 +1,158 @@
#import "MASShortcut+Monitoring.h"
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 (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;
}
}
-1
View File
@@ -5,5 +5,4 @@
+ (void)registerGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey handler:(void (^)())handler;
+ (void)unregisterGlobalShortcutWithUserDefaultsKey:(NSString *)userDefaultsKey;
@end
+15 -86
View File
@@ -1,23 +1,21 @@
#import "MASShortcut+UserDefaults.h"
#import "MASShortcut+Monitoring.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 +27,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 +65,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;
}
if (shortcut == nil) return;
self.monitor = [MASShortcut addGlobalHotkeyMonitorWithShortcut:shortcut handler:self.handler];
}
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;
}
@end
@end
+1
View File
@@ -37,6 +37,7 @@ enum {
@property (nonatomic, readonly) UInt32 carbonKeyCode;
@property (nonatomic, readonly) UInt32 carbonFlags;
@property (nonatomic, readonly) NSString *keyCodeString;
@property (nonatomic, readonly) NSString *keyCodeStringForKeyEquivalent;
@property (nonatomic, readonly) NSString *modifierFlagsString;
@property (nonatomic, readonly) NSData *data;
@property (nonatomic, readonly) BOOL shouldBypass;
+41 -4
View File
@@ -52,7 +52,8 @@ NSString *const kMASShortcutModifierFlags = @"ModifierFlags";
+ (MASShortcut *)shortcutWithData:(NSData *)data
{
return (data ? (MASShortcut *)[NSKeyedUnarchiver unarchiveObjectWithData:data] : nil);
id shortcut = (data ? [NSKeyedUnarchiver unarchiveObjectWithData:data] : nil);
return shortcut;
}
#pragma mark - Shortcut accessors
@@ -82,6 +83,35 @@ NSString *const kMASShortcutModifierFlags = @"ModifierFlags";
return [NSString stringWithFormat:@"%@%@", self.modifierFlagsString, self.keyCodeString];
}
- (NSString *)keyCodeStringForKeyEquivalent
{
NSString *keyCodeString = self.keyCodeString;
if (keyCodeString.length > 1) {
switch (self.keyCode) {
case kVK_F1: return MASShortcutChar(0xF704);
case kVK_F2: return MASShortcutChar(0xF705);
case kVK_F3: return MASShortcutChar(0xF706);
case kVK_F4: return MASShortcutChar(0xF707);
case kVK_F5: return MASShortcutChar(0xF708);
case kVK_F6: return MASShortcutChar(0xF709);
case kVK_F7: return MASShortcutChar(0xF70a);
case kVK_F8: return MASShortcutChar(0xF70b);
case kVK_F9: return MASShortcutChar(0xF70c);
case kVK_F10: return MASShortcutChar(0xF70d);
case kVK_F11: return MASShortcutChar(0xF70e);
case kVK_F12: return MASShortcutChar(0xF70f);
// From this point down I am guessing F13 etc come sequentially, I don't have a keyboard to test.
case kVK_F13: return MASShortcutChar(0xF710);
case kVK_F14: return MASShortcutChar(0xF711);
case kVK_F15: return MASShortcutChar(0xF712);
case kVK_F16: return MASShortcutChar(0xF713);
case kVK_Space: return MASShortcutChar(0x20);
default: return @"";
}
}
return keyCodeString.lowercaseString;
}
- (NSString *)keyCodeString
{
// Some key codes don't have an equivalent
@@ -223,8 +253,15 @@ NSString *const kMASShortcutModifierFlags = @"ModifierFlags";
if (menuItem.hasSubmenu && [self isKeyEquivalent:keyEquivalent flags:flags takenInMenu:menuItem.submenu error:outError]) return YES;
BOOL equalFlags = (MASShortcutClear(menuItem.keyEquivalentModifierMask) == flags);
BOOL equalHotkey = [menuItem.keyEquivalent.uppercaseString isEqualToString:keyEquivalent];
if (equalFlags && equalHotkey) {
BOOL equalHotkeyLowercase = [menuItem.keyEquivalent.lowercaseString isEqualToString:keyEquivalent];
// Check if the cases are different, we know ours is lower and that shift is included in our modifiers
// If theirs is capitol, we need to add shift to their modifiers
if (equalHotkeyLowercase && ![menuItem.keyEquivalent isEqualToString:keyEquivalent]) {
equalFlags = (MASShortcutClear(menuItem.keyEquivalentModifierMask | NSShiftKeyMask) == flags);
}
if (equalFlags && equalHotkeyLowercase) {
if (outError) {
NSString *format = NSLocalizedString(@"This shortcut cannot be used used because it is already used by the menu item %@.",
@"Message for alert when shortcut is already used");
@@ -265,7 +302,7 @@ NSString *const kMASShortcutModifierFlags = @"ModifierFlags";
}
CFRelease(globalHotKeys);
}
return [self isKeyEquivalent:self.keyCodeString flags:self.modifierFlags takenInMenu:[NSApp mainMenu] error:outError];
return [self isKeyEquivalent:self.keyCodeStringForKeyEquivalent flags:self.modifierFlags takenInMenu:[NSApp mainMenu] error:outError];
}
@end
+9 -1
View File
@@ -25,6 +25,10 @@ void *kDefaultsObserver = &kDefaultsObserver;
- (void)setAssociatedUserDefaultsKey:(NSString *)associatedUserDefaultsKey
{
// First, stop observing previous shortcut view
objc_setAssociatedObject(self, kDefaultsObserver, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// Next, start observing current shortcut view
MASShortcutDefaultsObserver *defaultsObserver = [[MASShortcutDefaultsObserver alloc] initWithShortcutView:self userDefaultsKey:associatedUserDefaultsKey];
objc_setAssociatedObject(self, kDefaultsObserver, defaultsObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@@ -109,7 +113,11 @@ void *kShortcutValueObserver = &kShortcutValueObserver;
if (_internalShortcutChange) return;
MASShortcut *shortcut = [object valueForKey:keyPath];
_internalPreferenceChange = YES;
[[NSUserDefaults standardUserDefaults] setObject:shortcut.data forKey:_userDefaultsKey];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:(shortcut.data ?: [NSKeyedArchiver archivedDataWithRootObject:nil]) forKey:_userDefaultsKey];
[defaults synchronize];
_internalPreferenceChange = NO;
}
else {
+3 -2
View File
@@ -1,8 +1,9 @@
@class MASShortcut;
typedef enum {
MASShortcutViewAppearanceDefault = 0,
MASShortcutViewAppearanceTexturedRect
MASShortcutViewAppearanceDefault = 0, // Height = 19 px
MASShortcutViewAppearanceTexturedRect, // Height = 25 px
MASShortcutViewAppearanceRounded // Height = 43 px
} MASShortcutViewAppearance;
@interface MASShortcutView : NSView
+15 -1
View File
@@ -83,6 +83,10 @@
_shortcutCell.bezelStyle = NSTexturedRoundedBezelStyle;
break;
}
case MASShortcutViewAppearanceRounded: {
_shortcutCell.bezelStyle = NSRoundedBezelStyle;
break;
}
}
}
@@ -148,6 +152,10 @@
[_shortcutCell drawWithFrame:CGRectOffset(frame, 0.0, 1.0) inView:self];
break;
}
case MASShortcutViewAppearanceRounded: {
[_shortcutCell drawWithFrame:CGRectOffset(frame, 0.0, 1.0) inView:self];
break;
}
}
}
@@ -195,7 +203,13 @@
- (void)getShortcutRect:(CGRect *)shortcutRectRef hintRect:(CGRect *)hintRectRef
{
CGRect shortcutRect, hintRect;
CGRectDivide(self.bounds, &hintRect, &shortcutRect, HINT_BUTTON_WIDTH, CGRectMaxXEdge);
CGFloat hintButtonWidth = HINT_BUTTON_WIDTH;
switch (self.appearance) {
case MASShortcutViewAppearanceTexturedRect: hintButtonWidth += 2.0; break;
case MASShortcutViewAppearanceRounded: hintButtonWidth += 3.0; break;
default: break;
}
CGRectDivide(self.bounds, &hintRect, &shortcutRect, hintButtonWidth, CGRectMaxXEdge);
if (shortcutRectRef) *shortcutRectRef = shortcutRect;
if (hintRectRef) *hintRectRef = hintRect;
}