diff --git a/MacPass.xcodeproj/project.pbxproj b/MacPass.xcodeproj/project.pbxproj index bb96a49a..5adf29ab 100644 --- a/MacPass.xcodeproj/project.pbxproj +++ b/MacPass.xcodeproj/project.pbxproj @@ -171,6 +171,7 @@ 4C7B63801C0CB57300D7038C /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4C7B637A1C0CB55600D7038C /* Sparkle.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4C7BD07619FE94C900C7AA5C /* MacPassImages.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4C7BD07519FE94C900C7AA5C /* MacPassImages.xcassets */; }; 4C7F8B681A10B68400CCB83D /* WelcomeWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4C7F8B6A1A10B68400CCB83D /* WelcomeWindow.xib */; }; + 4C80304A1E2FBAA300133E4C /* MPTestKeyMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C8030491E2FBAA300133E4C /* MPTestKeyMapper.m */; }; 4C811C8316ECD06E00C4BAC6 /* MPKeyfilePathControlDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C811C8216ECD06E00C4BAC6 /* MPKeyfilePathControlDelegate.m */; }; 4C83814215BF4677001AE468 /* MPDocumentWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C83814115BF4677001AE468 /* MPDocumentWindowController.m */; }; 4C888C9016EB6C91003D34A1 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4C888C8E16EB6C91003D34A1 /* Localizable.strings */; }; @@ -550,6 +551,7 @@ 4C7F8B781A10B69500CCB83D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/WelcomeWindow.strings; sourceTree = ""; }; 4C7F8B7A1A10B69700CCB83D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/WelcomeWindow.strings; sourceTree = ""; }; 4C7F8B7C1A10B69800CCB83D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/WelcomeWindow.strings; sourceTree = ""; }; + 4C8030491E2FBAA300133E4C /* MPTestKeyMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTestKeyMapper.m; sourceTree = ""; }; 4C811C8116ECD06E00C4BAC6 /* MPKeyfilePathControlDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKeyfilePathControlDelegate.h; sourceTree = ""; }; 4C811C8216ECD06E00C4BAC6 /* MPKeyfilePathControlDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKeyfilePathControlDelegate.m; sourceTree = ""; }; 4C83814015BF4677001AE468 /* MPDocumentWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPDocumentWindowController.h; sourceTree = ""; }; @@ -1006,6 +1008,7 @@ 4C45FB2C178E0BCB0010007D /* MPDatabaseLoading.m */, 4C45FB2F178E0CE20010007D /* MPTestDocument.m */, 4C6BC65F1A36717E00BDDF3D /* MPDatabaseSearch.m */, + 4C8030491E2FBAA300133E4C /* MPTestKeyMapper.m */, 4C45FB1F178E09ED0010007D /* Supporting Files */, ); path = MacPassTests; @@ -1691,6 +1694,7 @@ 4C45FB30178E0CE20010007D /* MPTestDocument.m in Sources */, 4C6BC6601A36717E00BDDF3D /* MPDatabaseSearch.m in Sources */, 4C10207F1B750E2F00BFCD59 /* MPTestAutotype.m in Sources */, + 4C80304A1E2FBAA300133E4C /* MPTestKeyMapper.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MacPass/MPAutotypeClear.m b/MacPass/MPAutotypeClear.m index 1f8d3950..106cb6f4 100644 --- a/MacPass/MPAutotypeClear.m +++ b/MacPass/MPAutotypeClear.m @@ -17,7 +17,7 @@ } - (void)execute { - CGKeyCode keyCode = [MPKeyMapper keyCodeForCharacter:@"A"]; + CGKeyCode keyCode = [MPKeyMapper keyCodeForCharacter:@"a" modifier:NULL]; if(keyCode == kMPUnknownKeyCode) { NSLog(@"Unable to generate key code for 'A'"); return; diff --git a/MacPass/MPAutotypeCommand.m b/MacPass/MPAutotypeCommand.m index 4e1bc9ff..57fc6079 100644 --- a/MacPass/MPAutotypeCommand.m +++ b/MacPass/MPAutotypeCommand.m @@ -234,7 +234,7 @@ static CGKeyCode kMPFunctionKeyCodes[] = { kVK_F1, kVK_F2, kVK_F3, kVK_F4, kVK_F NSUInteger part = random() % 2; unichar key = [pasteContent characterAtIndex:i]; - CGKeyCode keyCode = [MPKeyMapper keyCodeForCharacter:[NSString stringWithFormat:@"%c", key]]; + CGKeyCode keyCode = [MPKeyMapper keyCodeForCharacter:[NSString stringWithFormat:@"%c", key] modifier:NULL]; /* append unknown keycodes to the paste since we can't type them */ if (part == 0 || keyCode == kMPUnknownKeyCode) { @@ -394,7 +394,7 @@ static CGKeyCode kMPFunctionKeyCodes[] = { kVK_F1, kVK_F2, kVK_F3, kVK_F4, kVK_F } - (void)sendPasteKeyCode { - CGKeyCode keyCode = [MPKeyMapper keyCodeForCharacter:@"V"]; + CGKeyCode keyCode = [MPKeyMapper keyCodeForCharacter:@"v" modifier:NULL]; if(keyCode == kMPUnknownKeyCode) { return; // We did not find a mapping for "V" } diff --git a/MacPass/MPAutotypeKeyPress.m b/MacPass/MPAutotypeKeyPress.m index b2d22df7..9cc7572e 100644 --- a/MacPass/MPAutotypeKeyPress.m +++ b/MacPass/MPAutotypeKeyPress.m @@ -32,11 +32,15 @@ static CGEventFlags _updateModifierMaskForCurrentDefaults(CGEventFlags modifiers } - (instancetype)initWithModifierMask:(CGEventFlags)modiferMask character:(NSString *)character { - CGKeyCode mappedKey = [MPKeyMapper keyCodeForCharacter:character]; + CGEventFlags modifiers; + CGKeyCode mappedKey = [MPKeyMapper keyCodeForCharacter:character modifier:&modifiers]; if(mappedKey == kMPUnknownKeyCode) { self = nil; } else { + if(modifiers && (modiferMask != modifiers)) { + NSLog(@"Supplied modifiers for character %@ do not match required modifiers", character); + } self = [self initWithModifierMask:modiferMask keyCode:mappedKey]; } return self; @@ -60,9 +64,4 @@ static CGEventFlags _updateModifierMaskForCurrentDefaults(CGEventFlags modifiers //return ([self _transformKeyCode] != kMPUnknownKeyCode); } -- (CGKeyCode)_transformKeyCode { - NSString *key = [MPKeyMapper stringForKey:self.keyCode]; - return [MPKeyMapper keyCodeForCharacter:key]; -} - @end diff --git a/MacPass/MPKeyMapper.h b/MacPass/MPKeyMapper.h index de2b187f..ce034f6e 100644 --- a/MacPass/MPKeyMapper.h +++ b/MacPass/MPKeyMapper.h @@ -16,16 +16,19 @@ FOUNDATION_EXTERN uint16_t const kMPUnknownKeyCode; * Retrieves the string representation with the current keyboard mapping for the keycode * * @param keyCode The virtual keycode to be pressed + * @param modifier State of modifier flags being pressed with the key * @return NSString containing the current mapping for the keyCode */ ++ (NSString *)stringForKey:(CGKeyCode)keyCode modifier:(CGEventFlags)modifier; + (NSString *)stringForKey:(CGKeyCode)keyCode; /** - * Determines the keyCode (if possible) for the character + * Determines the keyCode (if possible) for the character. Modifiers might be needed * * @param character NSString with a single character to be transformed + * @param modifier pointer to a modifer structure to return the modifer to use with the key code for the character * @return virtual Keycode for the supplied string. If none is found, kMPUnkonwKeyCode is returned */ -+ (CGKeyCode)keyCodeForCharacter:(NSString *)character; ++ (CGKeyCode)keyCodeForCharacter:(NSString *)character modifier:(CGEventFlags *)modifer; @end diff --git a/MacPass/MPKeyMapper.m b/MacPass/MPKeyMapper.m index d4b1328a..d0d7aa63 100644 --- a/MacPass/MPKeyMapper.m +++ b/MacPass/MPKeyMapper.m @@ -28,9 +28,13 @@ uint16_t const kMPUnknownKeyCode = UINT16_MAX; @implementation MPKeyMapper + (NSString *)stringForKey:(CGKeyCode)keyCode { + return [self stringForKey:keyCode modifier:0]; +} + ++ (NSString *)stringForKey:(CGKeyCode)keyCode modifier:(CGEventFlags)modifier { TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); CFDataRef layoutData = TISGetInputSourceProperty(currentKeyboard,kTISPropertyUnicodeKeyLayoutData); - + if(!layoutData) { currentKeyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); @@ -43,21 +47,36 @@ uint16_t const kMPUnknownKeyCode = UINT16_MAX; UniChar chars[4]; UniCharCount realLength; - UCKeyTranslate(keyboardLayout, - keyCode, - kUCKeyActionDisplay, - 0, - LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, - &keysDown, - sizeof(chars) / sizeof(chars[0]), - &realLength, - chars); + + uint32_t modifierKeyState = 0; + if(modifier & kCGEventFlagMaskCommand) { + modifierKeyState |= ((cmdKey >> 8 ) & 0xFF); + } + if(modifier & kCGEventFlagMaskShift) { + modifierKeyState |= ((shiftKey >> 8) & 0xFF); + } + if(modifier & kCGEventFlagMaskAlternate) { + modifierKeyState |= ((optionKey >> 8) & 0xFF); + } + if(modifier & kCGEventFlagMaskControl) { + modifierKeyState |= ((controlKey >> 8) & 0xFF); + } + OSStatus success = 0; + success = UCKeyTranslate(keyboardLayout, + keyCode, + kUCKeyActionDisplay, + modifierKeyState, + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &keysDown, + sizeof(chars) / sizeof(chars[0]), + &realLength, + chars); return CFBridgingRelease(CFStringCreateWithCharacters(kCFAllocatorDefault, chars, 1)); } -+ (CGKeyCode)keyCodeForCharacter:(NSString *)character { ++ (CGKeyCode)keyCodeForCharacter:(NSString *)character modifier:(CGEventFlags *)modifer { static NSMutableDictionary *keyboardCodeDictionary; TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); @@ -70,26 +89,38 @@ uint16_t const kMPUnknownKeyCode = UINT16_MAX; keyboardCodeDictionary = [[NSMutableDictionary alloc] initWithCapacity:2]; } /* search for current character mapping */ - NSDictionary *charToCodeDict = keyboardCodeDictionary[localizedName]; - + NSDictionary *> *charToCodeDict = keyboardCodeDictionary[localizedName]; + if(nil == charToCodeDict) { /* Add mapping */ NSMutableDictionary *tempCharToCodeDict = [[NSMutableDictionary alloc] initWithCapacity:128]; /* Generate table of keycodes and characters. */ /* Loop through every keycode (0 - 127) to find its current mapping. */ + /* Loop throuhg every control key compbination for every virutal key */ for(CGKeyCode keyCode = 0; keyCode < 128; ++keyCode) { - NSString *string = [self stringForKey:keyCode]; - if(string != nil) { - tempCharToCodeDict[string] = @(keyCode); + uint64_t modifiers[] = { 0, kCGEventFlagMaskShift, kCGEventFlagMaskAlternate, kCGEventFlagMaskControl, kCGEventFlagMaskShift | kCGEventFlagMaskAlternate, kCGEventFlagMaskShift | kCGEventFlagMaskControl, kCGEventFlagMaskShift | kCGEventFlagMaskAlternate | kCGEventFlagMaskControl }; + for(int modifierIndex = 0; modifierIndex < sizeof(modifiers); modifierIndex++) { + NSString *string = [self stringForKey:keyCode modifier:modifiers[modifierIndex]]; + if(string != nil && string.length > 0 && nil == tempCharToCodeDict[string]) { + tempCharToCodeDict[string] = @[@(keyCode), @(modifiers[modifierIndex])]; + } } } charToCodeDict = [[NSDictionary alloc] initWithDictionary:tempCharToCodeDict]; keyboardCodeDictionary[localizedName] = charToCodeDict; } NSString *singleCharacter = [character substringToIndex:1].lowercaseString; - if(charToCodeDict[singleCharacter]) { - return charToCodeDict[singleCharacter].integerValue; + NSArray *result = charToCodeDict[singleCharacter]; + if(result) { + if(modifer) { + *modifer = result[1].unsignedIntegerValue; + } + /* false positive when no modifier was supplied! */ + return result[0].integerValue; + } + if(modifer) { + *modifer = 0; } return kMPUnknownKeyCode; } diff --git a/MacPassTests/MPTestKeyMapper.m b/MacPassTests/MPTestKeyMapper.m new file mode 100644 index 00000000..7b4506e2 --- /dev/null +++ b/MacPassTests/MPTestKeyMapper.m @@ -0,0 +1,41 @@ +// +// MPTestKeyMapper.m +// MacPass +// +// Created by Michael Starke on 18/01/2017. +// Copyright © 2017 HicknHack Software GmbH. All rights reserved. +// + +#import +#import + +#import "MPKeyMapper.h" + + +@interface MPTestKeyMapper : XCTestCase + +@end + +@implementation MPTestKeyMapper + +- (void)testKeyMapper { + CGEventFlags flags = 0; + CGKeyCode keyCode = [MPKeyMapper keyCodeForCharacter:@"A" modifier:&flags]; + XCTAssertEqual(kVK_ANSI_A, keyCode); + XCTAssertEqual(kCGEventFlagMaskShift, flags); + + /* Test only works for german keyboard layout! + XCTAssertEqualObjects(@"a",[MPKeyMapper stringForKey:kVK_ANSI_A modifier:0]); + XCTAssertEqualObjects(@"A",[MPKeyMapper stringForKey:kVK_ANSI_A modifier:kCGEventFlagMaskShift]); + + XCTAssertEqualObjects(@"8",[MPKeyMapper stringForKey:kVK_ANSI_8 modifier:0]); + XCTAssertEqualObjects(@"(",[MPKeyMapper stringForKey:kVK_ANSI_8 modifier:kCGEventFlagMaskShift]); + XCTAssertEqualObjects(@"{",[MPKeyMapper stringForKey:kVK_ANSI_8 modifier:kCGEventFlagMaskAlternate]); + + XCTAssertEqualObjects(@"n",[MPKeyMapper stringForKey:kVK_ANSI_N modifier:0]); + XCTAssertEqualObjects(@"N",[MPKeyMapper stringForKey:kVK_ANSI_N modifier:kCGEventFlagMaskShift]); + XCTAssertEqualObjects(@"~",[MPKeyMapper stringForKey:kVK_ANSI_N modifier:kCGEventFlagMaskAlternate]); + */ +} + +@end