diff options
Diffstat (limited to 'MASShortcut.m')
| -rw-r--r-- | MASShortcut.m | 354 |
1 files changed, 0 insertions, 354 deletions
diff --git a/MASShortcut.m b/MASShortcut.m deleted file mode 100644 index ccc2035..0000000 --- a/MASShortcut.m +++ /dev/null @@ -1,354 +0,0 @@ -#import "MASShortcut.h" - -NSString *const MASShortcutKeyCode = @"KeyCode"; -NSString *const MASShortcutModifierFlags = @"ModifierFlags"; - -@implementation MASShortcut { - NSUInteger _keyCode; // NSNotFound if empty - NSUInteger _modifierFlags; // 0 if empty -} - -@synthesize modifierFlags = _modifierFlags; -@synthesize keyCode = _keyCode; - -#pragma mark - - -+ (BOOL)supportsSecureCoding -{ - return YES; -} - -- (void)encodeWithCoder:(NSCoder *)coder -{ - [coder encodeInteger:(self.keyCode != NSNotFound ? (NSInteger)self.keyCode : - 1) forKey:MASShortcutKeyCode]; - [coder encodeInteger:(NSInteger)self.modifierFlags forKey:MASShortcutModifierFlags]; -} - -- (id)initWithCoder:(NSCoder *)decoder -{ - self = [super init]; - if (self) { - NSInteger code = [decoder decodeIntegerForKey:MASShortcutKeyCode]; - self.keyCode = (code < 0 ? NSNotFound : (NSUInteger)code); - self.modifierFlags = [decoder decodeIntegerForKey:MASShortcutModifierFlags]; - } - return self; -} - -- (id)initWithKeyCode:(NSUInteger)code modifierFlags:(NSUInteger)flags -{ - self = [super init]; - if (self) { - _keyCode = code; - _modifierFlags = MASShortcutClear(flags); - } - return self; -} - -+ (MASShortcut *)shortcutWithKeyCode:(NSUInteger)code modifierFlags:(NSUInteger)flags -{ - return [[self alloc] initWithKeyCode:code modifierFlags:flags]; -} - -+ (MASShortcut *)shortcutWithEvent:(NSEvent *)event -{ - return [[self alloc] initWithKeyCode:event.keyCode modifierFlags:event.modifierFlags]; -} - -+ (MASShortcut *)shortcutWithData:(NSData *)data -{ - id shortcut = (data ? [NSKeyedUnarchiver unarchiveObjectWithData:data] : nil); - return shortcut; -} - -#pragma mark - Shortcut accessors - -- (NSData *)data -{ - return [NSKeyedArchiver archivedDataWithRootObject:self]; -} - -- (void)setModifierFlags:(NSUInteger)value -{ - _modifierFlags = MASShortcutClear(value); -} - -- (UInt32)carbonKeyCode -{ - return (self.keyCode == NSNotFound ? 0 : (UInt32)self.keyCode); -} - -- (UInt32)carbonFlags -{ - return MASShortcutCarbonFlags(self.modifierFlags); -} - -- (NSString *)description -{ - 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_F17: return MASShortcutChar(0xF714); - case kVK_F18: return MASShortcutChar(0xF715); - case kVK_F19: return MASShortcutChar(0xF716); - case kVK_Space: return MASShortcutChar(0x20); - default: return @""; - } - } - return keyCodeString.lowercaseString; -} - -- (NSString *)keyCodeString -{ - // Some key codes don't have an equivalent - switch (self.keyCode) { - case NSNotFound: return @""; - case kVK_F1: return @"F1"; - case kVK_F2: return @"F2"; - case kVK_F3: return @"F3"; - case kVK_F4: return @"F4"; - case kVK_F5: return @"F5"; - case kVK_F6: return @"F6"; - case kVK_F7: return @"F7"; - case kVK_F8: return @"F8"; - case kVK_F9: return @"F9"; - case kVK_F10: return @"F10"; - case kVK_F11: return @"F11"; - case kVK_F12: return @"F12"; - case kVK_F13: return @"F13"; - case kVK_F14: return @"F14"; - case kVK_F15: return @"F15"; - case kVK_F16: return @"F16"; - case kVK_F17: return @"F17"; - case kVK_F18: return @"F18"; - case kVK_F19: return @"F19"; - case kVK_Space: return NSLocalizedString(@"Space", @"Shortcut glyph name for SPACE key"); - case kVK_Escape: return MASShortcutChar(kMASShortcutGlyphEscape); - case kVK_Delete: return MASShortcutChar(kMASShortcutGlyphDeleteLeft); - case kVK_ForwardDelete: return MASShortcutChar(kMASShortcutGlyphDeleteRight); - case kVK_LeftArrow: return MASShortcutChar(kMASShortcutGlyphLeftArrow); - case kVK_RightArrow: return MASShortcutChar(kMASShortcutGlyphRightArrow); - case kVK_UpArrow: return MASShortcutChar(kMASShortcutGlyphUpArrow); - case kVK_DownArrow: return MASShortcutChar(kMASShortcutGlyphDownArrow); - case kVK_Help: return MASShortcutChar(kMASShortcutGlyphHelp); - case kVK_PageUp: return MASShortcutChar(kMASShortcutGlyphPageUp); - case kVK_PageDown: return MASShortcutChar(kMASShortcutGlyphPageDown); - case kVK_Tab: return MASShortcutChar(kMASShortcutGlyphTabRight); - case kVK_Return: return MASShortcutChar(kMASShortcutGlyphReturnR2L); - - // Keypad - case kVK_ANSI_Keypad0: return @"0"; - case kVK_ANSI_Keypad1: return @"1"; - case kVK_ANSI_Keypad2: return @"2"; - case kVK_ANSI_Keypad3: return @"3"; - case kVK_ANSI_Keypad4: return @"4"; - case kVK_ANSI_Keypad5: return @"5"; - case kVK_ANSI_Keypad6: return @"6"; - case kVK_ANSI_Keypad7: return @"7"; - case kVK_ANSI_Keypad8: return @"8"; - case kVK_ANSI_Keypad9: return @"9"; - case kVK_ANSI_KeypadDecimal: return @"."; - case kVK_ANSI_KeypadMultiply: return @"*"; - case kVK_ANSI_KeypadPlus: return @"+"; - case kVK_ANSI_KeypadClear: return MASShortcutChar(kMASShortcutGlyphPadClear); - case kVK_ANSI_KeypadDivide: return @"/"; - case kVK_ANSI_KeypadEnter: return MASShortcutChar(kMASShortcutGlyphReturn); - case kVK_ANSI_KeypadMinus: return @"–"; - case kVK_ANSI_KeypadEquals: return @"="; - - // Hardcode - case 119: return MASShortcutChar(kMASShortcutGlyphSoutheastArrow); - case 115: return MASShortcutChar(kMASShortcutGlyphNorthwestArrow); - } - - // Everything else should be printable so look it up in the current keyboard - OSStatus error = noErr; - NSString *keystroke = nil; - TISInputSourceRef inputSource = TISCopyCurrentKeyboardLayoutInputSource(); - if (inputSource) { - CFDataRef layoutDataRef = TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData); - if (layoutDataRef) { - UCKeyboardLayout *layoutData = (UCKeyboardLayout *)CFDataGetBytePtr(layoutDataRef); - UniCharCount length = 0; - UniChar chars[256] = { 0 }; - UInt32 deadKeyState = 0; - error = UCKeyTranslate(layoutData, (UInt16)self.keyCode, kUCKeyActionDisplay, 0, // No modifiers - LMGetKbdType(), kUCKeyTranslateNoDeadKeysMask, &deadKeyState, - sizeof(chars) / sizeof(UniChar), &length, chars); - keystroke = ((error == noErr) && length ? [NSString stringWithCharacters:chars length:length] : @""); - } - CFRelease(inputSource); - } - - // Validate keystroke - if (keystroke.length) { - static NSMutableCharacterSet *validChars = nil; - if (validChars == nil) { - validChars = [[NSMutableCharacterSet alloc] init]; - [validChars formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]]; - [validChars formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; - [validChars formUnionWithCharacterSet:[NSCharacterSet symbolCharacterSet]]; - } - for (NSUInteger i = 0, length = keystroke.length; i < length; i++) { - if (![validChars characterIsMember:[keystroke characterAtIndex:i]]) { - keystroke = @""; - break; - } - } - } - - // Finally, we've got a shortcut! - return keystroke.uppercaseString; -} - -- (NSString *)modifierFlagsString -{ - unichar chars[4]; - NSUInteger count = 0; - // These are in the same order as the menu manager shows them - if (self.modifierFlags & NSControlKeyMask) chars[count++] = kControlUnicode; - if (self.modifierFlags & NSAlternateKeyMask) chars[count++] = kOptionUnicode; - if (self.modifierFlags & NSShiftKeyMask) chars[count++] = kShiftUnicode; - if (self.modifierFlags & NSCommandKeyMask) chars[count++] = kCommandUnicode; - 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 = (MASShortcutClear(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 = (MASShortcutClear(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 |
