Extended key mapper to include modifiers as well

This commit is contained in:
michael starke
2017-01-18 18:30:10 +01:00
parent 334e92d1b4
commit c9b3637c24
7 changed files with 108 additions and 30 deletions

View File

@@ -171,6 +171,7 @@
4C7B63801C0CB57300D7038C /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4C7B637A1C0CB55600D7038C /* Sparkle.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 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 */; }; 4C7BD07619FE94C900C7AA5C /* MacPassImages.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4C7BD07519FE94C900C7AA5C /* MacPassImages.xcassets */; };
4C7F8B681A10B68400CCB83D /* WelcomeWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4C7F8B6A1A10B68400CCB83D /* WelcomeWindow.xib */; }; 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 */; }; 4C811C8316ECD06E00C4BAC6 /* MPKeyfilePathControlDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C811C8216ECD06E00C4BAC6 /* MPKeyfilePathControlDelegate.m */; };
4C83814215BF4677001AE468 /* MPDocumentWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C83814115BF4677001AE468 /* MPDocumentWindowController.m */; }; 4C83814215BF4677001AE468 /* MPDocumentWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C83814115BF4677001AE468 /* MPDocumentWindowController.m */; };
4C888C9016EB6C91003D34A1 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4C888C8E16EB6C91003D34A1 /* Localizable.strings */; }; 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 = "<group>"; }; 4C7F8B781A10B69500CCB83D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/WelcomeWindow.strings; sourceTree = "<group>"; };
4C7F8B7A1A10B69700CCB83D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/WelcomeWindow.strings; sourceTree = "<group>"; }; 4C7F8B7A1A10B69700CCB83D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/WelcomeWindow.strings; sourceTree = "<group>"; };
4C7F8B7C1A10B69800CCB83D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/WelcomeWindow.strings; sourceTree = "<group>"; }; 4C7F8B7C1A10B69800CCB83D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/WelcomeWindow.strings; sourceTree = "<group>"; };
4C8030491E2FBAA300133E4C /* MPTestKeyMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTestKeyMapper.m; sourceTree = "<group>"; };
4C811C8116ECD06E00C4BAC6 /* MPKeyfilePathControlDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKeyfilePathControlDelegate.h; sourceTree = "<group>"; }; 4C811C8116ECD06E00C4BAC6 /* MPKeyfilePathControlDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKeyfilePathControlDelegate.h; sourceTree = "<group>"; };
4C811C8216ECD06E00C4BAC6 /* MPKeyfilePathControlDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKeyfilePathControlDelegate.m; sourceTree = "<group>"; }; 4C811C8216ECD06E00C4BAC6 /* MPKeyfilePathControlDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKeyfilePathControlDelegate.m; sourceTree = "<group>"; };
4C83814015BF4677001AE468 /* MPDocumentWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPDocumentWindowController.h; sourceTree = "<group>"; }; 4C83814015BF4677001AE468 /* MPDocumentWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPDocumentWindowController.h; sourceTree = "<group>"; };
@@ -1006,6 +1008,7 @@
4C45FB2C178E0BCB0010007D /* MPDatabaseLoading.m */, 4C45FB2C178E0BCB0010007D /* MPDatabaseLoading.m */,
4C45FB2F178E0CE20010007D /* MPTestDocument.m */, 4C45FB2F178E0CE20010007D /* MPTestDocument.m */,
4C6BC65F1A36717E00BDDF3D /* MPDatabaseSearch.m */, 4C6BC65F1A36717E00BDDF3D /* MPDatabaseSearch.m */,
4C8030491E2FBAA300133E4C /* MPTestKeyMapper.m */,
4C45FB1F178E09ED0010007D /* Supporting Files */, 4C45FB1F178E09ED0010007D /* Supporting Files */,
); );
path = MacPassTests; path = MacPassTests;
@@ -1691,6 +1694,7 @@
4C45FB30178E0CE20010007D /* MPTestDocument.m in Sources */, 4C45FB30178E0CE20010007D /* MPTestDocument.m in Sources */,
4C6BC6601A36717E00BDDF3D /* MPDatabaseSearch.m in Sources */, 4C6BC6601A36717E00BDDF3D /* MPDatabaseSearch.m in Sources */,
4C10207F1B750E2F00BFCD59 /* MPTestAutotype.m in Sources */, 4C10207F1B750E2F00BFCD59 /* MPTestAutotype.m in Sources */,
4C80304A1E2FBAA300133E4C /* MPTestKeyMapper.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@@ -17,7 +17,7 @@
} }
- (void)execute { - (void)execute {
CGKeyCode keyCode = [MPKeyMapper keyCodeForCharacter:@"A"]; CGKeyCode keyCode = [MPKeyMapper keyCodeForCharacter:@"a" modifier:NULL];
if(keyCode == kMPUnknownKeyCode) { if(keyCode == kMPUnknownKeyCode) {
NSLog(@"Unable to generate key code for 'A'"); NSLog(@"Unable to generate key code for 'A'");
return; return;

View File

@@ -234,7 +234,7 @@ static CGKeyCode kMPFunctionKeyCodes[] = { kVK_F1, kVK_F2, kVK_F3, kVK_F4, kVK_F
NSUInteger part = random() % 2; NSUInteger part = random() % 2;
unichar key = [pasteContent characterAtIndex:i]; 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 */ /* append unknown keycodes to the paste since we can't type them */
if (part == 0 || keyCode == kMPUnknownKeyCode) { if (part == 0 || keyCode == kMPUnknownKeyCode) {
@@ -394,7 +394,7 @@ static CGKeyCode kMPFunctionKeyCodes[] = { kVK_F1, kVK_F2, kVK_F3, kVK_F4, kVK_F
} }
- (void)sendPasteKeyCode { - (void)sendPasteKeyCode {
CGKeyCode keyCode = [MPKeyMapper keyCodeForCharacter:@"V"]; CGKeyCode keyCode = [MPKeyMapper keyCodeForCharacter:@"v" modifier:NULL];
if(keyCode == kMPUnknownKeyCode) { if(keyCode == kMPUnknownKeyCode) {
return; // We did not find a mapping for "V" return; // We did not find a mapping for "V"
} }

View File

@@ -32,11 +32,15 @@ static CGEventFlags _updateModifierMaskForCurrentDefaults(CGEventFlags modifiers
} }
- (instancetype)initWithModifierMask:(CGEventFlags)modiferMask character:(NSString *)character { - (instancetype)initWithModifierMask:(CGEventFlags)modiferMask character:(NSString *)character {
CGKeyCode mappedKey = [MPKeyMapper keyCodeForCharacter:character]; CGEventFlags modifiers;
CGKeyCode mappedKey = [MPKeyMapper keyCodeForCharacter:character modifier:&modifiers];
if(mappedKey == kMPUnknownKeyCode) { if(mappedKey == kMPUnknownKeyCode) {
self = nil; self = nil;
} }
else { else {
if(modifiers && (modiferMask != modifiers)) {
NSLog(@"Supplied modifiers for character %@ do not match required modifiers", character);
}
self = [self initWithModifierMask:modiferMask keyCode:mappedKey]; self = [self initWithModifierMask:modiferMask keyCode:mappedKey];
} }
return self; return self;
@@ -60,9 +64,4 @@ static CGEventFlags _updateModifierMaskForCurrentDefaults(CGEventFlags modifiers
//return ([self _transformKeyCode] != kMPUnknownKeyCode); //return ([self _transformKeyCode] != kMPUnknownKeyCode);
} }
- (CGKeyCode)_transformKeyCode {
NSString *key = [MPKeyMapper stringForKey:self.keyCode];
return [MPKeyMapper keyCodeForCharacter:key];
}
@end @end

View File

@@ -16,16 +16,19 @@ FOUNDATION_EXTERN uint16_t const kMPUnknownKeyCode;
* Retrieves the string representation with the current keyboard mapping for the keycode * Retrieves the string representation with the current keyboard mapping for the keycode
* *
* @param keyCode The virtual keycode to be pressed * @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 * @return NSString containing the current mapping for the keyCode
*/ */
+ (NSString *)stringForKey:(CGKeyCode)keyCode modifier:(CGEventFlags)modifier;
+ (NSString *)stringForKey:(CGKeyCode)keyCode; + (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 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 * @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 @end

View File

@@ -28,6 +28,10 @@ uint16_t const kMPUnknownKeyCode = UINT16_MAX;
@implementation MPKeyMapper @implementation MPKeyMapper
+ (NSString *)stringForKey:(CGKeyCode)keyCode { + (NSString *)stringForKey:(CGKeyCode)keyCode {
return [self stringForKey:keyCode modifier:0];
}
+ (NSString *)stringForKey:(CGKeyCode)keyCode modifier:(CGEventFlags)modifier {
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
CFDataRef layoutData = TISGetInputSourceProperty(currentKeyboard,kTISPropertyUnicodeKeyLayoutData); CFDataRef layoutData = TISGetInputSourceProperty(currentKeyboard,kTISPropertyUnicodeKeyLayoutData);
@@ -43,10 +47,25 @@ uint16_t const kMPUnknownKeyCode = UINT16_MAX;
UniChar chars[4]; UniChar chars[4];
UniCharCount realLength; UniCharCount realLength;
UCKeyTranslate(keyboardLayout,
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, keyCode,
kUCKeyActionDisplay, kUCKeyActionDisplay,
0, modifierKeyState,
LMGetKbdType(), LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit, kUCKeyTranslateNoDeadKeysBit,
&keysDown, &keysDown,
@@ -57,7 +76,7 @@ uint16_t const kMPUnknownKeyCode = UINT16_MAX;
return CFBridgingRelease(CFStringCreateWithCharacters(kCFAllocatorDefault, chars, 1)); return CFBridgingRelease(CFStringCreateWithCharacters(kCFAllocatorDefault, chars, 1));
} }
+ (CGKeyCode)keyCodeForCharacter:(NSString *)character { + (CGKeyCode)keyCodeForCharacter:(NSString *)character modifier:(CGEventFlags *)modifer {
static NSMutableDictionary *keyboardCodeDictionary; static NSMutableDictionary *keyboardCodeDictionary;
TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
@@ -70,7 +89,7 @@ uint16_t const kMPUnknownKeyCode = UINT16_MAX;
keyboardCodeDictionary = [[NSMutableDictionary alloc] initWithCapacity:2]; keyboardCodeDictionary = [[NSMutableDictionary alloc] initWithCapacity:2];
} }
/* search for current character mapping */ /* search for current character mapping */
NSDictionary<NSString *, NSNumber *> *charToCodeDict = keyboardCodeDictionary[localizedName]; NSDictionary<NSString *, NSArray<NSNumber *> *> *charToCodeDict = keyboardCodeDictionary[localizedName];
if(nil == charToCodeDict) { if(nil == charToCodeDict) {
/* Add mapping */ /* Add mapping */
@@ -78,18 +97,30 @@ uint16_t const kMPUnknownKeyCode = UINT16_MAX;
/* Generate table of keycodes and characters. */ /* Generate table of keycodes and characters. */
/* Loop through every keycode (0 - 127) to find its current mapping. */ /* 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) { for(CGKeyCode keyCode = 0; keyCode < 128; ++keyCode) {
NSString *string = [self stringForKey:keyCode]; uint64_t modifiers[] = { 0, kCGEventFlagMaskShift, kCGEventFlagMaskAlternate, kCGEventFlagMaskControl, kCGEventFlagMaskShift | kCGEventFlagMaskAlternate, kCGEventFlagMaskShift | kCGEventFlagMaskControl, kCGEventFlagMaskShift | kCGEventFlagMaskAlternate | kCGEventFlagMaskControl };
if(string != nil) { for(int modifierIndex = 0; modifierIndex < sizeof(modifiers); modifierIndex++) {
tempCharToCodeDict[string] = @(keyCode); 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]; charToCodeDict = [[NSDictionary alloc] initWithDictionary:tempCharToCodeDict];
keyboardCodeDictionary[localizedName] = charToCodeDict; keyboardCodeDictionary[localizedName] = charToCodeDict;
} }
NSString *singleCharacter = [character substringToIndex:1].lowercaseString; NSString *singleCharacter = [character substringToIndex:1].lowercaseString;
if(charToCodeDict[singleCharacter]) { NSArray<NSNumber *> *result = charToCodeDict[singleCharacter];
return charToCodeDict[singleCharacter].integerValue; 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; return kMPUnknownKeyCode;
} }

View File

@@ -0,0 +1,41 @@
//
// MPTestKeyMapper.m
// MacPass
//
// Created by Michael Starke on 18/01/2017.
// Copyright © 2017 HicknHack Software GmbH. All rights reserved.
//
#import <XCTest/XCTest.h>
#import <Carbon/Carbon.h>
#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