diff options
Diffstat (limited to 'Framework/MASShortcutValidator.m')
| -rw-r--r-- | Framework/MASShortcutValidator.m | 109 |
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 |
