From afc69f69aeac28cb2e7333a9346b13339219745e Mon Sep 17 00:00:00 2001 From: Michael Starke Date: Mon, 5 Nov 2018 11:55:07 +0100 Subject: [PATCH] Autotype now uses key presses instead of paste whenever possible. --- MacPass.xcodeproj/project.pbxproj | 30 +- MacPass/MPAutotypeClear.m | 8 +- MacPass/MPAutotypeCommand.h | 18 - MacPass/MPAutotypeCommand.m | 419 +---------------- MacPass/MPAutotypeKeyPress.h | 26 ++ MacPass/MPAutotypeKeyPress.m | 48 +- MacPass/MPAutotypeParser.h | 25 + MacPass/MPAutotypeParser.m | 428 ++++++++++++++++++ MacPass/MPAutotypePaste.m | 3 +- MacPass/MPKeyMapper.h | 2 - MacPass/MPKeyMapper.m | 8 +- MacPass/MPKeyTyper.h | 36 ++ MacPass/MPKeyTyper.m | 79 ++++ MacPass/MPModifiedKey.h | 6 + MacPass/MPModifiedKey.m | 9 +- .../NSString+MPComposedCharacterAdditions.m | 4 +- MacPass/NSString+MPPasswordCreation.m | 2 +- MacPassTests/MPTestAutotype.m | 378 ++++++++++++++-- 18 files changed, 1008 insertions(+), 521 deletions(-) create mode 100644 MacPass/MPAutotypeParser.h create mode 100644 MacPass/MPAutotypeParser.m create mode 100644 MacPass/MPKeyTyper.h create mode 100644 MacPass/MPKeyTyper.m diff --git a/MacPass.xcodeproj/project.pbxproj b/MacPass.xcodeproj/project.pbxproj index f5efef3f..6aa7db50 100644 --- a/MacPass.xcodeproj/project.pbxproj +++ b/MacPass.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ 4C2E382616D1470200037A9D /* MPViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C2E382516D1470200037A9D /* MPViewController.m */; }; 4C2F17A21FD69BCA0097418D /* MPUserNotificationCenterDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C2F17A11FD69BCA0097418D /* MPUserNotificationCenterDelegate.m */; }; 4C32B0E71A1D4436007E12F1 /* KPKFormat+MPUTIDetection.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B0E61A1D4436007E12F1 /* KPKFormat+MPUTIDetection.m */; }; + 4C349A00218852160055AF45 /* MPKeyTyper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C3499FF218852160055AF45 /* MPKeyTyper.m */; }; 4C3666411787327E00B249F1 /* MPDocument+Attachments.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C3666401787327E00B249F1 /* MPDocument+Attachments.m */; }; 4C370EFE215B76CB00703AAE /* MPOutlineTableCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C370EFD215B76CB00703AAE /* MPOutlineTableCellView.m */; }; 4C37A84015B8B474005EF8EE /* MPOutlineDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C37A83F15B8B474005EF8EE /* MPOutlineDataSource.m */; }; @@ -131,6 +132,7 @@ 4C586FA016D07D7200E7DB57 /* 01_PackageNetworkTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 4C586F9F16D07D7200E7DB57 /* 01_PackageNetworkTemplate.pdf */; }; 4C586FA216D07F6A00E7DB57 /* 02_MessageBoxWarningTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 4C586FA116D07F6A00E7DB57 /* 02_MessageBoxWarningTemplate.pdf */; }; 4C5A11FE1708DE8700223D8A /* MPPasswordCreatorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C5A11FC1708DE8700223D8A /* MPPasswordCreatorViewController.m */; }; + 4C5EF816218CA03F0003C00E /* MPAutotypeParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C5EF815218CA03F0003C00E /* MPAutotypeParser.m */; }; 4C5FE9AE17843CE20001D5A8 /* MPSelectedAttachmentTableCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C5FE9AD17843CE20001D5A8 /* MPSelectedAttachmentTableCellView.m */; }; 4C61EA0316D2FD0800AC519E /* MPOutlineViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C61EA0216D2FD0800AC519E /* MPOutlineViewController.m */; }; 4C61EA0516D2FFE200AC519E /* OutlineView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4C61EA0416D2FFE200AC519E /* OutlineView.xib */; }; @@ -398,6 +400,8 @@ 4C2F17A11FD69BCA0097418D /* MPUserNotificationCenterDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPUserNotificationCenterDelegate.m; sourceTree = ""; }; 4C32B0E51A1D4436007E12F1 /* KPKFormat+MPUTIDetection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "KPKFormat+MPUTIDetection.h"; sourceTree = ""; }; 4C32B0E61A1D4436007E12F1 /* KPKFormat+MPUTIDetection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "KPKFormat+MPUTIDetection.m"; sourceTree = ""; }; + 4C3499FE218852160055AF45 /* MPKeyTyper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPKeyTyper.h; sourceTree = ""; }; + 4C3499FF218852160055AF45 /* MPKeyTyper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPKeyTyper.m; sourceTree = ""; }; 4C3666401787327E00B249F1 /* MPDocument+Attachments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPDocument+Attachments.m"; sourceTree = ""; }; 4C370EFC215B76CB00703AAE /* MPOutlineTableCellView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPOutlineTableCellView.h; sourceTree = ""; }; 4C370EFD215B76CB00703AAE /* MPOutlineTableCellView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPOutlineTableCellView.m; sourceTree = ""; }; @@ -535,6 +539,8 @@ 4C5CD34617D15912000B7F38 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/MainMenu.strings; sourceTree = ""; }; 4C5CD34717D1591A000B7F38 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/PasswordInputView.strings; sourceTree = ""; }; 4C5CD34817D15920000B7F38 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InspectorView.strings; sourceTree = ""; }; + 4C5EF814218CA03F0003C00E /* MPAutotypeParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPAutotypeParser.h; sourceTree = ""; }; + 4C5EF815218CA03F0003C00E /* MPAutotypeParser.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPAutotypeParser.m; sourceTree = ""; }; 4C5F72851FC4351E00929153 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InspectorView.strings; sourceTree = ""; }; 4C5FE9AC17843CE20001D5A8 /* MPSelectedAttachmentTableCellView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSelectedAttachmentTableCellView.h; sourceTree = ""; }; 4C5FE9AD17843CE20001D5A8 /* MPSelectedAttachmentTableCellView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSelectedAttachmentTableCellView.m; sourceTree = ""; }; @@ -1351,6 +1357,18 @@ path = Icons; sourceTree = ""; }; + 4C5EF817218CA0470003C00E /* Parser */ = { + isa = PBXGroup; + children = ( + 4C8F0C6F1FCEF91400BE157F /* MPPickcharsParser.h */, + 4C088C401FD9A42800F92502 /* MPPickcharsParser_Private.h */, + 4C8F0C701FCEF91400BE157F /* MPPickcharsParser.m */, + 4C5EF814218CA03F0003C00E /* MPAutotypeParser.h */, + 4C5EF815218CA03F0003C00E /* MPAutotypeParser.m */, + ); + name = Parser; + sourceTree = ""; + }; 4C6AEF041A0441F800CA2420 /* AccessoryViews */ = { isa = PBXGroup; children = ( @@ -1476,6 +1494,7 @@ 4C89F525182FB4C50069C73C /* Autotype */ = { isa = PBXGroup; children = ( + 4C5EF817218CA0470003C00E /* Parser */, 4C90757B18A42E7A00E598DA /* Commands */, 4CEE46DB181C301D006BF1E5 /* MPAutotypeDaemon.h */, 4CEE46DC181C301D006BF1E5 /* MPAutotypeDaemon.m */, @@ -1483,11 +1502,10 @@ 4CD2B9051849424B0051B395 /* MPAutotypeContext.m */, 4CA3530918A53CB800839B0F /* MPKeyMapper.h */, 4CA3530A18A53CB800839B0F /* MPKeyMapper.m */, + 4C3499FE218852160055AF45 /* MPKeyTyper.h */, + 4C3499FF218852160055AF45 /* MPKeyTyper.m */, 4C1F7FA01E3A12E600D6A40E /* MPModifiedKey.h */, 4C1F7FA11E3A12E600D6A40E /* MPModifiedKey.m */, - 4C8F0C6F1FCEF91400BE157F /* MPPickcharsParser.h */, - 4C088C401FD9A42800F92502 /* MPPickcharsParser_Private.h */, - 4C8F0C701FCEF91400BE157F /* MPPickcharsParser.m */, ); name = Autotype; sourceTree = ""; @@ -1495,10 +1513,10 @@ 4C90757B18A42E7A00E598DA /* Commands */ = { isa = PBXGroup; children = ( - 4CE2961318429AA5005F01CE /* MPAutotypeKeyPress.h */, - 4CE2961418429AA5005F01CE /* MPAutotypeKeyPress.m */, 4C89F522182FB4740069C73C /* MPAutotypeCommand.h */, 4C89F523182FB4740069C73C /* MPAutotypeCommand.m */, + 4CE2961318429AA5005F01CE /* MPAutotypeKeyPress.h */, + 4CE2961418429AA5005F01CE /* MPAutotypeKeyPress.m */, 4CE296171842A166005F01CE /* MPAutotypePaste.h */, 4CE296181842A166005F01CE /* MPAutotypePaste.m */, 4C6F228719A4A7F90012310C /* MPAutotypeClear.h */, @@ -2025,6 +2043,7 @@ 4CF78064176E75AD0032EE71 /* MPIntegrationSettingsController.m in Sources */, 4C25703F1BF11C2300D39416 /* MPPluginSettingsController.m in Sources */, 4CF6C711176F4533007A811D /* MPStringLengthValueTransformer.m in Sources */, + 4C349A00218852160055AF45 /* MPKeyTyper.m in Sources */, 4CD5D705177A5F3300100649 /* MPDatabaseSettingsWindowController.m in Sources */, 4C4FCE15177CFE6B00BBF7AE /* MPCustomFieldTableCellView.m in Sources */, 4C4B728518E4B9B400A1A5D5 /* MPDockTileHelper.m in Sources */, @@ -2084,6 +2103,7 @@ 4C7B63751C0CB51F00D7038C /* TTTImageTransformers.m in Sources */, 4CEED1C617D7BD0E007180F1 /* NSError+Messages.m in Sources */, 4C00E33817D8FA3500F37192 /* DDHotKeyCenter.m in Sources */, + 4C5EF816218CA03F0003C00E /* MPAutotypeParser.m in Sources */, 4C224B4217DFCB2400FF6AEE /* MPNumericalInputFormatter.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/MacPass/MPAutotypeClear.m b/MacPass/MPAutotypeClear.m index 6ec634a4..6ca2a449 100644 --- a/MacPass/MPAutotypeClear.m +++ b/MacPass/MPAutotypeClear.m @@ -22,12 +22,14 @@ #import "MPAutotypeClear.h" #import "MPKeyMapper.h" +#import "MPKeyTyper.h" + #import @implementation MPAutotypeClear - (NSString *)description { - return [[self class] description]; + return self.class.description; } - (void)execute { @@ -36,8 +38,8 @@ NSLog(@"Unable to generate key code for 'A'"); return; } - [self sendPressKey:key.keyCode modifierFlags:kCGEventFlagMaskCommand]; - [self sendPressKey:kVK_Delete modifierFlags:0]; + [MPKeyTyper sendKey:MPMakeModifiedKey(kCGEventFlagMaskCommand, key.keyCode)]; + [MPKeyTyper sendKey:MPMakeModifiedKey(0, kVK_Delete)]; } @end diff --git a/MacPass/MPAutotypeCommand.h b/MacPass/MPAutotypeCommand.h index b45ed4c6..7e1656fa 100644 --- a/MacPass/MPAutotypeCommand.h +++ b/MacPass/MPAutotypeCommand.h @@ -21,7 +21,6 @@ // #import -#import "MPModifiedKey.h" @class MPAutotypeContext; /** @@ -30,7 +29,6 @@ * entry point for creating AutotypeCommands. You should never need to build a command on your own. */ @interface MPAutotypeCommand : NSObject - @property (readonly, strong) MPAutotypeContext *context; /** @@ -44,22 +42,6 @@ */ + (NSArray *)commandsForContext:(MPAutotypeContext *)context; -/** - * Sends a KeyPress Event with the supplied modifier flags and Keycode - * Any existing modifiers will be disabled for this event. If the user - * presses any key, those will be ignored during this event - * - * @param keyCode virtual KeyCode to be sent - * @param flags modifier flags for the key press event - */ -- (void)sendPressKey:(CGKeyCode)keyCode modifierFlags:(CGEventFlags)flags; -- (void)sendPressKey:(MPModifiedKey)key; - -/** - * Convenience message to be sent for executing a simple paste command - */ -- (void)sendPasteKeyCode; - /** * Executes the Autotype Command. */ diff --git a/MacPass/MPAutotypeCommand.m b/MacPass/MPAutotypeCommand.m index 01cdfe1b..0966f143 100644 --- a/MacPass/MPAutotypeCommand.m +++ b/MacPass/MPAutotypeCommand.m @@ -21,428 +21,17 @@ // #import "MPAutotypeCommand.h" - -#import "MPAutotypePaste.h" -#import "MPAutotypeKeyPress.h" -#import "MPAutotypeClear.h" -#import "MPAutotypeDelay.h" - +#import "MPAutotypeParser.h" #import "MPAutotypeContext.h" -#import "MPKeyMapper.h" - -#import "KeePassKit/KeePassKit.h" - -#import -#import - -static CGKeyCode kMPFunctionKeyCodes[] = { - kVK_F1, - kVK_F2, - kVK_F3, - kVK_F4, - kVK_F5, - kVK_F6, - kVK_F7, - kVK_F8, - kVK_F9, - kVK_F10, - kVK_F11, - kVK_F12, - kVK_F13, - kVK_F14, - kVK_F15, - kVK_F16, - kVK_F17, - kVK_F18, - kVK_F19 -}; - -static CGKeyCode kMPNumpadKeyCodes[] = { - kVK_ANSI_Keypad0, - kVK_ANSI_Keypad1, - kVK_ANSI_Keypad2, - kVK_ANSI_Keypad3, - kVK_ANSI_Keypad4, - kVK_ANSI_Keypad5, - kVK_ANSI_Keypad6, - kVK_ANSI_Keypad7, - kVK_ANSI_Keypad8, - kVK_ANSI_Keypad9 -}; - -@interface NSNumber (AutotypeCommand) - -@property (nonatomic, readonly, assign) CGEventFlags eventFlagsValue; -@property (nonatomic, readonly, assign) CGKeyCode keyCodeValue; - -@end - -@implementation NSNumber (AutotypeCommand) - -- (CGEventFlags)eventFlagsValue { - return (CGEventFlags)self.integerValue; -} -- (CGKeyCode)keyCodeValue { - return (CGKeyCode)self.integerValue; -} - -@end @implementation MPAutotypeCommand -+ (NSDictionary *)_keypressCommands { - static NSDictionary *keypressCommands; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - keypressCommands = @{ kKPKAutotypeBackspace : @(kVK_Delete), - //kKPKAutotypeBreak : @0, - kKPKAutotypeCapsLock : @(kVK_CapsLock), - kKPKAutotypeDelete : @(kVK_ForwardDelete), - kKPKAutotypeDown : @(kVK_DownArrow), - kKPKAutotypeEnd : @(kVK_End), - kKPKAutotypeEnter : @(kVK_Return), - kKPKAutotypeEscape : @(kVK_Escape), - kKPKAutotypeHelp : @(kVK_Help), - kKPKAutotypeHome : @(kVK_Home), - //kKPKAutotypeInsert : @(), - kKPKAutotypeLeft : @(kVK_LeftArrow), - kKPKAutotypeLeftWindows : @(kVK_Command), - //kKPKAutotypeNumlock : @(), - kKPKAutotypePageDown : @(kVK_PageDown), - kKPKAutotypePageUp : @(kVK_PageUp), - //kKPKAutotypePrintScreen : @(), - kKPKAutotypeRight : @(kVK_RightArrow), - kKPKAutotypeRightWindows : @(kVK_Command), - //kKPKAutotypeScrollLock : @(), - kKPKAutotypeSpace : @(kVK_Space), - kKPKAutotypeTab : @(kVK_Tab), - kKPKAutotypeUp : @(kVK_UpArrow), - kKPKAutotypeWindows : @(kVK_Command) - }; - }); - return keypressCommands; -} -/* Commands that are actually just one symbol to be pasted */ -+ (NSDictionary *)_characterCommands { - static NSDictionary *characterCommands; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - characterCommands = @{ - kKPKAutotypePlus: @"+", - kKPKAutotypeCaret: @"^", - kKPKAutotypePercent: @"%", - kKPKAutotypeTilde : @"~", - kKPKAutotypeRoundBracketLeft : @"(", - kKPKAutotypeRoundBracketRight : @")", - kKPKAutotypeSquareBracketLeft : @"[", - kKPKAutotypeSquareBracketRight : @"]", - kKPKAutotypeCurlyBracketLeft: @"{", - kKPKAutotypeCurlyBracketRight: @"}" - }; - }); - return characterCommands; -} - -/** - * Mapping for modifier to CGEventFlags. - * - * @return dictionary with commands as keys and CGEventFlags as wrapped values - */ -+ (NSDictionary *)_modifierCommands { - static NSDictionary *modifierCommands; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - modifierCommands = @{ - kKPKAutotypeAlt : @(kCGEventFlagMaskAlternate), - kKPKAutotypeControl : @(kCGEventFlagMaskControl), - kKPKAutotypeShift : @(kCGEventFlagMaskShift) - }; - }); - return modifierCommands; -} - + (NSArray *)commandsForContext:(MPAutotypeContext *)context { if(!context.valid) { - return nil; + return @[]; } - NSUInteger reserverd = MAX(1,context.normalizedCommand.length / 4); - NSMutableArray *commands = [[NSMutableArray alloc] initWithCapacity:reserverd]; - NSMutableArray __block *commandRanges = [[NSMutableArray alloc] initWithCapacity:reserverd]; - NSRegularExpression *commandRegExp = [[NSRegularExpression alloc] initWithPattern:@"\\{[^\\{\\}]+\\}" options:NSRegularExpressionCaseInsensitive error:0]; - NSAssert(commandRegExp, @"RegExp is constant. Has to work all the time"); - [commandRegExp enumerateMatchesInString:context.evaluatedCommand options:0 range:NSMakeRange(0, context.evaluatedCommand.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { - @autoreleasepool { - [commandRanges addObject:[NSValue valueWithRange:result.range]]; - } - }]; - - /* add range at the end as terminator */ - [commandRanges addObject:[NSValue valueWithRange:NSMakeRange(context.evaluatedCommand.length, 0)]]; - - NSUInteger location = 0; - CGEventFlags modifiers = 0; - - for(NSValue *rangeValue in commandRanges) { - NSRange commandRange = rangeValue.rangeValue; - /* All non-commands will get translated into key presses if possible, otherwiese into paste commands */ - if(location < commandRange.location) { - /* If there were modifiers we need to use the next single stroke and update the modifier command */ - if(modifiers) { - NSString *modifiedKey = [context.evaluatedCommand substringWithRange:NSMakeRange(location, 1)]; - MPAutotypeKeyPress *press = [[MPAutotypeKeyPress alloc] initWithModifierMask:modifiers character:modifiedKey]; - if(press) { - [commands addObject:press]; - } - modifiers = 0; - location++; - } - NSRange textRange = NSMakeRange(location, commandRange.location - location); - if(textRange.length > 0) { - NSString *textValue = [context.evaluatedCommand substringWithRange:textRange]; - [self _appendTextCommandWithContent:textValue toCommands:commands obfusctate:context.entry.autotype.obfuscateDataTransfer]; - } - } - /* Test for modifer Key */ - NSString *commandString = [context.evaluatedCommand substringWithRange:commandRange]; - /* append commands for non-modifer keys */ - if(![self _updateModifierMask:&modifiers forCommand:commandString]) { - [self _appendCommandForString:commandString toCommands:commands activeModifer:modifiers obfuscate:context.entry.autotype.obfuscateDataTransfer]; - modifiers = 0; // Reset the modifers; - } - location = commandRange.location + commandRange.length; - } - return commands; -} - -+ (void)_appendTextCommandWithContent:(NSString *)pasteContent toCommands:(NSMutableArray *)commands obfusctate:(BOOL)obfuscate { - if(obfuscate) { - [self _appendObfuscatedPasteCommandForContent:pasteContent toCommands:commands]; - } - else { - MPAutotypePaste *pasteCommand = [[MPAutotypePaste alloc] initWithString:pasteContent]; - [commands addObject:pasteCommand]; - } -} - -+ (void)_appendObfuscatedPasteCommandForContent:(NSString *)pasteContent toCommands:(NSMutableArray *)commands { - if(!pasteContent) { - return; - } - @autoreleasepool { - /* - * obfuscate entered data using Two-Channel Auto-Type Obfuscation - * refer to KeePass documentation for more information - * http://keepass.info/help/v2/autotype_obfuscation.html - */ - - NSMutableString *paste = [@"" mutableCopy]; - NSMutableArray *typeKeys = [[NSMutableArray alloc] init]; - - /* - * seed the random number generator using the first 4 bytes of the string's SHA1 - * this ensures that you get the same string split every time for a given string - */ - const char *cstr = [pasteContent cStringUsingEncoding:NSUTF8StringEncoding]; - NSData *data = [NSData dataWithBytes:cstr length:pasteContent.length]; - uint8_t digest[CC_SHA1_DIGEST_LENGTH]; - CC_SHA1(data.bytes, (unsigned int)data.length, digest); - srandom(*((unsigned int*)digest)); - - for (NSUInteger i = 0; i < pasteContent.length; i++) { - NSUInteger part = random() % 2; - - NSString *key = [pasteContent substringWithRange:NSMakeRange(i, 1)]; - MPModifiedKey mKey = [MPKeyMapper modifiedKeyForCharacter:key]; - /* append unknown keycodes to the paste since we can't type them */ - if (part == 0 || mKey.keyCode == kMPUnknownKeyCode) { - [paste appendString:key]; - [typeKeys addObject:[NSValue valueWithModifiedKey:MPMakeModifiedKey(0, kVK_RightArrow)]]; - } - else { - [typeKeys addObject:[NSValue valueWithModifiedKey:mKey ]]; - } - } - - /* move to the end of the content */ - for (NSUInteger i = typeKeys.count; i < pasteContent.length; i++) { - [typeKeys addObject:[NSValue valueWithModifiedKey:MPMakeModifiedKey(0, kVK_RightArrow)]]; - } - - /* add paste command */ - MPAutotypePaste *pasteCommand = [[MPAutotypePaste alloc] initWithString:paste]; - [commands addObject:pasteCommand]; - - /* add keypress commands */ - if(typeKeys.count > 0) { - for(NSUInteger i = 0; i < paste.length; i++) { - [commands addObject:[[MPAutotypeKeyPress alloc] initWithModifiedKey:MPMakeModifiedKey(0, kVK_LeftArrow)]]; - } - for(NSUInteger i = 0; i < typeKeys.count; i++) { - [commands addObject:[[MPAutotypeKeyPress alloc] initWithModifiedKey:typeKeys[i].modifiedKeyValue]]; - } - } - } -} - -+ (void)_appendCommandForString:(NSString *)commandString toCommands:(NSMutableArray *)commands activeModifer:(CGEventFlags)flags obfuscate:(BOOL)obfuscate { - if(!commandString || !commandString.length) { - return; - } - /* Simple Special Press */ - NSString *uppercaseCommand = commandString.uppercaseString; - NSNumber *keyCodeNumber = [self _keypressCommands][uppercaseCommand]; - if(nil != keyCodeNumber) { - MPAutotypeKeyPress *press = [[MPAutotypeKeyPress alloc] initWithModifiedKey:MPMakeModifiedKey(flags, keyCodeNumber.keyCodeValue)]; - if(press) { - [commands addObject:press]; - } - return; // Done - } - /* {PLUS}, {TILDE}, {PERCENT}, {+}, etc */ - NSString *character = [self _characterCommands][uppercaseCommand]; - if(character) { - MPAutotypeKeyPress *press = [[MPAutotypeKeyPress alloc] initWithModifierMask:flags character:character]; - if(press) { - [commands addObject:press]; - } - return; // Done - } - - /* F1-16 */ - static NSRegularExpression *functionKeyRegExp; - static NSRegularExpression *numpadKeyRegExp; - static NSRegularExpression *delayRegExp; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSString *delayPattern = [[NSString alloc] initWithFormat:@"\\{(%@|%@)[ |=]+([0-9]+)\\}", - kKPKAutotypeDelay, - kKPKAutotypeVirtualKey/*, - kKPKAutotypeVirtualExtendedKey, - kKPKAutotypeVirtualNonExtendedKey*/]; - functionKeyRegExp = [[NSRegularExpression alloc] initWithPattern:kKPKAutotypeFunctionMaskRegularExpression options:NSRegularExpressionCaseInsensitive error:0]; - numpadKeyRegExp = [[NSRegularExpression alloc] initWithPattern:kKPKAutotypeKeypaddNumberMaskRegularExpression options:NSRegularExpressionCaseInsensitive error:0]; - delayRegExp = [[NSRegularExpression alloc] initWithPattern:delayPattern options:NSRegularExpressionCaseInsensitive error:0]; - }); - NSTextCheckingResult *functionResult = [functionKeyRegExp firstMatchInString:commandString options:0 range:NSMakeRange(0, commandString.length)]; - if(functionResult && functionResult.numberOfRanges == 2) { - NSString *numberString = [commandString substringWithRange:[functionResult rangeAtIndex:1]]; - NSScanner *numberScanner = [[NSScanner alloc] initWithString:numberString]; - NSInteger functionNumber = 0; - if([numberScanner scanInteger:&functionNumber] && functionNumber >= 1 && functionNumber <= 19) { - MPAutotypeKeyPress *press = [[MPAutotypeKeyPress alloc] initWithModifiedKey:MPMakeModifiedKey(flags, kMPFunctionKeyCodes[functionNumber-1])]; - if(press) { - [commands addObject:press]; - } - return; // Done - } - } - - /* Numpad0-9 */ - NSTextCheckingResult *numpadResult = [numpadKeyRegExp firstMatchInString:commandString options:0 range:NSMakeRange(0, commandString.length)]; - if(numpadResult && numpadResult.numberOfRanges == 2) { - NSString *numberString = [commandString substringWithRange:[numpadResult rangeAtIndex:1]]; - NSScanner *numberScanner = [[NSScanner alloc] initWithString:numberString]; - NSInteger numpadNumber = 0; - if([numberScanner scanInteger:&numpadNumber] && numpadNumber >= 0 && numpadNumber <= 9) { - MPAutotypeKeyPress *press = [[MPAutotypeKeyPress alloc] initWithModifiedKey:MPMakeModifiedKey(flags, kMPNumpadKeyCodes[numpadNumber])]; - if(press) { - [commands addObject:press]; - } - return; // Done - } - } - - /* Clearfield */ - if([kKPKAutotypeClearField isEqualToString:uppercaseCommand]) { - [commands addObject:[[MPAutotypeClear alloc] init]]; - return; // Done - } - // TODO: add {APPLICATION } - /* Delay */ - NSTextCheckingResult *result = [delayRegExp firstMatchInString:commandString options:0 range:NSMakeRange(0, commandString.length)]; - if(result && (result.numberOfRanges == 3)) { - NSString *uppercaseCommand = [[commandString substringWithRange:[result rangeAtIndex:1]] uppercaseString]; - NSString *valueString = [commandString substringWithRange:[result rangeAtIndex:2]]; - NSScanner *numberScanner = [[NSScanner alloc] initWithString:valueString]; - NSInteger value; - if([numberScanner scanInteger:&value]) { - if([kKPKAutotypeDelay isEqualToString:uppercaseCommand]) { - if(MAX(0, value) <= 0) { - return; // Value too low, just skipp - } - [commands addObject:[[MPAutotypeDelay alloc] initWithDelay:value]]; - return; // Done - } - else if([kKPKAutotypeVirtualKey isEqualToString:uppercaseCommand]) { - NSLog(@"Virtual key strokes aren't supported yet!"); - // TODO add key - } - } - else { - NSLog(@"Unable to parse value part in command:%@", commandString); - } - } - else { - /* Fallback */ - [self _appendTextCommandWithContent:commandString toCommands:commands obfusctate:obfuscate]; - } -} - -+ (BOOL)_updateModifierMask:(CGEventFlags *)mask forCommand:(NSString *)commandString { - NSAssert(mask != NULL, @"Input pointer missing!"); - if(mask == NULL) { - return NO; - } - NSNumber *flagNumber = [self _modifierCommands][commandString.uppercaseString]; - if(!flagNumber) { - return NO; // No modifier key, just leave - } - CGEventFlags flags = flagNumber.eventFlagsValue; - *mask |= flags; - return YES; -} - -- (void)sendPressKey:(MPModifiedKey)key { - [self sendPressKey:key.keyCode modifierFlags:key.modifier]; -} - -- (void)sendPressKey:(CGKeyCode)keyCode modifierFlags:(CGEventFlags)flags { - - CGEventSourceRef eventSource = CGEventSourceCreate(kCGEventSourceStatePrivate); - if(NULL == eventSource) { - return; // We could not create our own source, abort! - } - CGEventRef pressKey = CGEventCreateKeyboardEvent (eventSource, keyCode, YES); - CGEventRef releaseKey = CGEventCreateKeyboardEvent (eventSource, keyCode, NO); - - /* - Set the modifiers to the ones we want - We use our private event source so no modifier reset should be needed - */ - CGEventSetFlags(pressKey, flags); - CGEventSetFlags(releaseKey, flags); - - /* Send the event */ - CGEventPost(kCGHIDEventTap, pressKey); - /* TODO: Evaluate postToPid */ - //CGEventPostToPid(0, pressKey); - usleep(0.05 * NSEC_PER_MSEC); - CGEventPost(kCGHIDEventTap, releaseKey); - /* TODO: Evaluate postToPid */ - //CGEventPostToPid(0, releaseKey); - usleep(0.05 * NSEC_PER_MSEC); - - CFRelease(pressKey); - CFRelease(releaseKey); - CFRelease(eventSource); -} - -- (void)sendPasteKeyCode { - MPModifiedKey mKey = [MPKeyMapper modifiedKeyForCharacter:@"v"]; - if(mKey.keyCode == kMPUnknownKeyCode) { - return; // We did not find a mapping for "V" - } - [self sendPressKey:mKey.keyCode modifierFlags:kCGEventFlagMaskCommand]; + MPAutotypeParser *parser = [[MPAutotypeParser alloc] initWithContext:context]; + return parser.commands; } - (void)execute { diff --git a/MacPass/MPAutotypeKeyPress.h b/MacPass/MPAutotypeKeyPress.h index c27cd9f5..c3757f4f 100644 --- a/MacPass/MPAutotypeKeyPress.h +++ b/MacPass/MPAutotypeKeyPress.h @@ -28,8 +28,34 @@ @interface MPAutotypeKeyPress : MPAutotypeCommand @property (readonly, assign) MPModifiedKey key; +@property (readonly, copy) NSString *character; + +/** + Initializes a command with the given keycode and modifier mask + MacPass will update the modifiers according to user preferences to accomodate + for command-control differences between windows/linux and macOS + + The virtual key code is used as-is without and re-mapping. + + This will result in unexpected behaviour for keyboard layouts other tha us-ascii + + use initWithModifierMask:character to initalized the key press command to ensurue the + correct character is typed regardless of the keyboard layout + + @param key The modified key to be pressed + @return The press key command with the supplied argurments set + */ - (instancetype)initWithModifiedKey:(MPModifiedKey)key; + +/** + Initalizes a command with the given modifier mask and the character to be typed. + A suitable keycode and modifier + + @param modiferMask Modifiers mask to use when typing the character + @param character The character to be typed. It is ecnoureaded to use single characters + @return The type command to type the supplied character with the given modifiers + */ - (instancetype)initWithModifierMask:(CGEventFlags)modiferMask character:(NSString *)character; @end diff --git a/MacPass/MPAutotypeKeyPress.m b/MacPass/MPAutotypeKeyPress.m index 4179cb36..f559f703 100644 --- a/MacPass/MPAutotypeKeyPress.m +++ b/MacPass/MPAutotypeKeyPress.m @@ -23,42 +23,44 @@ #import "MPAutotypeKeyPress.h" #import "MPFlagsHelper.h" #import "MPKeyMapper.h" +#import "MPKeyTyper.h" #import "MPSettingsHelper.h" +@interface MPAutotypeKeyPress () +@property (copy) NSString *character; +@end + @implementation MPAutotypeKeyPress -static CGEventFlags _updateModifierMaskForCurrentDefaults(CGEventFlags modifiers) { - BOOL sendCommand = [[NSUserDefaults standardUserDefaults] boolForKey:kMPSettingsKeySendCommandForControlKey]; - if(sendCommand && MPIsFlagSetInOptions(kCGEventFlagMaskControl, modifiers)) { - return (modifiers ^ kCGEventFlagMaskControl) | kCGEventFlagMaskCommand; - } - return modifiers; +- (instancetype)initWithModifiedKey:(MPModifiedKey)key { + return [self _initWithModifiedKey:key text:nil]; } -- (instancetype)initWithModifiedKey:(MPModifiedKey)key { +- (instancetype)_initWithModifiedKey:(MPModifiedKey)key text:(NSString *)text { self = [super init]; if(self) { + _character = [text copy]; _key = key; - _key.modifier = _updateModifierMaskForCurrentDefaults(_key.modifier); } return self; } -- (instancetype)initWithModifierMask:(CGEventFlags)modiferMask keyCode:(CGKeyCode)code { - self = [self initWithModifiedKey:MPMakeModifiedKey(modiferMask, code)]; - return self; -} - (instancetype)initWithModifierMask:(CGEventFlags)modiferMask character:(NSString *)character { - MPModifiedKey mappedKey = [MPKeyMapper modifiedKeyForCharacter:character]; - if(mappedKey.keyCode == kMPUnknownKeyCode) { - self = nil; + /* try to map the character */ + if(modiferMask) { + MPModifiedKey mappedKey = [MPKeyMapper modifiedKeyForCharacter:character]; + if(mappedKey.keyCode == kMPUnknownKeyCode) { + NSLog(@"Error. Unable to determine virtual key for character %@ to send with modifiers %llu.", character, modiferMask); + self = nil; + } + else { + mappedKey.modifier = modiferMask; + self = [self _initWithModifiedKey:mappedKey text:nil]; + } } else { - if(mappedKey.modifier && (modiferMask != mappedKey.modifier)) { - NSLog(@"Supplied modifiers for character %@ do not match required modifiers", character); - } - self = [self initWithModifierMask:modiferMask keyCode:mappedKey.keyCode]; + self = [self _initWithModifiedKey:MPMakeModifiedKey(0, 0) text:character]; } return self; } @@ -71,7 +73,12 @@ static CGEventFlags _updateModifierMaskForCurrentDefaults(CGEventFlags modifiers if(![self isValid]) { return; // no valid command. Stop. } - [self sendPressKey:self.key]; + if(self.character) { + [MPKeyTyper sendText:self.character]; + } + else { + [MPKeyTyper sendKey:self.key]; + } } - (BOOL)isValid { @@ -81,3 +88,4 @@ static CGEventFlags _updateModifierMaskForCurrentDefaults(CGEventFlags modifiers } @end + diff --git a/MacPass/MPAutotypeParser.h b/MacPass/MPAutotypeParser.h new file mode 100644 index 00000000..f367d7e8 --- /dev/null +++ b/MacPass/MPAutotypeParser.h @@ -0,0 +1,25 @@ +// +// MPAutotypeParser.h +// MacPass +// +// Created by Michael Starke on 02.11.18. +// Copyright © 2018 HicknHack Software GmbH. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class MPAutotypeCommand; +@class MPAutotypeContext; + +@interface MPAutotypeParser : NSObject + +@property (nonatomic, copy, readonly) NSArray *commands; +@property (readonly, strong) MPAutotypeContext *context; + +- (instancetype)initWithContext:(MPAutotypeContext *)context; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MacPass/MPAutotypeParser.m b/MacPass/MPAutotypeParser.m new file mode 100644 index 00000000..0efe0755 --- /dev/null +++ b/MacPass/MPAutotypeParser.m @@ -0,0 +1,428 @@ +// +// MPAutotypeParser.m +// MacPass +// +// Created by Michael Starke on 02.11.18. +// Copyright © 2018 HicknHack Software GmbH. All rights reserved. +// + +#import "MPAutotypeParser.h" +#import "MPAutotypeClear.h" +#import "MPAutotypeContext.h" +#import "MPAutotypeDelay.h" +#import "MPAutotypeKeyPress.h" +#import "MPAutotypePaste.h" +#import "MPKeyMapper.h" +#import "MPFlagsHelper.h" +#import "MPSettingsHelper.h" + +#import "NSString+MPComposedCharacterAdditions.h" + +#import + +#import +#import + +static CGKeyCode kMPFunctionKeyCodes[] = { + kVK_F1, + kVK_F2, + kVK_F3, + kVK_F4, + kVK_F5, + kVK_F6, + kVK_F7, + kVK_F8, + kVK_F9, + kVK_F10, + kVK_F11, + kVK_F12, + kVK_F13, + kVK_F14, + kVK_F15, + kVK_F16, + kVK_F17, + kVK_F18, + kVK_F19 +}; + +static CGKeyCode kMPNumpadKeyCodes[] = { + kVK_ANSI_Keypad0, + kVK_ANSI_Keypad1, + kVK_ANSI_Keypad2, + kVK_ANSI_Keypad3, + kVK_ANSI_Keypad4, + kVK_ANSI_Keypad5, + kVK_ANSI_Keypad6, + kVK_ANSI_Keypad7, + kVK_ANSI_Keypad8, + kVK_ANSI_Keypad9 +}; + +@interface NSNumber (AutotypeCommand) + +@property (nonatomic, readonly, assign) CGEventFlags eventFlagsValue; +@property (nonatomic, readonly, assign) CGKeyCode keyCodeValue; + +@end + +@implementation NSNumber (AutotypeCommand) + +- (CGEventFlags)eventFlagsValue { + return (CGEventFlags)self.integerValue; +} +- (CGKeyCode)keyCodeValue { + return (CGKeyCode)self.integerValue; +} + +@end + + +@interface MPAutotypeParser () + +@property (strong) NSMutableArray *mutableCommands; +@property (strong, nonatomic, readonly) NSDictionary *keyPressCommands; +@property (strong, nonatomic, readonly) NSDictionary *characterCommands; +@property (strong, nonatomic, readonly) NSDictionary *modifierCommands; + +@end + +@implementation MPAutotypeParser + +@dynamic commands; +@dynamic keyPressCommands; +@dynamic characterCommands; +@dynamic modifierCommands; + +- (NSDictionary *)keypressCommands { + static NSDictionary *keypressCommands; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + keypressCommands = @{ kKPKAutotypeBackspace : @(kVK_Delete), + //kKPKAutotypeBreak : @0, + kKPKAutotypeCapsLock : @(kVK_CapsLock), + kKPKAutotypeDelete : @(kVK_ForwardDelete), + kKPKAutotypeDown : @(kVK_DownArrow), + kKPKAutotypeEnd : @(kVK_End), + kKPKAutotypeEnter : @(kVK_Return), + kKPKAutotypeEscape : @(kVK_Escape), + kKPKAutotypeHelp : @(kVK_Help), + kKPKAutotypeHome : @(kVK_Home), + //kKPKAutotypeInsert : @(), + kKPKAutotypeLeft : @(kVK_LeftArrow), + kKPKAutotypeLeftWindows : @(kVK_Command), + //kKPKAutotypeNumlock : @(), + kKPKAutotypePageDown : @(kVK_PageDown), + kKPKAutotypePageUp : @(kVK_PageUp), + //kKPKAutotypePrintScreen : @(), + kKPKAutotypeRight : @(kVK_RightArrow), + kKPKAutotypeRightWindows : @(kVK_Command), + //kKPKAutotypeScrollLock : @(), + kKPKAutotypeSpace : @(kVK_Space), + kKPKAutotypeTab : @(kVK_Tab), + kKPKAutotypeUp : @(kVK_UpArrow), + kKPKAutotypeWindows : @(kVK_Command) + }; + }); + return keypressCommands; +} +/* Commands that are actually just one symbol to be pasted */ +- (NSDictionary *)characterCommands { + static NSDictionary *characterCommands; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + characterCommands = @{ + kKPKAutotypePlus: @"+", + kKPKAutotypeCaret: @"^", + kKPKAutotypePercent: @"%", + kKPKAutotypeTilde : @"~", + kKPKAutotypeRoundBracketLeft : @"(", + kKPKAutotypeRoundBracketRight : @")", + kKPKAutotypeSquareBracketLeft : @"[", + kKPKAutotypeSquareBracketRight : @"]", + kKPKAutotypeCurlyBracketLeft: @"{", + kKPKAutotypeCurlyBracketRight: @"}" + }; + }); + return characterCommands; +} + +/** + * Mapping for modifier to CGEventFlags. + * + * @return dictionary with commands as keys and CGEventFlags as wrapped values + */ +- (NSDictionary *)modifierCommands { + static NSDictionary *modifierCommands; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + modifierCommands = @{ + kKPKAutotypeAlt : @(kCGEventFlagMaskAlternate), + kKPKAutotypeControl : @(kCGEventFlagMaskControl), + kKPKAutotypeShift : @(kCGEventFlagMaskShift) + }; + }); + return modifierCommands; +} + + +- (instancetype)initWithContext:(MPAutotypeContext *)context { + self = [super init]; + if(self) { + _context = context; + } + return self; +} + +- (NSArray *)commands { + if(!self.mutableCommands) { + [self _parseCommands]; + } + return [self.mutableCommands copy]; +} + +- (void)_parseCommands { + if(!self.context.valid) { + return; + } + NSUInteger reserverd = MAX(1,self.context.normalizedCommand.length / 4); + self.mutableCommands = [[NSMutableArray alloc] initWithCapacity:reserverd]; + NSMutableArray __block *commandRanges = [[NSMutableArray alloc] initWithCapacity:reserverd]; + NSRegularExpression *commandRegExp = [[NSRegularExpression alloc] initWithPattern:@"\\{[^\\{\\}]+\\}" options:NSRegularExpressionCaseInsensitive error:0]; + NSAssert(commandRegExp, @"RegExp is constant. Has to work all the time"); + [commandRegExp enumerateMatchesInString:self.context.evaluatedCommand options:0 range:NSMakeRange(0, self.context.evaluatedCommand.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { + @autoreleasepool { + [commandRanges addObject:[NSValue valueWithRange:result.range]]; + } + }]; + + /* add range at the end as terminator */ + [commandRanges addObject:[NSValue valueWithRange:NSMakeRange(self.context.evaluatedCommand.length, 0)]]; + + NSUInteger location = 0; + CGEventFlags modifiers = 0; + + for(NSValue *rangeValue in commandRanges) { + NSRange commandRange = rangeValue.rangeValue; + /* All non-commands will get translated into key presses if possible, otherwiese into paste commands */ + if(location < commandRange.location) { + /* If there were modifiers we need to use the next single stroke and update the modifier command */ + if(modifiers) { + NSString *modifiedKey = [self.context.evaluatedCommand substringWithRange:NSMakeRange(location, 1)]; + MPAutotypeKeyPress *press = [[MPAutotypeKeyPress alloc] initWithModifierMask:modifiers character:modifiedKey]; + if(press) { + [self.mutableCommands addObject:press]; + } + modifiers = 0; + location++; + } + NSRange textRange = NSMakeRange(location, commandRange.location - location); + if(textRange.length > 0) { + NSString *textValue = [self.context.evaluatedCommand substringWithRange:textRange]; + [self _appendTextCommandWithContent:textValue]; + } + } + /* Test for modifer Key */ + NSString *commandString = [self.context.evaluatedCommand substringWithRange:commandRange]; + /* append commands for non-modifer keys */ + if(![self _updateModifierMask:&modifiers forCommand:commandString]) { + [self _appendCommandForString:commandString activeModifer:modifiers]; + modifiers = 0; // Reset the modifers; + } + location = commandRange.location + commandRange.length; + } +} + +- (BOOL)_updateModifierMask:(CGEventFlags *)mask forCommand:(NSString *)commandString { + NSAssert(mask != NULL, @"Input pointer missing!"); + if(mask == NULL) { + return NO; + } + + NSNumber *flagNumber = self.modifierCommands[commandString.uppercaseString]; + if(!flagNumber) { + return NO; // No modifier key, just leave + } + CGEventFlags flags = flagNumber.eventFlagsValue; + BOOL useCommandForControl = [NSUserDefaults.standardUserDefaults boolForKey:kMPSettingsKeySendCommandForControlKey]; + if(useCommandForControl && flags == kCGEventFlagMaskControl) { + flags = kCGEventFlagMaskCommand; + } + *mask |= flags; + return YES; +} + +- (void)_appendCommandForString:(NSString *)commandString activeModifer:(CGEventFlags)flags { + if(!commandString || !commandString.length) { + return; + } + /* Simple Special Press */ + NSString *uppercaseCommand = commandString.uppercaseString; + NSNumber *keyCodeNumber = self.keypressCommands[uppercaseCommand]; + if(nil != keyCodeNumber) { + MPAutotypeKeyPress *press = [[MPAutotypeKeyPress alloc] initWithModifiedKey:MPMakeModifiedKey(flags, keyCodeNumber.keyCodeValue)]; + if(press) { + [self.mutableCommands addObject:press]; + } + return; // Done + } + /* {PLUS}, {TILDE}, {PERCENT}, {+}, etc */ + NSString *character = self.characterCommands[uppercaseCommand]; + if(character) { + MPAutotypeKeyPress *press = [[MPAutotypeKeyPress alloc] initWithModifierMask:flags character:character]; + if(press) { + [self.mutableCommands addObject:press]; + } + return; // Done + } + + /* F1-16 */ + static NSRegularExpression *functionKeyRegExp; + static NSRegularExpression *numpadKeyRegExp; + static NSRegularExpression *delayRegExp; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *delayPattern = [[NSString alloc] initWithFormat:@"\\{(%@|%@)[ |=]+([0-9]+)\\}", + kKPKAutotypeDelay, + kKPKAutotypeVirtualKey/*, + kKPKAutotypeVirtualExtendedKey, + kKPKAutotypeVirtualNonExtendedKey*/]; + functionKeyRegExp = [[NSRegularExpression alloc] initWithPattern:kKPKAutotypeFunctionMaskRegularExpression options:NSRegularExpressionCaseInsensitive error:0]; + numpadKeyRegExp = [[NSRegularExpression alloc] initWithPattern:kKPKAutotypeKeypaddNumberMaskRegularExpression options:NSRegularExpressionCaseInsensitive error:0]; + delayRegExp = [[NSRegularExpression alloc] initWithPattern:delayPattern options:NSRegularExpressionCaseInsensitive error:0]; + }); + NSTextCheckingResult *functionResult = [functionKeyRegExp firstMatchInString:commandString options:0 range:NSMakeRange(0, commandString.length)]; + if(functionResult && functionResult.numberOfRanges == 2) { + NSString *numberString = [commandString substringWithRange:[functionResult rangeAtIndex:1]]; + NSScanner *numberScanner = [[NSScanner alloc] initWithString:numberString]; + NSInteger functionNumber = 0; + if([numberScanner scanInteger:&functionNumber] && functionNumber >= 1 && functionNumber <= 19) { + MPAutotypeKeyPress *press = [[MPAutotypeKeyPress alloc] initWithModifiedKey:MPMakeModifiedKey(flags, kMPFunctionKeyCodes[functionNumber-1])]; + if(press) { + [self.mutableCommands addObject:press]; + } + return; // Done + } + } + + /* Numpad0-9 */ + NSTextCheckingResult *numpadResult = [numpadKeyRegExp firstMatchInString:commandString options:0 range:NSMakeRange(0, commandString.length)]; + if(numpadResult && numpadResult.numberOfRanges == 2) { + NSString *numberString = [commandString substringWithRange:[numpadResult rangeAtIndex:1]]; + NSScanner *numberScanner = [[NSScanner alloc] initWithString:numberString]; + NSInteger numpadNumber = 0; + if([numberScanner scanInteger:&numpadNumber] && numpadNumber >= 0 && numpadNumber <= 9) { + MPAutotypeKeyPress *press = [[MPAutotypeKeyPress alloc] initWithModifiedKey:MPMakeModifiedKey(flags, kMPNumpadKeyCodes[numpadNumber])]; + if(press) { + [self.mutableCommands addObject:press]; + } + return; // Done + } + } + + /* Clearfield */ + if([kKPKAutotypeClearField isEqualToString:uppercaseCommand]) { + [self.mutableCommands addObject:[[MPAutotypeClear alloc] init]]; + return; // Done + } + // TODO: add {APPLICATION } + /* Delay */ + NSTextCheckingResult *result = [delayRegExp firstMatchInString:commandString options:0 range:NSMakeRange(0, commandString.length)]; + if(result && (result.numberOfRanges == 3)) { + NSString *uppercaseCommand = [[commandString substringWithRange:[result rangeAtIndex:1]] uppercaseString]; + NSString *valueString = [commandString substringWithRange:[result rangeAtIndex:2]]; + NSScanner *numberScanner = [[NSScanner alloc] initWithString:valueString]; + NSInteger value; + if([numberScanner scanInteger:&value]) { + if([kKPKAutotypeDelay isEqualToString:uppercaseCommand]) { + if(MAX(0, value) <= 0) { + return; // Value too low, just skipp + } + [self.mutableCommands addObject:[[MPAutotypeDelay alloc] initWithDelay:value]]; + return; // Done + } + else if([kKPKAutotypeVirtualKey isEqualToString:uppercaseCommand]) { + NSLog(@"Virtual key strokes aren't supported yet!"); + // TODO add key + } + } + else { + NSLog(@"Unable to parse value part in command:%@", commandString); + } + } + else { + /* Fallback */ + [self _appendTextCommandWithContent:commandString]; + } +} + +- (void)_appendTextCommandWithContent:(NSString *)content { + if(content.length == 0) { + return; + } + BOOL obfuscate = self.context.entry.autotype.obfuscateDataTransfer; + if(obfuscate) { + @autoreleasepool { + /* + * obfuscate entered data using Two-Channel Auto-Type Obfuscation + * refer to KeePass documentation for more information + * http://keepass.info/help/v2/autotype_obfuscation.html + */ + + NSMutableString *paste = [[NSMutableString alloc] initWithString:@""]; + //NSMutableArray *typeKeys = [[NSMutableArray alloc] init]; + NSMutableArray *typeCommands = [[NSMutableArray alloc] init]; + + /* + * seed the random number generator using the first 4 bytes of the string's SHA1 + * this ensures that you get the same string split every time for a given string + */ + const char *cstr = [content cStringUsingEncoding:NSUTF8StringEncoding]; + NSData *data = [NSData dataWithBytes:cstr length:content.length]; + uint8_t digest[CC_SHA1_DIGEST_LENGTH]; + CC_SHA1(data.bytes, (unsigned int)data.length, digest); + srandom(*((unsigned int*)digest)); + + for(NSString *key in content.composedCharacters) { + NSUInteger part = random() % 2; + if (part == 0) { + [paste appendString:key]; + [typeCommands addObject:[[MPAutotypeKeyPress alloc] initWithModifiedKey:MPMakeModifiedKey(0, kVK_RightArrow)]]; + } + else { + [typeCommands addObject:[[MPAutotypeKeyPress alloc] initWithModifierMask:0 character:key]]; + } + } + + /* move to the end of the content */ + for (NSUInteger i = typeCommands.count; i < content.composedCharacterLength; i++) { + [typeCommands addObject:[[MPAutotypeKeyPress alloc] initWithModifiedKey:MPMakeModifiedKey(0, kVK_RightArrow)]]; + } + + /* add paste command */ + MPAutotypePaste *pasteCommand = [[MPAutotypePaste alloc] initWithString:paste]; + [self.mutableCommands addObject:pasteCommand]; + + /* add keypress commands */ + if(typeCommands.count > 0) { + for(NSUInteger i = 0; i < paste.composedCharacterLength; i++) { + MPAutotypeKeyPress *pressLeftArrowCommand = [[MPAutotypeKeyPress alloc] initWithModifiedKey:MPMakeModifiedKey(0, kVK_LeftArrow)]; + if(pressLeftArrowCommand) { + [self.mutableCommands addObject:pressLeftArrowCommand]; + } + } + [self.mutableCommands addObjectsFromArray:typeCommands]; + } + } + } + else { + for(NSString *character in content.composedCharacters) { + MPAutotypeKeyPress *keyPress = [[MPAutotypeKeyPress alloc] initWithModifierMask:0 character:character]; + if(keyPress) { + [self.mutableCommands addObject:keyPress]; + } + } + } +} + +@end + diff --git a/MacPass/MPAutotypePaste.m b/MacPass/MPAutotypePaste.m index 0f01a13a..abcdac6f 100644 --- a/MacPass/MPAutotypePaste.m +++ b/MacPass/MPAutotypePaste.m @@ -22,6 +22,7 @@ #import "MPAutotypePaste.h" #import "MPPasteBoardController.h" +#import "MPKeyTyper.h" #import "KeePassKit/KeePassKit.h" @@ -53,7 +54,7 @@ if([self.pasteData length] > 0) { [MPPasteBoardController.defaultController stashObjects]; [MPPasteBoardController.defaultController copyObjectsWithoutTimeout:@[self.pasteData]]; - [self sendPasteKeyCode]; + [MPKeyTyper sendPaste]; usleep(0.2 * NSEC_PER_MSEC); // on 10.10 we need to wait a bit before restoring the pasteboard contents [MPPasteBoardController.defaultController restoreObjects]; } diff --git a/MacPass/MPKeyMapper.h b/MacPass/MPKeyMapper.h index f277650a..d46ad973 100644 --- a/MacPass/MPKeyMapper.h +++ b/MacPass/MPKeyMapper.h @@ -23,8 +23,6 @@ #import #import "MPModifiedKey.h" -FOUNDATION_EXTERN uint16_t const kMPUnknownKeyCode; - @interface MPKeyMapper : NSObject /** diff --git a/MacPass/MPKeyMapper.m b/MacPass/MPKeyMapper.m index fcef0d4a..a800d629 100644 --- a/MacPass/MPKeyMapper.m +++ b/MacPass/MPKeyMapper.m @@ -38,8 +38,6 @@ #define MPArrayCount(array) (sizeof(array) / sizeof(array[0])) -uint16_t const kMPUnknownKeyCode = UINT16_MAX; - @implementation MPKeyMapper + (NSString *)stringForKey:(CGKeyCode)keyCode { @@ -90,8 +88,7 @@ uint16_t const kMPUnknownKeyCode = UINT16_MAX; sizeof(chars) / sizeof(chars[0]), &realLength, chars); - - return CFBridgingRelease(CFStringCreateWithCharacters(kCFAllocatorDefault, chars, 1)); + return CFBridgingRelease(CFStringCreateWithCharacters(kCFAllocatorDefault, chars, realLength)); } + (MPModifiedKey)modifiedKeyForCharacter:(NSString *)character { @@ -137,8 +134,7 @@ uint16_t const kMPUnknownKeyCode = UINT16_MAX; charToCodeDict = [[NSDictionary alloc] initWithDictionary:tempCharToCodeDict]; keyboardCodeDictionary[localizedName] = charToCodeDict; } - NSString *singleCharacter = [character substringToIndex:1]; - NSValue *result = charToCodeDict[singleCharacter]; + NSValue *result = charToCodeDict[character]; if(!result) { return MPMakeModifiedKey(0, kMPUnknownKeyCode); } diff --git a/MacPass/MPKeyTyper.h b/MacPass/MPKeyTyper.h new file mode 100644 index 00000000..a969c8b0 --- /dev/null +++ b/MacPass/MPKeyTyper.h @@ -0,0 +1,36 @@ +// +// MPKeyboardTyper.h +// MacPass +// +// Created by Michael Starke on 30.10.18. +// Copyright © 2018 HicknHack Software GmbH. All rights reserved. +// + +#import +#import "MPModifiedKey.h" + +NS_ASSUME_NONNULL_BEGIN + + +@interface MPKeyTyper : NSObject + +/** + * Sends a KeyPress Event with the supplied modifier flags and Keycode + * Any existing modifiers will be disabled for this event. If the user + * presses any key, those will be ignored during this event + * + * @param keyCode virtual KeyCode to be sent + * @param flags modifier flags for the key press event + */ ++ (void)sendKey:(MPModifiedKey)key; + ++ (void)sendText:(NSString *)text; + +/** + * Convenience message to be sent for executing a simple paste command + */ ++ (void)sendPaste; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MacPass/MPKeyTyper.m b/MacPass/MPKeyTyper.m new file mode 100644 index 00000000..962c0fec --- /dev/null +++ b/MacPass/MPKeyTyper.m @@ -0,0 +1,79 @@ +// +// MPKeyboardTyper.m +// MacPass +// +// Created by Michael Starke on 30.10.18. +// Copyright © 2018 HicknHack Software GmbH. All rights reserved. +// + +#import "MPKeyTyper.h" +#import "MPKeyMapper.h" + +@implementation MPKeyTyper + ++ (void)sendKey:(MPModifiedKey)key { + [self _sendKey:key text:nil]; +} + ++ (void)sendText:(NSString *)text { + [self _sendKey:MPMakeModifiedKey(0, 0) text:text]; +} + ++ (void)_sendKey:(MPModifiedKey)key text:(NSString *)text { + if(key.modifier) { + NSAssert(text.length == 0, @"Unable to send keyboard events with modifers and text."); + } + if(key.keyCode == 0 && key.modifier == 0 && text.length == 0) { + return; + } + CGEventSourceRef eventSource = CGEventSourceCreate(kCGEventSourceStatePrivate); + if(NULL == eventSource) { + return; // We could not create our own source, abort! + } + CGEventRef pressKey = CGEventCreateKeyboardEvent (eventSource, key.keyCode, YES); + CGEventRef releaseKey = CGEventCreateKeyboardEvent (eventSource, key.keyCode, NO); + + /* + Set the modifiers to the ones we want + We use our private event source so no modifier reset should be needed + */ + CGEventSetFlags(pressKey, key.modifier); + CGEventSetFlags(releaseKey, key.modifier); + + unichar *charBuffer; + if(text.length > 0) { + charBuffer = malloc(sizeof(unichar) * text.length); + [text getCharacters:charBuffer range:NSMakeRange(0, text.length)]; + CGEventKeyboardSetUnicodeString(pressKey, text.length, charBuffer); + CGEventKeyboardSetUnicodeString(releaseKey, text.length, charBuffer); + } + + /* Send the event */ + CGEventPost(kCGHIDEventTap, pressKey); + /* TODO: Evaluate postToPid */ + //CGEventPostToPid(0, pressKey); + usleep(0.05 * NSEC_PER_MSEC); + CGEventPost(kCGHIDEventTap, releaseKey); + /* TODO: Evaluate postToPid */ + //CGEventPostToPid(0, releaseKey); + usleep(0.05 * NSEC_PER_MSEC); + + CFRelease(pressKey); + CFRelease(releaseKey); + CFRelease(eventSource); + + if(text.length > 0) { + free(charBuffer); + } +} + ++ (void)sendPaste { + MPModifiedKey mKey = [MPKeyMapper modifiedKeyForCharacter:@"v"]; + if(mKey.keyCode == kMPUnknownKeyCode) { + NSLog(@"Autotype error. Unable to map V to virtual key to send paste command. Skipping."); + return; // We did not find a mapping for "V" + } + [self sendKey:MPMakeModifiedKey(kCGEventFlagMaskCommand, mKey.keyCode)]; +} + +@end diff --git a/MacPass/MPModifiedKey.h b/MacPass/MPModifiedKey.h index 7d093a7d..90edf511 100644 --- a/MacPass/MPModifiedKey.h +++ b/MacPass/MPModifiedKey.h @@ -22,6 +22,8 @@ #import +FOUNDATION_EXTERN uint16_t const kMPUnknownKeyCode; + typedef struct { CGEventFlags modifier; CGKeyCode keyCode; @@ -34,6 +36,10 @@ NS_INLINE MPModifiedKey MPMakeModifiedKey(CGEventFlags modifier, CGKeyCode keyCo return k; } +NS_INLINE BOOL MPIsValidModifiedKey(MPModifiedKey k) { + return (k.keyCode == kMPUnknownKeyCode); +} + @interface NSValue(NSValueMPModifiedKeyExtensions) @property (nonatomic, readonly, assign) MPModifiedKey modifiedKeyValue; + (instancetype)valueWithModifiedKey:(MPModifiedKey)key; diff --git a/MacPass/MPModifiedKey.m b/MacPass/MPModifiedKey.m index 058f083e..feb192c4 100644 --- a/MacPass/MPModifiedKey.m +++ b/MacPass/MPModifiedKey.m @@ -22,11 +22,18 @@ #import "MPModifiedKey.h" +uint16_t const kMPUnknownKeyCode = UINT16_MAX; + @implementation NSValue(NSValueMPModifiedKeyExtensions) - (MPModifiedKey)modifiedKeyValue { MPModifiedKey key; - [self getValue:&key]; + if(@available(macOS 10.13, *)) { + [self getValue:&key size:sizeof(MPModifiedKey)]; + } + else { + [self getValue:&key]; + } return key; } diff --git a/MacPass/NSString+MPComposedCharacterAdditions.m b/MacPass/NSString+MPComposedCharacterAdditions.m index fb592376..4c5162bd 100644 --- a/MacPass/NSString+MPComposedCharacterAdditions.m +++ b/MacPass/NSString+MPComposedCharacterAdditions.m @@ -54,7 +54,9 @@ __block NSMutableArray *characters = [[NSMutableArray alloc] init]; [self enumerateSubstringsInRange:NSMakeRange(0, self.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) { - [characters addObject:substring]; + if(substring) { + [characters addObject:substring]; + } }]; return [characters copy]; } diff --git a/MacPass/NSString+MPPasswordCreation.m b/MacPass/NSString+MPPasswordCreation.m index c5782100..191b0971 100644 --- a/MacPass/NSString+MPPasswordCreation.m +++ b/MacPass/NSString+MPPasswordCreation.m @@ -112,7 +112,7 @@ static NSString *mergeWithoutDuplicates(NSString* baseCharacters, NSString* cust if(self.length == 0) { return nil; } - return [self composedCharacterAtIndex:arc4random_uniform((int)[self length])]; + return [self composedCharacterAtIndex:arc4random_uniform((int)self.length)]; } - (CGFloat)entropyWhithPossibleCharacterSet:(MPPasswordCharacterFlags)allowedCharacters orCustomCharacters:(NSString *)customCharacters { diff --git a/MacPassTests/MPTestAutotype.m b/MacPassTests/MPTestAutotype.m index c6a8e793..c1fdf957 100644 --- a/MacPassTests/MPTestAutotype.m +++ b/MacPassTests/MPTestAutotype.m @@ -32,8 +32,8 @@ self.entry = [[KPKEntry alloc] init]; self.entry.title = @"Title"; self.entry.url = @"www.myurl.com"; - self.entry.username = @"Username"; - self.entry.password = @"Password"; + self.entry.username = @"User Name"; + self.entry.password = @"Pass👩🏿‍🔧word"; } - (void)tearDown { @@ -98,62 +98,161 @@ MPAutotypeContext *context = [[MPAutotypeContext alloc] initWithEntry:self.entry andSequence:autotypeSequence]; NSArray *commands = [MPAutotypeCommand commandsForContext:context]; - XCTAssertEqual(commands.count, 1); - MPAutotypePaste *paste = commands[0]; - NSString *result = [[NSString alloc] initWithFormat:@"%@%@%@%@", numberAttribute.value, self.entry.username, self.entry.username, self.entry.username]; - XCTAssertEqualObjects(paste.pasteData, result); + + /* NoRepeatValueUsernameUsernameUsername */ + XCTAssertEqual(commands.count, 40); + MPAutotypeKeyPress *keyPress = commands.firstObject; + XCTAssertEqualObjects(keyPress.character, @"N"); + keyPress = commands[1]; + XCTAssertEqualObjects(keyPress.character, @"o"); + keyPress = commands[2]; + XCTAssertEqualObjects(keyPress.character, @"R"); + keyPress = commands[3]; + XCTAssertEqualObjects(keyPress.character, @"e"); + keyPress = commands[4]; + XCTAssertEqualObjects(keyPress.character, @"p"); + keyPress = commands[5]; + XCTAssertEqualObjects(keyPress.character, @"e"); + keyPress = commands[6]; + XCTAssertEqualObjects(keyPress.character, @"a"); + keyPress = commands[7]; + XCTAssertEqualObjects(keyPress.character, @"t"); + keyPress = commands[8]; + XCTAssertEqualObjects(keyPress.character, @"V"); + keyPress = commands[9]; + XCTAssertEqualObjects(keyPress.character, @"a"); + keyPress = commands[10]; + XCTAssertEqualObjects(keyPress.character, @"l"); + keyPress = commands[11]; + XCTAssertEqualObjects(keyPress.character, @"u"); + keyPress = commands[12]; + XCTAssertEqualObjects(keyPress.character, @"e"); + keyPress = commands[13]; + XCTAssertEqualObjects(keyPress.character, @"U"); + keyPress = commands[14]; + XCTAssertEqualObjects(keyPress.character, @"s"); + keyPress = commands[15]; + XCTAssertEqualObjects(keyPress.character, @"e"); + keyPress = commands[16]; + XCTAssertEqualObjects(keyPress.character, @"r"); + keyPress = commands[17]; + XCTAssertEqualObjects(keyPress.character, @" "); + keyPress = commands[18]; + XCTAssertEqualObjects(keyPress.character, @"N"); + keyPress = commands[19]; + XCTAssertEqualObjects(keyPress.character, @"a"); + keyPress = commands[20]; + XCTAssertEqualObjects(keyPress.character, @"m"); + keyPress = commands[21]; + XCTAssertEqualObjects(keyPress.character, @"e"); + keyPress = commands[22]; + XCTAssertEqualObjects(keyPress.character, @"U"); + keyPress = commands[23]; + XCTAssertEqualObjects(keyPress.character, @"s"); + keyPress = commands[24]; + XCTAssertEqualObjects(keyPress.character, @"e"); + keyPress = commands[25]; + XCTAssertEqualObjects(keyPress.character, @"r"); + keyPress = commands[26]; + XCTAssertEqualObjects(keyPress.character, @" "); + keyPress = commands[27]; + XCTAssertEqualObjects(keyPress.character, @"N"); + keyPress = commands[28]; + XCTAssertEqualObjects(keyPress.character, @"a"); + keyPress = commands[29]; + XCTAssertEqualObjects(keyPress.character, @"m"); + keyPress = commands[30]; + XCTAssertEqualObjects(keyPress.character, @"e"); + keyPress = commands[31]; + XCTAssertEqualObjects(keyPress.character, @"U"); + keyPress = commands[32]; + XCTAssertEqualObjects(keyPress.character, @"s"); + keyPress = commands[33]; + XCTAssertEqualObjects(keyPress.character, @"e"); + keyPress = commands[34]; + XCTAssertEqualObjects(keyPress.character, @"r"); + keyPress = commands[35]; + XCTAssertEqualObjects(keyPress.character, @" "); + keyPress = commands[36]; + XCTAssertEqualObjects(keyPress.character, @"N"); + keyPress = commands[37]; + XCTAssertEqualObjects(keyPress.character, @"a"); + keyPress = commands[38]; + XCTAssertEqualObjects(keyPress.character, @"m"); + keyPress = commands[39]; + XCTAssertEqualObjects(keyPress.character, @"e"); } - (void)testFunctionKeyCommand { MPAutotypeContext *context = [[MPAutotypeContext alloc] initWithEntry:self.entry andSequence:@"{F0}{F1}{F2}{F3}{F4}{F5}^%{F6}{F7}{F19}{F20}"]; NSArray *commands = [MPAutotypeCommand commandsForContext:context]; - XCTAssertEqual(commands.count, 10); - /* {F0} -> invalid, paste */ - MPAutotypePaste *paste = commands[0]; - XCTAssertEqualObjects(paste.pasteData, @"{F0}"); + XCTAssertEqual(commands.count, 17); + /* {F0} -> invalid, type */ + MPAutotypeKeyPress *key = commands[0]; + XCTAssertEqualObjects(key.character, @"{"); + key = commands[1]; + XCTAssertEqualObjects(key.character, @"F"); + key = commands[2]; + XCTAssertEqualObjects(key.character, @"0"); + key = commands[3]; + XCTAssertEqualObjects(key.character, @"}"); /* {F1} */ - MPAutotypeKeyPress *key = commands[1]; + key = commands[4]; XCTAssertEqual(key.key.modifier, 0); XCTAssertEqual(key.key.keyCode, kVK_F1); /* {F2} */ - key = commands[2]; + key = commands[5]; XCTAssertEqual(key.key.modifier, 0); XCTAssertEqual(key.key.keyCode, kVK_F2); /* {F3} */ - key = commands[3]; + key = commands[6]; XCTAssertEqual(key.key.modifier, 0); XCTAssertEqual(key.key.keyCode, kVK_F3); /* {F4} */ - key = commands[4]; + key = commands[7]; XCTAssertEqual(key.key.modifier, 0); XCTAssertEqual(key.key.keyCode, kVK_F4); /* {F5} */ - key = commands[5]; + key = commands[8]; XCTAssertEqual(key.key.modifier, 0); XCTAssertEqual(key.key.keyCode, kVK_F5); /* ^%{F6} */ - key = commands[6]; + key = commands[9]; XCTAssertEqual(key.key.modifier, (kCGEventFlagMaskCommand | kCGEventFlagMaskAlternate)); XCTAssertEqual(key.key.keyCode, kVK_F6); /* {F7} */ - key = commands[7]; + key = commands[10]; XCTAssertEqual(key.key.modifier, 0); XCTAssertEqual(key.key.keyCode, kVK_F7); /* {F19} */ - key = commands[8]; + key = commands[11]; XCTAssertEqual(key.key.modifier, 0); XCTAssertEqual(key.key.keyCode, kVK_F19); - paste = commands[9]; - XCTAssertEqualObjects(paste.pasteData, @"{F20}"); + /* {F20} -> invalid, type */ + key = commands[12]; + XCTAssertEqualObjects(key.character, @"{"); + + key = commands[13]; + XCTAssertEqualObjects(key.character, @"F"); + + key = commands[14]; + XCTAssertEqualObjects(key.character, @"2"); + + key = commands[15]; + XCTAssertEqualObjects(key.character, @"0"); + + key = commands[16]; + XCTAssertEqualObjects(key.character, @"}"); + } - (void)testCommandCreation { @@ -161,38 +260,117 @@ MPAutotypeContext *context = [[MPAutotypeContext alloc] initWithEntry:self.entry andSequence:@"{USERNAME}{TAB}{PASSWORD}{ENTER}"]; NSArray *commands = [MPAutotypeCommand commandsForContext:context]; - XCTAssertEqual(commands.count, 4); - XCTAssertTrue([commands[0] isKindOfClass:[MPAutotypePaste class]]); - XCTAssertTrue([commands[1] isKindOfClass:[MPAutotypeKeyPress class]]); - XCTAssertTrue([commands[2] isKindOfClass:[MPAutotypePaste class]]); - XCTAssertTrue([commands[3] isKindOfClass:[MPAutotypeKeyPress class]]); + XCTAssertEqual(commands.count, 20); - /* {USERNAME} */ - MPAutotypePaste *paste = commands[0]; - XCTAssertEqualObjects(paste.pasteData, self.entry.username); + /* {USERNAME} -> type U s e r N a m e*/ + MPAutotypeKeyPress *keyPress = commands[0]; + XCTAssertEqualObjects(keyPress.character, @"U"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[1]; + XCTAssertEqualObjects(keyPress.character, @"s"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[2]; + XCTAssertEqualObjects(keyPress.character, @"e"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[3]; + XCTAssertEqualObjects(keyPress.character, @"r"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[4]; + XCTAssertEqualObjects(keyPress.character, @" "); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[5]; + XCTAssertEqualObjects(keyPress.character, @"N"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[6]; + XCTAssertEqualObjects(keyPress.character, @"a"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[7]; + XCTAssertEqualObjects(keyPress.character, @"m"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[8]; + XCTAssertEqualObjects(keyPress.character, @"e"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); /* {TAB} */ - MPAutotypeKeyPress *keyPress = commands[1]; + keyPress = commands[9]; + XCTAssertNil(keyPress.character); XCTAssertEqual(keyPress.key.keyCode, kVK_Tab); // Tab is a fixed key, no mapping needed XCTAssertEqual(keyPress.key.modifier, 0); - /* {PASSWORD} */ - paste = commands[2]; - XCTAssertEqualObjects(self.entry.password, paste.pasteData); + /* {PASSWORD} -> type P a s s 👩🏿‍🔧 w o r d */ + keyPress = commands[10]; + XCTAssertEqualObjects(keyPress.character, @"P"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[11]; + XCTAssertEqualObjects(keyPress.character, @"a"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[12]; + XCTAssertEqualObjects(keyPress.character, @"s"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[13]; + XCTAssertEqualObjects(keyPress.character, @"s"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[14]; + XCTAssertEqualObjects(keyPress.character, @"👩🏿‍🔧"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[15]; + XCTAssertEqualObjects(keyPress.character, @"w"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[16]; + XCTAssertEqualObjects(keyPress.character, @"o"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[17]; + XCTAssertEqualObjects(keyPress.character, @"r"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[18]; + XCTAssertEqualObjects(keyPress.character, @"d"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + /* {ENTER} */ - keyPress = commands[3]; + keyPress = commands[19]; + XCTAssertNil(keyPress.character); XCTAssertEqual(keyPress.key.keyCode, kVK_Return); + XCTAssertEqual(keyPress.key.modifier, 0); /* Command 2 */ context = [[MPAutotypeContext alloc] initWithEntry:self.entry andSequence:@"^T{USERNAME}%+^{TAB}Whoo{PASSWORD}{ENTER}"]; commands = [MPAutotypeCommand commandsForContext:context]; - XCTAssertEqual(commands.count, 5); - XCTAssertTrue([commands[0] isKindOfClass:[MPAutotypeKeyPress class]]); - XCTAssertTrue([commands[1] isKindOfClass:[MPAutotypePaste class]]); - XCTAssertTrue([commands[2] isKindOfClass:[MPAutotypeKeyPress class]]); - XCTAssertTrue([commands[3] isKindOfClass:[MPAutotypePaste class]]); - XCTAssertTrue([commands[4] isKindOfClass:[MPAutotypeKeyPress class]]); + XCTAssertEqual(commands.count, 25); /* ^T */ keyPress = commands[0]; @@ -206,12 +384,54 @@ XCTAssertEqual(keyPress.key.modifier, kCGEventFlagMaskControl); } - /* {USERNAME} */ - paste = commands[1]; - XCTAssertEqualObjects(paste.pasteData, self.entry.username); + /* {USERNAME} -> type U s e r N a m e */ + keyPress = commands[1]; + XCTAssertEqualObjects(keyPress.character, @"U"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[2]; + XCTAssertEqualObjects(keyPress.character, @"s"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[3]; + XCTAssertEqualObjects(keyPress.character, @"e"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[4]; + XCTAssertEqualObjects(keyPress.character, @"r"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[5]; + XCTAssertEqualObjects(keyPress.character, @" "); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[6]; + XCTAssertEqualObjects(keyPress.character, @"N"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[7]; + XCTAssertEqualObjects(keyPress.character, @"a"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[8]; + XCTAssertEqualObjects(keyPress.character, @"m"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[9]; + XCTAssertEqualObjects(keyPress.character, @"e"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); /* %+^{TAB} */ - keyPress = commands[2]; + keyPress = commands[10]; XCTAssertEqual(keyPress.key.keyCode, kVK_Tab); // Tab is a fixed key, no mapping needed if(useCommandInsteadOfControl) { XCTAssertEqual(keyPress.key.modifier, (kCGEventFlagMaskCommand | kCGEventFlagMaskShift | kCGEventFlagMaskAlternate)); @@ -220,13 +440,75 @@ XCTAssertEqual(keyPress.key.modifier, (kCGEventFlagMaskControl | kCGEventFlagMaskShift | kCGEventFlagMaskAlternate)); } - /* Whoo{PASSWORD} */ - paste = commands[3]; - NSString *pasteString = [[NSString alloc] initWithFormat:@"%@%@", @"Whoo", self.entry.password]; - XCTAssertEqualObjects(pasteString, paste.pasteData); + /* Whoo{PASSWORD} -> type W h o o P a s s w o r d */ + keyPress = commands[11]; + XCTAssertEqualObjects(keyPress.character, @"W"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[12]; + XCTAssertEqualObjects(keyPress.character, @"h"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[13]; + XCTAssertEqualObjects(keyPress.character, @"o"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[14]; + XCTAssertEqualObjects(keyPress.character, @"o"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[15]; + XCTAssertEqualObjects(keyPress.character, @"P"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[16]; + XCTAssertEqualObjects(keyPress.character, @"a"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[17]; + XCTAssertEqualObjects(keyPress.character, @"s"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[18]; + XCTAssertEqualObjects(keyPress.character, @"s"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[19]; + XCTAssertEqualObjects(keyPress.character, @"👩🏿‍🔧"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[20]; + XCTAssertEqualObjects(keyPress.character, @"w"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[21]; + XCTAssertEqualObjects(keyPress.character, @"o"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[22]; + XCTAssertEqualObjects(keyPress.character, @"r"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); + + keyPress = commands[23]; + XCTAssertEqualObjects(keyPress.character, @"d"); + XCTAssertEqual(keyPress.key.keyCode, 0); + XCTAssertEqual(keyPress.key.modifier, 0); /* {ENTER} */ - keyPress = commands[4]; + keyPress = commands[24]; + XCTAssertNil(keyPress.character); XCTAssertEqual(keyPress.key.keyCode, kVK_Return); XCTAssertEqual(keyPress.key.modifier, 0);