aboutsummaryrefslogtreecommitdiffstats
path: root/Framework/MASShortcutValidator.m
diff options
context:
space:
mode:
Diffstat (limited to 'Framework/MASShortcutValidator.m')
-rw-r--r--Framework/MASShortcutValidator.m109
1 files changed, 109 insertions, 0 deletions
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