mirror of
https://github.com/MacPass/MacPass.git
synced 2025-12-13 08:52:20 +00:00
Autotype now uses key presses instead of paste whenever possible.
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
4C32B0E51A1D4436007E12F1 /* KPKFormat+MPUTIDetection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "KPKFormat+MPUTIDetection.h"; sourceTree = "<group>"; };
|
||||
4C32B0E61A1D4436007E12F1 /* KPKFormat+MPUTIDetection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "KPKFormat+MPUTIDetection.m"; sourceTree = "<group>"; };
|
||||
4C3499FE218852160055AF45 /* MPKeyTyper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPKeyTyper.h; sourceTree = "<group>"; };
|
||||
4C3499FF218852160055AF45 /* MPKeyTyper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPKeyTyper.m; sourceTree = "<group>"; };
|
||||
4C3666401787327E00B249F1 /* MPDocument+Attachments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPDocument+Attachments.m"; sourceTree = "<group>"; };
|
||||
4C370EFC215B76CB00703AAE /* MPOutlineTableCellView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPOutlineTableCellView.h; sourceTree = "<group>"; };
|
||||
4C370EFD215B76CB00703AAE /* MPOutlineTableCellView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPOutlineTableCellView.m; sourceTree = "<group>"; };
|
||||
@@ -535,6 +539,8 @@
|
||||
4C5CD34617D15912000B7F38 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/MainMenu.strings; sourceTree = "<group>"; };
|
||||
4C5CD34717D1591A000B7F38 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/PasswordInputView.strings; sourceTree = "<group>"; };
|
||||
4C5CD34817D15920000B7F38 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InspectorView.strings; sourceTree = "<group>"; };
|
||||
4C5EF814218CA03F0003C00E /* MPAutotypeParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPAutotypeParser.h; sourceTree = "<group>"; };
|
||||
4C5EF815218CA03F0003C00E /* MPAutotypeParser.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPAutotypeParser.m; sourceTree = "<group>"; };
|
||||
4C5F72851FC4351E00929153 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InspectorView.strings; sourceTree = "<group>"; };
|
||||
4C5FE9AC17843CE20001D5A8 /* MPSelectedAttachmentTableCellView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSelectedAttachmentTableCellView.h; sourceTree = "<group>"; };
|
||||
4C5FE9AD17843CE20001D5A8 /* MPSelectedAttachmentTableCellView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSelectedAttachmentTableCellView.m; sourceTree = "<group>"; };
|
||||
@@ -1351,6 +1357,18 @@
|
||||
path = Icons;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4C5EF817218CA0470003C00E /* Parser */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C8F0C6F1FCEF91400BE157F /* MPPickcharsParser.h */,
|
||||
4C088C401FD9A42800F92502 /* MPPickcharsParser_Private.h */,
|
||||
4C8F0C701FCEF91400BE157F /* MPPickcharsParser.m */,
|
||||
4C5EF814218CA03F0003C00E /* MPAutotypeParser.h */,
|
||||
4C5EF815218CA03F0003C00E /* MPAutotypeParser.m */,
|
||||
);
|
||||
name = Parser;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
@@ -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;
|
||||
|
||||
@@ -22,12 +22,14 @@
|
||||
|
||||
#import "MPAutotypeClear.h"
|
||||
#import "MPKeyMapper.h"
|
||||
#import "MPKeyTyper.h"
|
||||
|
||||
#import <Carbon/Carbon.h>
|
||||
|
||||
@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
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#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.
|
||||
*/
|
||||
|
||||
@@ -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 <Carbon/Carbon.h>
|
||||
#import <CommonCrypto/CommonCrypto.h>
|
||||
|
||||
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<NSValue *> *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 <appname>}
|
||||
/* 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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
25
MacPass/MPAutotypeParser.h
Normal file
25
MacPass/MPAutotypeParser.h
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// MPAutotypeParser.h
|
||||
// MacPass
|
||||
//
|
||||
// Created by Michael Starke on 02.11.18.
|
||||
// Copyright © 2018 HicknHack Software GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class MPAutotypeCommand;
|
||||
@class MPAutotypeContext;
|
||||
|
||||
@interface MPAutotypeParser : NSObject
|
||||
|
||||
@property (nonatomic, copy, readonly) NSArray<MPAutotypeCommand *> *commands;
|
||||
@property (readonly, strong) MPAutotypeContext *context;
|
||||
|
||||
- (instancetype)initWithContext:(MPAutotypeContext *)context;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
428
MacPass/MPAutotypeParser.m
Normal file
428
MacPass/MPAutotypeParser.m
Normal file
@@ -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 <KeePassKit/KeePassKit.h>
|
||||
|
||||
#import <Carbon/Carbon.h>
|
||||
#import <CommonCrypto/CommonCrypto.h>
|
||||
|
||||
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<MPAutotypeCommand *> *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<MPAutotypeCommand *> *)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 <appname>}
|
||||
/* 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<NSValue *> *typeKeys = [[NSMutableArray alloc] init];
|
||||
NSMutableArray<MPAutotypeCommand *> *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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MPModifiedKey.h"
|
||||
|
||||
FOUNDATION_EXTERN uint16_t const kMPUnknownKeyCode;
|
||||
|
||||
@interface MPKeyMapper : NSObject
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
36
MacPass/MPKeyTyper.h
Normal file
36
MacPass/MPKeyTyper.h
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// MPKeyboardTyper.h
|
||||
// MacPass
|
||||
//
|
||||
// Created by Michael Starke on 30.10.18.
|
||||
// Copyright © 2018 HicknHack Software GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#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
|
||||
79
MacPass/MPKeyTyper.m
Normal file
79
MacPass/MPKeyTyper.m
Normal file
@@ -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
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user