// // SRKeyCodeTransformer.h // ShortcutRecorder // // Copyright 2006-2012 Contributors. All rights reserved. // // License: BSD // // Contributors: // David Dauer // Jesper // Jamie Kirkpatrick // Ilya Kulakov // Silvio Rizzi #import "SRKeyCodeTransformer.h" #import "SRCommon.h" FOUNDATION_STATIC_INLINE NSString* _SRUnicharToString(unichar aChar) { return [NSString stringWithFormat: @"%C", aChar]; } @implementation SRKeyCodeTransformer - (instancetype)initWithASCIICapableKeyboardInputSource:(BOOL)aUsesASCII plainStrings:(BOOL)aUsesPlainStrings { self = [super init]; if (self) { _usesASCIICapableKeyboardInputSource = aUsesASCII; _usesPlainStrings = aUsesPlainStrings; } return self; } - (instancetype)init { return [self initWithASCIICapableKeyboardInputSource:NO plainStrings:NO]; } #pragma mark Methods + (instancetype)sharedTransformer { static dispatch_once_t OnceToken; static SRKeyCodeTransformer *Transformer = nil; dispatch_once(&OnceToken, ^{ Transformer = [[self alloc] initWithASCIICapableKeyboardInputSource:NO plainStrings:NO]; }); return Transformer; } + (instancetype)sharedASCIITransformer { static dispatch_once_t OnceToken; static SRKeyCodeTransformer *Transformer = nil; dispatch_once(&OnceToken, ^{ Transformer = [[self alloc] initWithASCIICapableKeyboardInputSource:YES plainStrings:NO]; }); return Transformer; } + (instancetype)sharedPlainTransformer { static dispatch_once_t OnceToken; static SRKeyCodeTransformer *Transformer = nil; dispatch_once(&OnceToken, ^{ Transformer = [[self alloc] initWithASCIICapableKeyboardInputSource:NO plainStrings:YES]; }); return Transformer; } + (SRKeyCodeTransformer *)sharedPlainASCIITransformer { static dispatch_once_t OnceToken; static SRKeyCodeTransformer *Transformer = nil; dispatch_once(&OnceToken, ^{ Transformer = [[self alloc] initWithASCIICapableKeyboardInputSource:YES plainStrings:YES]; }); return Transformer; } + (NSDictionary *)specialKeyCodesToUnicodeCharactersMapping { // Most of these keys are system constans. // Values for rest of the keys were given by setting key equivalents in IB. static dispatch_once_t OnceToken; static NSDictionary *Mapping = nil; dispatch_once(&OnceToken, ^{ Mapping = @{ @(kVK_F1): _SRUnicharToString(NSF1FunctionKey), @(kVK_F2): _SRUnicharToString(NSF2FunctionKey), @(kVK_F3): _SRUnicharToString(NSF3FunctionKey), @(kVK_F4): _SRUnicharToString(NSF4FunctionKey), @(kVK_F5): _SRUnicharToString(NSF5FunctionKey), @(kVK_F6): _SRUnicharToString(NSF6FunctionKey), @(kVK_F7): _SRUnicharToString(NSF7FunctionKey), @(kVK_F8): _SRUnicharToString(NSF8FunctionKey), @(kVK_F9): _SRUnicharToString(NSF9FunctionKey), @(kVK_F10): _SRUnicharToString(NSF10FunctionKey), @(kVK_F11): _SRUnicharToString(NSF11FunctionKey), @(kVK_F12): _SRUnicharToString(NSF12FunctionKey), @(kVK_F13): _SRUnicharToString(NSF13FunctionKey), @(kVK_F14): _SRUnicharToString(NSF14FunctionKey), @(kVK_F15): _SRUnicharToString(NSF15FunctionKey), @(kVK_F16): _SRUnicharToString(NSF16FunctionKey), @(kVK_F17): _SRUnicharToString(NSF17FunctionKey), @(kVK_F18): _SRUnicharToString(NSF18FunctionKey), @(kVK_F19): _SRUnicharToString(NSF19FunctionKey), @(kVK_F20): _SRUnicharToString(NSF20FunctionKey), @(kVK_Space): _SRUnicharToString(' '), @(kVK_Delete): _SRUnicharToString(NSBackspaceCharacter), @(kVK_ForwardDelete): _SRUnicharToString(NSDeleteCharacter), @(kVK_ANSI_KeypadClear): _SRUnicharToString(NSClearLineFunctionKey), @(kVK_LeftArrow): _SRUnicharToString(NSLeftArrowFunctionKey), @(kVK_RightArrow): _SRUnicharToString(NSRightArrowFunctionKey), @(kVK_UpArrow): _SRUnicharToString(NSUpArrowFunctionKey), @(kVK_DownArrow): _SRUnicharToString(NSDownArrowFunctionKey), @(kVK_End): _SRUnicharToString(NSEndFunctionKey), @(kVK_Home): _SRUnicharToString(NSHomeFunctionKey), @(kVK_Escape): _SRUnicharToString('\e'), @(kVK_PageDown): _SRUnicharToString(NSPageDownFunctionKey), @(kVK_PageUp): _SRUnicharToString(NSPageUpFunctionKey), @(kVK_Return): _SRUnicharToString(NSCarriageReturnCharacter), @(kVK_ANSI_KeypadEnter): _SRUnicharToString(NSEnterCharacter), @(kVK_Tab): _SRUnicharToString(NSTabCharacter), @(kVK_Help): _SRUnicharToString(NSHelpFunctionKey) }; }); return Mapping; } + (NSDictionary *)specialKeyCodesToPlainStringsMapping { static dispatch_once_t OnceToken; static NSDictionary *Mapping = nil; dispatch_once(&OnceToken, ^{ Mapping = @{ @(kVK_F1): @"F1", @(kVK_F2): @"F2", @(kVK_F3): @"F3", @(kVK_F4): @"F4", @(kVK_F5): @"F5", @(kVK_F6): @"F6", @(kVK_F7): @"F7", @(kVK_F8): @"F8", @(kVK_F9): @"F9", @(kVK_F10): @"F10", @(kVK_F11): @"F11", @(kVK_F12): @"F12", @(kVK_F13): @"F13", @(kVK_F14): @"F14", @(kVK_F15): @"F15", @(kVK_F16): @"F16", @(kVK_F17): @"F17", @(kVK_F18): @"F18", @(kVK_F19): @"F19", @(kVK_F20): @"F20", @(kVK_Space): SRLoc(@"Space"), @(kVK_Delete): _SRUnicharToString(SRKeyCodeGlyphDeleteLeft), @(kVK_ForwardDelete): _SRUnicharToString(SRKeyCodeGlyphDeleteRight), @(kVK_ANSI_KeypadClear): _SRUnicharToString(SRKeyCodeGlyphPadClear), @(kVK_LeftArrow): _SRUnicharToString(SRKeyCodeGlyphLeftArrow), @(kVK_RightArrow): _SRUnicharToString(SRKeyCodeGlyphRightArrow), @(kVK_UpArrow): _SRUnicharToString(SRKeyCodeGlyphUpArrow), @(kVK_DownArrow): _SRUnicharToString(SRKeyCodeGlyphDownArrow), @(kVK_End): _SRUnicharToString(SRKeyCodeGlyphSoutheastArrow), @(kVK_Home): _SRUnicharToString(SRKeyCodeGlyphNorthwestArrow), @(kVK_Escape): _SRUnicharToString(SRKeyCodeGlyphEscape), @(kVK_PageDown): _SRUnicharToString(SRKeyCodeGlyphPageDown), @(kVK_PageUp): _SRUnicharToString(SRKeyCodeGlyphPageUp), @(kVK_Return): _SRUnicharToString(SRKeyCodeGlyphReturnR2L), @(kVK_ANSI_KeypadEnter): _SRUnicharToString(SRKeyCodeGlyphReturn), @(kVK_Tab): _SRUnicharToString(SRKeyCodeGlyphTabRight), @(kVK_Help): @"?⃝" }; }); return Mapping; } - (BOOL)isKeyCodeSpecial:(unsigned short)aKeyCode { switch (aKeyCode) { case kVK_F1: case kVK_F2: case kVK_F3: case kVK_F4: case kVK_F5: case kVK_F6: case kVK_F7: case kVK_F8: case kVK_F9: case kVK_F10: case kVK_F11: case kVK_F12: case kVK_F13: case kVK_F14: case kVK_F15: case kVK_F16: case kVK_F17: case kVK_F18: case kVK_F19: case kVK_F20: case kVK_Space: case kVK_Delete: case kVK_ForwardDelete: case kVK_ANSI_KeypadClear: case kVK_LeftArrow: case kVK_RightArrow: case kVK_UpArrow: case kVK_DownArrow: case kVK_End: case kVK_Home: case kVK_Escape: case kVK_PageDown: case kVK_PageUp: case kVK_Return: case kVK_ANSI_KeypadEnter: case kVK_Tab: case kVK_Help: return YES; default: return NO; } } #pragma mark NSValueTransformer + (BOOL)allowsReverseTransformation { return NO; } + (Class)transformedValueClass; { return [NSString class]; } - (NSString *)transformedValue:(NSNumber *)aValue { return [self transformedValue:aValue withModifierFlags:nil]; } - (NSString *)transformedValue:(NSNumber *)aValue withModifierFlags:(NSNumber *)aModifierFlags { return [self transformedValue:aValue withImplicitModifierFlags:aModifierFlags explicitModifierFlags:nil]; } - (NSString *)transformedValue:(NSNumber *)aValue withImplicitModifierFlags:(NSNumber *)anImplicitModifierFlags explicitModifierFlags:(NSNumber *)anExplicitModifierFlags { if ([anImplicitModifierFlags unsignedIntegerValue] & [anExplicitModifierFlags unsignedIntegerValue] & SRCocoaModifierFlagsMask) { [NSException raise:NSInvalidArgumentException format:@"anImplicitModifierFlags and anExplicitModifierFlags MUST NOT have common elements"]; } if (![aValue isKindOfClass:[NSNumber class]]) return @""; // Some key codes cannot be translated directly. NSString *unmappedString = [self transformedSpecialKeyCode:aValue withExplicitModifierFlags:anExplicitModifierFlags]; if (unmappedString) return unmappedString; CFDataRef layoutData = NULL; if (self.usesASCIICapableKeyboardInputSource) { TISInputSourceRef tisSource = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); if (!tisSource) return @""; layoutData = (CFDataRef)TISGetInputSourceProperty(tisSource, kTISPropertyUnicodeKeyLayoutData); CFRelease(tisSource); } else { TISInputSourceRef tisSource = TISCopyCurrentKeyboardLayoutInputSource(); if (!tisSource) return @""; layoutData = (CFDataRef)TISGetInputSourceProperty(tisSource, kTISPropertyUnicodeKeyLayoutData); CFRelease(tisSource); // For non-unicode layouts such as Chinese, Japanese, and Korean, get the ASCII capable layout if (!layoutData) { tisSource = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); if (!tisSource) return @""; layoutData = (CFDataRef)TISGetInputSourceProperty(tisSource, kTISPropertyUnicodeKeyLayoutData); CFRelease(tisSource); } } if (!layoutData) return @""; const UCKeyboardLayout *keyLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); static const UniCharCount MaxLength = 255; UniCharCount actualLength = 0; UniChar chars[MaxLength] = {0}; UInt32 deadKeyState = 0; OSStatus err = UCKeyTranslate(keyLayout, [aValue unsignedShortValue], kUCKeyActionDisplay, SRCocoaToCarbonFlags([anImplicitModifierFlags unsignedIntegerValue]) >> 8, LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &deadKeyState, sizeof(chars) / sizeof(UniChar), &actualLength, chars); if (err != noErr) return @""; if (self.usesPlainStrings) return [[NSString stringWithCharacters:chars length:actualLength] uppercaseString]; else return [NSString stringWithCharacters:chars length:actualLength]; } - (NSString *)transformedSpecialKeyCode:(NSNumber *)aKeyCode withExplicitModifierFlags:(NSNumber *)anExplicitModifierFlags { if ([anExplicitModifierFlags unsignedIntegerValue] & NSShiftKeyMask && [aKeyCode unsignedShortValue] == kVK_Tab) { if (self.usesPlainStrings) return _SRUnicharToString(SRKeyCodeGlyphTabLeft); else return _SRUnicharToString(NSBackTabCharacter); } if (self.usesPlainStrings) return [[self class] specialKeyCodesToPlainStringsMapping][aKeyCode]; else return [[self class] specialKeyCodesToUnicodeCharactersMapping][aKeyCode]; } @end