aboutsummaryrefslogtreecommitdiffstats
path: root/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'Framework')
-rw-r--r--Framework/MASShortcut.h8
-rw-r--r--Framework/MASShortcut.m116
-rw-r--r--Framework/MASShortcutValidator.h15
-rw-r--r--Framework/MASShortcutValidator.m109
-rw-r--r--Framework/MASShortcutView.h3
-rw-r--r--Framework/MASShortcutView.m20
6 files changed, 139 insertions, 132 deletions
diff --git a/Framework/MASShortcut.h b/Framework/MASShortcut.h
index b7ed55c..123e25e 100644
--- a/Framework/MASShortcut.h
+++ b/Framework/MASShortcut.h
@@ -11,7 +11,6 @@
@property (nonatomic, readonly) NSString *modifierFlagsString;
@property (nonatomic, readonly) NSData *data;
@property (nonatomic, readonly) BOOL shouldBypass;
-@property (nonatomic, readonly, getter = isValid) BOOL valid;
- (id)initWithKeyCode:(NSUInteger)code modifierFlags:(NSUInteger)flags;
@@ -19,11 +18,4 @@
+ (MASShortcut *)shortcutWithEvent:(NSEvent *)anEvent;
+ (MASShortcut *)shortcutWithData:(NSData *)aData;
-- (BOOL)isTakenError:(NSError **)error;
-
-// The following API enable hotkeys with the Option key as the only modifier
-// For example, Option-G will not generate © and Option-R will not paste ®
-+ (void)setAllowsAnyHotkeyWithOptionModifier:(BOOL)allow;
-+ (BOOL)allowsAnyHotkeyWithOptionModifier;
-
@end
diff --git a/Framework/MASShortcut.m b/Framework/MASShortcut.m
index 3e1e498..0e834eb 100644
--- a/Framework/MASShortcut.m
+++ b/Framework/MASShortcut.m
@@ -235,120 +235,4 @@ NSString *const MASShortcutModifierFlags = @"ModifierFlags";
return (count ? [NSString stringWithCharacters:chars length:count] : @"");
}
-#pragma mark - Validation logic
-
-- (BOOL)shouldBypass
-{
- NSString *codeString = self.keyCodeString;
- return (self.modifierFlags == NSCommandKeyMask) && ([codeString isEqualToString:@"W"] || [codeString isEqualToString:@"Q"]);
-}
-
-BOOL MASShortcutAllowsAnyHotkeyWithOptionModifier = NO;
-
-+ (void)setAllowsAnyHotkeyWithOptionModifier:(BOOL)allow
-{
- MASShortcutAllowsAnyHotkeyWithOptionModifier = allow;
-}
-
-+ (BOOL)allowsAnyHotkeyWithOptionModifier
-{
- return MASShortcutAllowsAnyHotkeyWithOptionModifier;
-}
-
-- (BOOL)isValid
-{
- // Allow any function key with any combination of modifiers
- BOOL includesFunctionKey = ((_keyCode == kVK_F1) || (_keyCode == kVK_F2) || (_keyCode == kVK_F3) || (_keyCode == kVK_F4) ||
- (_keyCode == kVK_F5) || (_keyCode == kVK_F6) || (_keyCode == kVK_F7) || (_keyCode == kVK_F8) ||
- (_keyCode == kVK_F9) || (_keyCode == kVK_F10) || (_keyCode == kVK_F11) || (_keyCode == kVK_F12) ||
- (_keyCode == kVK_F13) || (_keyCode == kVK_F14) || (_keyCode == kVK_F15) || (_keyCode == kVK_F16) ||
- (_keyCode == kVK_F17) || (_keyCode == kVK_F18) || (_keyCode == kVK_F19) || (_keyCode == kVK_F20));
- if (includesFunctionKey) return YES;
-
- // Do not allow any other key without modifiers
- BOOL hasModifierFlags = (_modifierFlags > 0);
- if (!hasModifierFlags) return NO;
-
- // Allow any hotkey containing Control or Command modifier
- BOOL includesCommand = ((_modifierFlags & NSCommandKeyMask) > 0);
- BOOL includesControl = ((_modifierFlags & NSControlKeyMask) > 0);
- if (includesCommand || includesControl) return YES;
-
- // Allow Option key only in selected cases
- BOOL includesOption = ((_modifierFlags & NSAlternateKeyMask) > 0);
- if (includesOption) {
-
- // Always allow Option-Space and Option-Escape because they do not have any bind system commands
- if ((_keyCode == kVK_Space) || (_keyCode == kVK_Escape)) return YES;
-
- // Allow Option modifier with any key even if it will break the system binding
- if ([[self class] allowsAnyHotkeyWithOptionModifier]) return YES;
- }
-
- // The hotkey does not have any modifiers or violates system bindings
- return NO;
-}
-
-- (BOOL)isKeyEquivalent:(NSString *)keyEquivalent flags:(NSUInteger)flags takenInMenu:(NSMenu *)menu error:(NSError **)outError
-{
- for (NSMenuItem *menuItem in menu.itemArray) {
- if (menuItem.hasSubmenu && [self isKeyEquivalent:keyEquivalent flags:flags takenInMenu:menuItem.submenu error:outError]) return YES;
-
- BOOL equalFlags = (MASPickCocoaModifiers(menuItem.keyEquivalentModifierMask) == flags);
- 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 = (MASPickCocoaModifiers(menuItem.keyEquivalentModifierMask | NSShiftKeyMask) == flags);
- }
-
- if (equalFlags && equalHotkeyLowercase) {
- if (outError) {
- NSString *format = NSLocalizedString(@"This shortcut cannot be used because it is already used by the menu item ‘%@’.",
- @"Message for alert when shortcut is already used");
- NSDictionary *info = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:format, menuItem.title]
- forKey:NSLocalizedDescriptionKey];
- *outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:info];
- }
- return YES;
- }
- }
- return NO;
-}
-
-- (BOOL)isTakenError:(NSError **)outError
-{
- CFArrayRef globalHotKeys;
- BOOL isTaken = NO;
- if (CopySymbolicHotKeys(&globalHotKeys) == noErr) {
-
- // Enumerate all global hotkeys and check if any of them matches current shortcut
- for (CFIndex i = 0, count = CFArrayGetCount(globalHotKeys); i < count; i++) {
- CFDictionaryRef hotKeyInfo = CFArrayGetValueAtIndex(globalHotKeys, i);
- CFNumberRef code = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyCode);
- CFNumberRef flags = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyModifiers);
- CFNumberRef enabled = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyEnabled);
-
- if (([(__bridge NSNumber *)code unsignedIntegerValue] == self.keyCode) &&
- ([(__bridge NSNumber *)flags unsignedIntegerValue] == self.carbonFlags) &&
- ([(__bridge NSNumber *)enabled boolValue])) {
-
- if (outError) {
- NSString *description = NSLocalizedString(@"This combination cannot be used because it is already used by a system-wide "
- @"keyboard shortcut.\nIf you really want to use this key combination, most shortcuts "
- @"can be changed in the Keyboard & Mouse panel in System Preferences.",
- @"Message for alert when shortcut is already used by the system");
- NSDictionary *info = [NSDictionary dictionaryWithObject:description forKey:NSLocalizedDescriptionKey];
- *outError = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:info];
- }
- isTaken = YES;
- break;
- }
- }
- CFRelease(globalHotKeys);
- }
- return (isTaken || [self isKeyEquivalent:self.keyCodeStringForKeyEquivalent flags:self.modifierFlags takenInMenu:[NSApp mainMenu] error:outError]);
-}
-
@end
diff --git a/Framework/MASShortcutValidator.h b/Framework/MASShortcutValidator.h
new file mode 100644
index 0000000..cc5f816
--- /dev/null
+++ b/Framework/MASShortcutValidator.h
@@ -0,0 +1,15 @@
+#import "MASShortcut.h"
+
+@interface MASShortcutValidator : NSObject
+
+// The following API enable hotkeys with the Option key as the only modifier
+// For example, Option-G will not generate © and Option-R will not paste ®
+@property(assign) BOOL allowAnyShortcutWithOptionModifier;
+
++ (instancetype) sharedValidator;
+
+- (BOOL) isShortcutValid: (MASShortcut*) shortcut;
+- (BOOL) isShortcut: (MASShortcut*) shortcut alreadyTakenInMenu: (NSMenu*) menu explanation: (NSString**) explanation;
+- (BOOL) isShortcutAlreadyTakenBySystem: (MASShortcut*) shortcut explanation: (NSString**) explanation;
+
+@end
diff --git a/Framework/MASShortcutValidator.m b/Framework/MASShortcutValidator.m
new file mode 100644
index 0000000..4fcba9b
--- /dev/null
+++ b/Framework/MASShortcutValidator.m
@@ -0,0 +1,109 @@
+#import "MASShortcutValidator.h"
+
+@implementation MASShortcutValidator
+
++ (instancetype) sharedValidator
+{
+ static dispatch_once_t once;
+ static MASShortcutValidator *sharedInstance;
+ dispatch_once(&once, ^{
+ sharedInstance = [[self alloc] init];
+ });
+ return sharedInstance;
+}
+
+- (BOOL) isShortcutValid: (MASShortcut*) shortcut
+{
+ NSUInteger keyCode = [shortcut keyCode];
+ NSUInteger modifiers = [shortcut modifierFlags];
+
+ // Allow any function key with any combination of modifiers
+ BOOL includesFunctionKey = ((keyCode == kVK_F1) || (keyCode == kVK_F2) || (keyCode == kVK_F3) || (keyCode == kVK_F4) ||
+ (keyCode == kVK_F5) || (keyCode == kVK_F6) || (keyCode == kVK_F7) || (keyCode == kVK_F8) ||
+ (keyCode == kVK_F9) || (keyCode == kVK_F10) || (keyCode == kVK_F11) || (keyCode == kVK_F12) ||
+ (keyCode == kVK_F13) || (keyCode == kVK_F14) || (keyCode == kVK_F15) || (keyCode == kVK_F16) ||
+ (keyCode == kVK_F17) || (keyCode == kVK_F18) || (keyCode == kVK_F19) || (keyCode == kVK_F20));
+ if (includesFunctionKey) return YES;
+
+ // Do not allow any other key without modifiers
+ BOOL hasModifierFlags = (modifiers > 0);
+ if (!hasModifierFlags) return NO;
+
+ // Allow any hotkey containing Control or Command modifier
+ BOOL includesCommand = ((modifiers & NSCommandKeyMask) > 0);
+ BOOL includesControl = ((modifiers & NSControlKeyMask) > 0);
+ if (includesCommand || includesControl) return YES;
+
+ // Allow Option key only in selected cases
+ BOOL includesOption = ((modifiers & NSAlternateKeyMask) > 0);
+ if (includesOption) {
+
+ // Always allow Option-Space and Option-Escape because they do not have any bind system commands
+ if ((keyCode == kVK_Space) || (keyCode == kVK_Escape)) return YES;
+
+ // Allow Option modifier with any key even if it will break the system binding
+ if (_allowAnyShortcutWithOptionModifier) return YES;
+ }
+
+ // The hotkey does not have any modifiers or violates system bindings
+ return NO;
+}
+
+- (BOOL) isShortcut: (MASShortcut*) shortcut alreadyTakenInMenu: (NSMenu*) menu explanation: (NSString**) explanation
+{
+ NSString *keyEquivalent = [shortcut keyCodeStringForKeyEquivalent];
+ NSUInteger flags = [shortcut modifierFlags];
+
+ for (NSMenuItem *menuItem in menu.itemArray) {
+ if (menuItem.hasSubmenu && [self isShortcut:shortcut alreadyTakenInMenu:[menuItem submenu] explanation:explanation]) return YES;
+
+ BOOL equalFlags = (MASPickCocoaModifiers(menuItem.keyEquivalentModifierMask) == flags);
+ 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 = (MASPickCocoaModifiers(menuItem.keyEquivalentModifierMask | NSShiftKeyMask) == flags);
+ }
+
+ if (equalFlags && equalHotkeyLowercase) {
+ if (explanation) {
+ *explanation = NSLocalizedString(@"This shortcut cannot be used because it is already used by the menu item ‘%@’.",
+ @"Message for alert when shortcut is already used");
+ *explanation = [NSString stringWithFormat:*explanation, menuItem.title];
+ }
+ return YES;
+ }
+ }
+ return NO;
+}
+
+- (BOOL) isShortcutAlreadyTakenBySystem: (MASShortcut*) shortcut explanation: (NSString**) explanation
+{
+ CFArrayRef globalHotKeys;
+ if (CopySymbolicHotKeys(&globalHotKeys) == noErr) {
+
+ // Enumerate all global hotkeys and check if any of them matches current shortcut
+ for (CFIndex i = 0, count = CFArrayGetCount(globalHotKeys); i < count; i++) {
+ CFDictionaryRef hotKeyInfo = CFArrayGetValueAtIndex(globalHotKeys, i);
+ CFNumberRef code = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyCode);
+ CFNumberRef flags = CFDictionaryGetValue(hotKeyInfo, kHISymbolicHotKeyModifiers);
+
+ if (([(__bridge NSNumber *)code unsignedIntegerValue] == [shortcut keyCode]) &&
+ ([(__bridge NSNumber *)flags unsignedIntegerValue] == [shortcut carbonFlags])) {
+
+ if (explanation) {
+ *explanation = NSLocalizedString(@"This combination cannot be used because it is already used by a system-wide "
+ @"keyboard shortcut.\nIf you really want to use this key combination, most shortcuts "
+ @"can be changed in the Keyboard & Mouse panel in System Preferences.",
+ @"Message for alert when shortcut is already used by the system");
+ }
+ return YES;
+ }
+ }
+ CFRelease(globalHotKeys);
+ }
+ return [self isShortcut:shortcut alreadyTakenInMenu:[NSApp mainMenu] explanation:explanation];
+}
+
+@end
diff --git a/Framework/MASShortcutView.h b/Framework/MASShortcutView.h
index 9f94bf5..5d788b9 100644
--- a/Framework/MASShortcutView.h
+++ b/Framework/MASShortcutView.h
@@ -1,4 +1,4 @@
-@class MASShortcut;
+@class MASShortcut, MASShortcutValidator;
typedef enum {
MASShortcutViewAppearanceDefault = 0, // Height = 19 px
@@ -10,6 +10,7 @@ typedef enum {
@interface MASShortcutView : NSView
@property (nonatomic, strong) MASShortcut *shortcutValue;
+@property (nonatomic, strong) MASShortcutValidator *shortcutValidator;
@property (nonatomic, getter = isRecording) BOOL recording;
@property (nonatomic, getter = isEnabled) BOOL enabled;
@property (nonatomic, copy) void (^shortcutValueChange)(MASShortcutView *sender);
diff --git a/Framework/MASShortcutView.m b/Framework/MASShortcutView.m
index 0212271..d45efd3 100644
--- a/Framework/MASShortcutView.m
+++ b/Framework/MASShortcutView.m
@@ -1,5 +1,5 @@
#import "MASShortcutView.h"
-#import "MASShortcut.h"
+#import "MASShortcutValidator.h"
#define HINT_BUTTON_WIDTH 23.0
#define BUTTON_FONT_SIZE 11.0
@@ -52,6 +52,12 @@
self = [super initWithCoder:coder];
if (self) {
[self commonInit];
+ _shortcutCell = [[[self.class shortcutCellClass] alloc] init];
+ _shortcutCell.buttonType = NSPushOnPushOffButton;
+ _shortcutCell.font = [[NSFontManager sharedFontManager] convertFont:_shortcutCell.font toSize:BUTTON_FONT_SIZE];
+ _shortcutValidator = [MASShortcutValidator sharedValidator];
+ _enabled = YES;
+ [self resetShortcutCellStyle];
}
return self;
}
@@ -396,21 +402,21 @@ void *kUserDataHint = &kUserDataHint;
else {
// Verify possible shortcut
if (shortcut.keyCodeString.length > 0) {
- if (shortcut.valid) {
+ if ([_shortcutValidator isShortcutValid:shortcut]) {
// Verify that shortcut is not used
- NSError *error = nil;
- if ([shortcut isTakenError:&error]) {
+ NSString *explanation = nil;
+ if ([_shortcutValidator isShortcutAlreadyTakenBySystem:shortcut explanation:&explanation]) {
// Prevent cancel of recording when Alert window is key
[weakSelf activateResignObserver:NO];
[weakSelf activateEventMonitoring:NO];
NSString *format = NSLocalizedString(@"The key combination %@ cannot be used",
@"Title for alert when shortcut is already used");
- NSAlert *alert = [[NSAlert alloc] init];
+ NSAlert* alert = [[NSAlert alloc]init];
alert.alertStyle = NSCriticalAlertStyle;
alert.informativeText = [NSString stringWithFormat:format, shortcut];
- alert.messageText = error.localizedDescription;
+ alert.messageText = explanation;
[alert addButtonWithTitle:NSLocalizedString(@"OK", @"Alert button when shortcut is already used")];
-
+
[alert runModal];
weakSelf.shortcutPlaceholder = nil;
[weakSelf activateResignObserver:YES];