From 2216a14729df06802348fbc0a3256414f86c0bf8 Mon Sep 17 00:00:00 2001 From: Michael Starke Date: Tue, 21 Jan 2020 14:38:52 +0100 Subject: [PATCH 1/2] Broken WIP commit --- MacPass.xcodeproj/project.pbxproj | 12 ++ ...AutotypeCandidateSelectionViewController.h | 4 +- ...AutotypeCandidateSelectionViewController.m | 6 - MacPass/MPAutotypeDaemon.h | 6 +- MacPass/MPAutotypeDaemon.m | 154 ++++++------------ MacPass/MPAutotypeEnvironment.h | 30 ++++ MacPass/MPAutotypeEnvironment.m | 84 ++++++++++ MacPass/NSRunningApplication+MPAdditions.h | 25 +++ MacPass/NSRunningApplication+MPAdditions.m | 50 ++++++ 9 files changed, 261 insertions(+), 110 deletions(-) create mode 100644 MacPass/MPAutotypeEnvironment.h create mode 100644 MacPass/MPAutotypeEnvironment.m create mode 100644 MacPass/NSRunningApplication+MPAdditions.h create mode 100644 MacPass/NSRunningApplication+MPAdditions.m diff --git a/MacPass.xcodeproj/project.pbxproj b/MacPass.xcodeproj/project.pbxproj index 29f63e54..366eb7e8 100644 --- a/MacPass.xcodeproj/project.pbxproj +++ b/MacPass.xcodeproj/project.pbxproj @@ -41,6 +41,8 @@ 4C1F7FA21E3A12E600D6A40E /* MPModifiedKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C1F7FA11E3A12E600D6A40E /* MPModifiedKey.m */; }; 4C1FA07B18231900003A3F8C /* MPDocument+Autotype.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C1FA07A18231900003A3F8C /* MPDocument+Autotype.m */; }; 4C2057EE23CDF6F900C731EC /* MPPathCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C2057ED23CDF6F900C731EC /* MPPathCell.m */; }; + 4C2057F423CF3BA600C731EC /* MPAutotypeEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C2057F323CF3BA600C731EC /* MPAutotypeEnvironment.m */; }; + 4C2057F723CF3E9A00C731EC /* NSRunningApplication+MPAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C2057F623CF3E9A00C731EC /* NSRunningApplication+MPAdditions.m */; }; 4C224B4217DFCB2400FF6AEE /* MPNumericalInputFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C224B4117DFCB2400FF6AEE /* MPNumericalInputFormatter.m */; }; 4C25703F1BF11C2300D39416 /* MPPluginPreferencesController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C25703D1BF11C2300D39416 /* MPPluginPreferencesController.m */; }; 4C25D58716CF0FAA00F6806C /* EntryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4C25D58616CF0FAA00F6806C /* EntryView.xib */; }; @@ -405,6 +407,10 @@ 4C2057EC23CDF6F900C731EC /* MPPathCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPPathCell.h; sourceTree = ""; }; 4C2057ED23CDF6F900C731EC /* MPPathCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPPathCell.m; sourceTree = ""; }; 4C2057EF23CDFC2000C731EC /* MPPathControl+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPPathControl+Private.h"; sourceTree = ""; }; + 4C2057F223CF3BA600C731EC /* MPAutotypeEnvironment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPAutotypeEnvironment.h; sourceTree = ""; }; + 4C2057F323CF3BA600C731EC /* MPAutotypeEnvironment.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPAutotypeEnvironment.m; sourceTree = ""; }; + 4C2057F523CF3E9A00C731EC /* NSRunningApplication+MPAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSRunningApplication+MPAdditions.h"; sourceTree = ""; }; + 4C2057F623CF3E9A00C731EC /* NSRunningApplication+MPAdditions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSRunningApplication+MPAdditions.m"; sourceTree = ""; }; 4C21F29F195B3A48002D610D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/MainMenu.strings; sourceTree = ""; }; 4C224B4017DFCB2300FF6AEE /* MPNumericalInputFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNumericalInputFormatter.h; sourceTree = ""; }; 4C224B4117DFCB2400FF6AEE /* MPNumericalInputFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNumericalInputFormatter.m; sourceTree = ""; }; @@ -1128,6 +1134,8 @@ 4C58A4A22192EC1600B13370 /* NSIndexPath+MPAdditions.m */, 3C0CDECD21CFED9000B2A10B /* NSTextView+MPTouchBarExtension.h */, 3C0CDECE21CFEDD200B2A10B /* NSTextView+MPTouchBarExtension.m */, + 4C2057F523CF3E9A00C731EC /* NSRunningApplication+MPAdditions.h */, + 4C2057F623CF3E9A00C731EC /* NSRunningApplication+MPAdditions.m */, ); name = Categories; sourceTree = ""; @@ -1521,6 +1529,8 @@ 4C90757B18A42E7A00E598DA /* Commands */, 4CEE46DB181C301D006BF1E5 /* MPAutotypeDaemon.h */, 4CEE46DC181C301D006BF1E5 /* MPAutotypeDaemon.m */, + 4C2057F223CF3BA600C731EC /* MPAutotypeEnvironment.h */, + 4C2057F323CF3BA600C731EC /* MPAutotypeEnvironment.m */, 4CD2B9041849424B0051B395 /* MPAutotypeContext.h */, 4CD2B9051849424B0051B395 /* MPAutotypeContext.m */, 4CA3530918A53CB800839B0F /* MPKeyMapper.h */, @@ -2044,6 +2054,7 @@ 4CE39AC416ECE4F7000FE29D /* MPIconImageView.m in Sources */, 4C46B88517063A070046109A /* NSString+MPPasswordCreation.m in Sources */, 4C5A11FE1708DE8700223D8A /* MPPasswordCreatorViewController.m in Sources */, + 4C2057F423CF3BA600C731EC /* MPAutotypeEnvironment.m in Sources */, 4CE5B54B173AFBA700207B39 /* MPDocument.m in Sources */, 4CE082C31F6FCD2A0034FF56 /* MPCollectionView.m in Sources */, 4C4A100F176286FD00BBF2CA /* MPTableView.m in Sources */, @@ -2092,6 +2103,7 @@ 4CAD8AA622CF397B0090B2DD /* MPAutotypeDoctorReportViewController.m in Sources */, 4C8990F71EE978EB0043B48D /* MPDuplicateEntryOptionsWindowController.m in Sources */, 4CA3530B18A53CB800839B0F /* MPKeyMapper.m in Sources */, + 4C2057F723CF3E9A00C731EC /* NSRunningApplication+MPAdditions.m in Sources */, 4CE298EB1795FC2A00DF7BDB /* MPEntryContextMenuDelegate.m in Sources */, 4CCCE8011D75CA48006AA951 /* MPArrayController.m in Sources */, 4CC0D2CE17974A47000B4BDA /* MPCustomFieldTableViewDelegate.m in Sources */, diff --git a/MacPass/MPAutotypeCandidateSelectionViewController.h b/MacPass/MPAutotypeCandidateSelectionViewController.h index e74fc5d8..863d7255 100644 --- a/MacPass/MPAutotypeCandidateSelectionViewController.h +++ b/MacPass/MPAutotypeCandidateSelectionViewController.h @@ -24,11 +24,13 @@ NS_ASSUME_NONNULL_BEGIN +@class MPAutotypeEnvironment; + @interface MPAutotypeCandidateSelectionViewController : NSViewController +@property (strong) MPAutotypeEnvironment *environment; @property (copy) NSArray *candidates; @property (copy) NSString *windowTitle; -@property (nonatomic, copy, nullable) void (^completionHandler)(void); - (IBAction)selectAutotypeContext:(id)sender; - (IBAction)cancelSelection:(id)sender; diff --git a/MacPass/MPAutotypeCandidateSelectionViewController.m b/MacPass/MPAutotypeCandidateSelectionViewController.m index e5123bdd..8a6925c4 100644 --- a/MacPass/MPAutotypeCandidateSelectionViewController.m +++ b/MacPass/MPAutotypeCandidateSelectionViewController.m @@ -80,9 +80,6 @@ - (void)selectAutotypeContext:(id)sender { NSInteger selectedRow = self.contextTableView.selectedRow; if(selectedRow >= 0 && selectedRow < self.candidates.count) { - if(self.completionHandler) { - self.completionHandler(); - } [MPAutotypeDaemon.defaultDaemon selectAutotypeCandiate:self.candidates[selectedRow]]; } else { @@ -91,9 +88,6 @@ } - (void)cancelSelection:(id)sender { - if(self.completionHandler) { - self.completionHandler(); - } [MPAutotypeDaemon.defaultDaemon cancelAutotypeCandidateSelection]; } diff --git a/MacPass/MPAutotypeDaemon.h b/MacPass/MPAutotypeDaemon.h index da0e0007..556ce554 100644 --- a/MacPass/MPAutotypeDaemon.h +++ b/MacPass/MPAutotypeDaemon.h @@ -25,7 +25,7 @@ @class DDHotKey; @class KPKEntry; @class MPAutotypeContext; -@class MPAutotypeExecutionContext; +@class MPAutotypeEnvironment; /** * The autotype daemon is responsible for registering the global hotkey and to perform any autotype actions */ @@ -40,7 +40,7 @@ - (void)performAutotypeForEntry:(KPKEntry *)entry; - (void)performAutotypeForEntry:(KPKEntry *)entry overrideSequence:(NSString *)sequence; -- (void)selectAutotypeCandiate:(MPAutotypeContext *)context; -- (void)cancelAutotypeCandidateSelection; +- (void)selectAutotypeCandiate:(MPAutotypeContext *)context forEnvironment:(MPAutotypeEnvironment *)environment; +- (void)cancelAutotypeCandidateSelectionForEnvironment:(MPAutotypeEnvironment *)environment; @end diff --git a/MacPass/MPAutotypeDaemon.m b/MacPass/MPAutotypeDaemon.m index 01b066ec..77599e62 100644 --- a/MacPass/MPAutotypeDaemon.m +++ b/MacPass/MPAutotypeDaemon.m @@ -25,6 +25,7 @@ #import "MPDocumentWindowController.h" #import "MPAutotypeCommand.h" #import "MPAutotypeContext.h" +#import "MPAutotypeEnvironment.h" #import "MPAutotypePaste.h" #import "MPAutotypeDelay.h" #import "MPPasteBoardController.h" @@ -45,16 +46,12 @@ #import "KeePassKit/KeePassKit.h" #import -NSString *const kMPWindowTitleKey = @"kMPWindowTitleKey"; -NSString *const kMPProcessIdentifierKey = @"kMPProcessIdentifierKey"; - @interface MPAutotypeDaemon () @property (nonatomic, assign) BOOL enabled; @property (nonatomic, copy) NSData *hotKeyData; @property (strong) DDHotKey *registredHotKey; -@property (assign) pid_t targetPID; // The pid of the process we want to sent commands to -@property (copy) NSString *targetWindowTitle; // The title of the window that we are targeting + @property (strong) NSRunningApplication *previousApplication; // The application that was active before we got invoked @property (assign) NSTimeInterval userActionRequested; @property (strong) id applicationActivationObserver; @@ -85,7 +82,6 @@ static MPAutotypeDaemon *_sharedInstance; self = [super init]; if (self) { _enabled = NO; - _targetPID = -1; _userActionRequested = NSDate.distantPast.timeIntervalSinceReferenceDate; [self bind:NSStringFromSelector(@selector(enabled)) toObject:NSUserDefaultsController.sharedUserDefaultsController @@ -144,27 +140,28 @@ static MPAutotypeDaemon *_sharedInstance; - (void)performAutotypeForEntry:(KPKEntry *)entry { [self performAutotypeForEntry:entry overrideSequence:nil]; } + - (void)performAutotypeForEntry:(KPKEntry *)entry overrideSequence:(NSString *)sequence { if(entry) { - [self _updateTargeInformationForApplication:self.previousApplication]; - [self _performAutotypeForEntry:entry]; + MPAutotypeEnvironment *env = [MPAutotypeEnvironment environmentWithTargetApplication:self.previousApplication entry:entry]; + [self _runAutotypeWithEnvironment:env]; } } - (void)_didPressHotKey { - [self _updateTargeInformationForApplication:NSWorkspace.sharedWorkspace.frontmostApplication]; - [self _performAutotypeForEntry:nil]; + MPAutotypeEnvironment *env = [MPAutotypeEnvironment environmentWithTargetApplication:NSWorkspace.sharedWorkspace.frontmostApplication entry:nil]; + [self _runAutotypeWithEnvironment:env]; } #pragma mark - #pragma mark Actions -- (void)selectAutotypeCandiate:(MPAutotypeContext *)context { +- (void)selectAutotypeCandiate:(MPAutotypeContext *)context forEnvironment:(MPAutotypeEnvironment *)environment { [self.matchSelectionWindow orderOut:self]; self.matchSelectionWindow = nil; - [self _performAutotypeForContext:context]; + [self _runAutotypeWithEnvirnment:environment forContext:context]; } -- (void)cancelAutotypeCandidateSelection { +- (void)cancelAutotypeCandidateSelectionForEnvironment:(MPAutotypeEnvironment *)environment { [self.matchSelectionWindow orderOut:self]; self.matchSelectionWindow = nil; if(self.targetPID) { @@ -174,8 +171,22 @@ static MPAutotypeDaemon *_sharedInstance; #pragma mark - #pragma mark Autotype Execution +- (void)_runAutotypeAfterDatabaseUnlockWithEnvironment:(MPAutotypeEnvironment *)environment requestedAt:(NSTimeInterval)requestTime { + NSTimeInterval now = NSDate.date.timeIntervalSinceReferenceDate; + if(now - requestTime > 30) { + NSUserNotification *notification = [[NSUserNotification alloc] init]; + notification.title = NSApp.applicationName; + notification.informativeText = NSLocalizedString(@"AUTOTYPE_TIMED_OUT", "Notficication: Autotype timed out"); + notification.userInfo = @{ MPUserNotificationTypeKey: MPUserNotificationTypeAutotypeFeedback }; + [NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:notification]; + } + else { + [self _runAutotypeWithEnvironment:environment]; + } +} + +- (void)_runAutotypeWithEnvironment:(MPAutotypeEnvironment *)env { -- (void)_performAutotypeForEntry:(KPKEntry *)entryOrNil { if(!self.hasNecessaryAutotypePermissions) { NSUserNotification *notification = [[NSUserNotification alloc] init]; notification.title = NSApp.applicationName; @@ -186,10 +197,6 @@ static MPAutotypeDaemon *_sharedInstance; [NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:notification]; return; } - NSInteger pid = NSProcessInfo.processInfo.processIdentifier; - if(self.targetPID == pid) { - return; // We do not perform Autotype on ourselves - } /* find autotype documents */ NSArray *documents = NSApp.orderedDocuments; @@ -202,8 +209,15 @@ static MPAutotypeDaemon *_sharedInstance; notification.userInfo = @{ MPUserNotificationTypeKey: MPUserNotificationTypeAutotypeOpenDocumentRequest }; notification.showsButtons = YES; [NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:notification]; - self.userActionRequested = NSDate.date.timeIntervalSinceReferenceDate; - [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_didUnlockDatabase:) name:MPDocumentDidUnlockDatabaseNotification object:nil]; + NSNotificationCenter * __weak nc = [NSNotificationCenter defaultCenter]; + MPAutotypeDaemon * __weak welf = self; + id __block unlockToken = [nc addObserverForName:MPDocumentDidUnlockDatabaseNotification + object:nil + queue:NSOperationQueue.mainQueue + usingBlock:^(NSNotification *notification) { + [welf _runAutotypeAfterDatabaseUnlockWithEnvironment:env requestedAt:NSDate.date.timeIntervalSinceReferenceDate]; + [nc removeObserver:unlockToken]; + }]; return; // Unlock should trigger autotype } @@ -211,6 +225,7 @@ static MPAutotypeDaemon *_sharedInstance; MPDocument *document = evaluatedObject; return !document.encrypted; }]; + NSArray *unlockedDocuments = [documents filteredArrayUsingPredicate:filterPredicate]; /* We look for all unlocked documents, if all open documents are locked, we pop the front most and try to search again */ if(unlockedDocuments.count == 0) { @@ -221,25 +236,33 @@ static MPAutotypeDaemon *_sharedInstance; [document showWindows]; MPDocumentWindowController *wc = document.windowControllers.firstObject; [wc showPasswordInputWithMessage:NSLocalizedString(@"AUTOTYPE_MESSAGE_UNLOCK_DATABASE", @"Message displayed to the user to unlock the database to perform global autotype")]; - self.userActionRequested = NSDate.date.timeIntervalSinceReferenceDate; - [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_didUnlockDatabase:) name:MPDocumentDidUnlockDatabaseNotification object:nil]; + NSNotificationCenter * __weak nc = [NSNotificationCenter defaultCenter]; + MPAutotypeDaemon * __weak welf = self; + id __block unlockToken = [nc addObserverForName:MPDocumentDidUnlockDatabaseNotification + object:nil + queue:NSOperationQueue.mainQueue + usingBlock:^(NSNotification *notification) { + [welf _runAutotypeAfterDatabaseUnlockWithEnvironment:env requestedAt:NSDate.date.timeIntervalSinceReferenceDate]; + + [nc removeObserver:unlockToken]; + }]; return; // wait for the unlock to happen } - MPAutotypeContext *context = [self _autotypeContextForDocuments:documents forWindowTitle:self.targetWindowTitle preferredEntry:entryOrNil]; + MPAutotypeContext *context = [self _autotypeContextForDocuments:documents forWindowTitle:env.windowTitle preferredEntry:env.entry]; /* TODO: that's popping up if the multi selection dialog goes up! */ if(self.matchSelectionWindow) { return; // we present the match selection window, just return } - if(!entryOrNil) { + if(!env.entry) { NSUserNotification *notification = [[NSUserNotification alloc] init]; notification.title = NSApp.applicationName; notification.userInfo = @{ MPUserNotificationTypeKey: MPUserNotificationTypeAutotypeFeedback }; if(context) { - notification.informativeText = [NSString stringWithFormat:NSLocalizedString(@"AUTOTYPE_OVERLAY_SINGLE_MATCH_FOR_%@", "Notification: Autotype found a single match for %@ (string placeholder)."), self.targetWindowTitle]; + notification.informativeText = [NSString stringWithFormat:NSLocalizedString(@"AUTOTYPE_OVERLAY_SINGLE_MATCH_FOR_%@", "Notification: Autotype found a single match for %@ (string placeholder)."), env.windowTitle]; } else { - notification.informativeText = [NSString stringWithFormat:NSLocalizedString(@"AUTOTYPE_OVERLAY_NO_MATCH_FOR_%@", "Noticiation: Autotype failed to find a match for %@ (string placeholder)"), self.targetWindowTitle]; + notification.informativeText = [NSString stringWithFormat:NSLocalizedString(@"AUTOTYPE_OVERLAY_NO_MATCH_FOR_%@", "Noticiation: Autotype failed to find a match for %@ (string placeholder)"), env.windowTitle]; } [NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:notification]; } @@ -267,7 +290,10 @@ static MPAutotypeDaemon *_sharedInstance; return nil; // Nothing to do, we get called back by the window } -- (void)_performAutotypeForContext:(MPAutotypeContext *)context { +- (void)_runAutotypeWithEnvirnment:(MPAutotypeEnvironment *)environment forContext:(MPAutotypeContext *)context { + if(nil == environment) { + return; // no Environment to work in + } if(nil == context) { return; // No context to work with } @@ -320,37 +346,6 @@ static MPAutotypeDaemon *_sharedInstance; } } -- (NSDictionary *)_infoDictionaryForApplication:(NSRunningApplication *)application { - NSArray *currentWindows = CFBridgingRelease(CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)); - NSArray *windowNumbers = [NSWindow windowNumbersWithOptions:NSWindowNumberListAllApplications]; - NSUInteger minZIndex = NSNotFound; - NSDictionary *infoDict = nil; - for(NSDictionary *windowDict in currentWindows) { - NSString *windowTitle = windowDict[(NSString *)kCGWindowName]; - if(windowTitle.length <= 0) { - continue; - } - NSNumber *processId = windowDict[(NSString *)kCGWindowOwnerPID]; - if(processId && [processId isEqualToNumber:@(application.processIdentifier)]) { - - NSNumber *number = (NSNumber *)windowDict[(NSString *)kCGWindowNumber]; - NSUInteger zIndex = [windowNumbers indexOfObject:number]; - if(zIndex < minZIndex) { - minZIndex = zIndex; - infoDict = @{ - kMPWindowTitleKey: windowTitle, - kMPProcessIdentifierKey : processId - }; - } - } - } - if(currentWindows.count > 0 && infoDict.count == 0) { - // show some information about not being able to determine any windows - NSLog(@"Unable to retrieve any window names. If you encounter this issue you might be running 10.15 and MacPass has no permission for screen recording."); - } - return infoDict; -} - - (void)_presentCandiadates:(NSArray *)candidates forWindowTitle:(NSString *)windowTitle { if(!self.matchSelectionWindow) { self.matchSelectionWindow = [[NSPanel alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100) @@ -376,24 +371,6 @@ static MPAutotypeDaemon *_sharedInstance; [self.matchSelectionWindow makeKeyAndOrderFront:self]; } -#pragma mark - -#pragma mark MPDocument Notifications -- (void)_didUnlockDatabase:(NSNotification *)notification { - /* Remove ourselves and call again to search matches */ - [NSNotificationCenter.defaultCenter removeObserver:self name:MPDocumentDidUnlockDatabaseNotification object:nil]; - NSTimeInterval now = NSDate.date.timeIntervalSinceReferenceDate; - if(now - self.userActionRequested > 30) { - NSUserNotification *notification = [[NSUserNotification alloc] init]; - notification.title = NSApp.applicationName; - notification.informativeText = NSLocalizedString(@"AUTOTYPE_TIMED_OUT", "Notficication: Autotype timed out"); - notification.userInfo = @{ MPUserNotificationTypeKey: MPUserNotificationTypeAutotypeFeedback }; - [NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:notification]; - } - else { - [self _performAutotypeForEntry:nil]; - } -} - #pragma mark - #pragma mark NSApplication Notifications - (void)_didDeactivateApplication:(NSNotification *)notification { @@ -403,6 +380,7 @@ static MPAutotypeDaemon *_sharedInstance; #pragma mark - #pragma mark Application information +- (BOOL)_orderApplicationToFront:(pid_t)processIdentifier inEnvironment:(MPAutotypeEnvironment *) environment { - (BOOL)_orderApplicationToFront:(pid_t)processIdentifier forContext:(MPAutotypeContext *)context { NSRunningApplication *runingApplication = [NSRunningApplication runningApplicationWithProcessIdentifier:processIdentifier]; NSRunningApplication *frontApplication = NSWorkspace.sharedWorkspace.frontmostApplication; @@ -426,28 +404,4 @@ static MPAutotypeDaemon *_sharedInstance; [runingApplication activateWithOptions:NSApplicationActivateIgnoringOtherApps]; return NO; } - -- (void)_updateTargeInformationForApplication:(NSRunningApplication *)application { - if(!application) { - self.targetPID = -1; - self.targetWindowTitle = @""; - } - else { - NSDictionary *frontApplicationInfoDict = [self _infoDictionaryForApplication:application]; - - self.targetPID = [frontApplicationInfoDict[kMPProcessIdentifierKey] intValue]; - self.targetWindowTitle = frontApplicationInfoDict[kMPWindowTitleKey]; - - /* if we have any resolvers, let them provide the window title */ - NSArray *resolvers = [MPPluginHost.sharedHost windowTitleResolverForRunningApplication:application]; - for(MPPlugin *resolver in resolvers) { - NSString *windowTitle = [resolver windowTitleForRunningApplication:application]; - if(windowTitle.length > 0) { - self.targetWindowTitle = windowTitle; - break; - } - } - } -} - @end diff --git a/MacPass/MPAutotypeEnvironment.h b/MacPass/MPAutotypeEnvironment.h new file mode 100644 index 00000000..0ce08abd --- /dev/null +++ b/MacPass/MPAutotypeEnvironment.h @@ -0,0 +1,30 @@ +// +// MPAutotypeEnvironment.h +// MacPass +// +// Created by Michael Starke on 15.01.20. +// Copyright © 2020 HicknHack Software GmbH. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class KPKEntry; +@class MPAutotypeContext; + +@interface MPAutotypeEnvironment : NSObject + +@property (readonly, weak) KPKEntry *entry; +@property (readonly) pid_t pid; +@property (readonly, copy) NSString *windowTitle; +@property (readonly) BOOL hidden; +@property (readonly) BOOL isSelfTargeting; + ++ (instancetype)environmentWithTargetApplication:(NSRunningApplication *)targetApplication entry:(KPKEntry * _Nullable)entry; +- (instancetype)initWithTargetApplication:(NSRunningApplication *)targetApplication entry:(KPKEntry * _Nullable)entry NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MacPass/MPAutotypeEnvironment.m b/MacPass/MPAutotypeEnvironment.m new file mode 100644 index 00000000..80a483a8 --- /dev/null +++ b/MacPass/MPAutotypeEnvironment.m @@ -0,0 +1,84 @@ +// +// MPAutotypeEnvironment.m +// MacPass +// +// Created by Michael Starke on 15.01.20. +// Copyright © 2020 HicknHack Software GmbH. All rights reserved. +// + +#import "MPAutotypeEnvironment.h" +#import "NSRunningApplication+MPAdditions.h" +#import "MPPluginHost.h" +#import "MPPlugin.h" + +@implementation MPAutotypeEnvironment + ++ (instancetype)environmentWithTargetApplication:(NSRunningApplication *)targetApplication entry:(KPKEntry *)entry { + return [[MPAutotypeEnvironment alloc] initWithTargetApplication:targetApplication entry:entry]; +} + +- (instancetype)initWithTargetApplication:(NSRunningApplication *)targetApplication entry:(KPKEntry *)entry { + self = [super init]; + if(self) { + _entry = entry; + if(!targetApplication) { + _pid = -1; + _windowTitle = @""; + } + else { + NSDictionary *frontApplicationInfoDict = targetApplication.mp_infoDictionary; + + _pid = [frontApplicationInfoDict[MPProcessIdentifierKey] intValue]; + _windowTitle = frontApplicationInfoDict[MPWindowTitleKey]; + + /* if we have any resolvers, let them provide the window title */ + NSArray *resolvers = [MPPluginHost.sharedHost windowTitleResolverForRunningApplication:targetApplication]; + for(MPPlugin *resolver in resolvers) { + NSString *windowTitle = [resolver windowTitleForRunningApplication:targetApplication]; + if(windowTitle.length > 0) { + _windowTitle = windowTitle; + break; + } + } + } + _hidden = NSRunningApplication.currentApplication.isHidden; + } + return self; +} + +- (BOOL)isSelfTargeting { + return NSRunningApplication.currentApplication.processIdentifier != _pid; +} + +- (NSDictionary *)_infoDictionaryForApplication:(NSRunningApplication *)application { + NSArray *currentWindows = CFBridgingRelease(CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)); + NSArray *windowNumbers = [NSWindow windowNumbersWithOptions:NSWindowNumberListAllApplications]; + NSUInteger minZIndex = NSNotFound; + NSDictionary *infoDict = nil; + for(NSDictionary *windowDict in currentWindows) { + NSString *windowTitle = windowDict[(NSString *)kCGWindowName]; + if(windowTitle.length <= 0) { + continue; + } + NSNumber *processId = windowDict[(NSString *)kCGWindowOwnerPID]; + if(processId && [processId isEqualToNumber:@(application.processIdentifier)]) { + + NSNumber *number = (NSNumber *)windowDict[(NSString *)kCGWindowNumber]; + NSUInteger zIndex = [windowNumbers indexOfObject:number]; + if(zIndex < minZIndex) { + minZIndex = zIndex; + infoDict = @{ + MPWindowTitleKey: windowTitle, + MPProcessIdentifierKey : processId + }; + } + } + } + if(currentWindows.count > 0 && infoDict.count == 0) { + // show some information about not being able to determine any windows + NSLog(@"Unable to retrieve any window names. If you encounter this issue you might be running 10.15 and MacPass has no permission for screen recording."); + } + return infoDict; +} + +@end diff --git a/MacPass/NSRunningApplication+MPAdditions.h b/MacPass/NSRunningApplication+MPAdditions.h new file mode 100644 index 00000000..fadf3007 --- /dev/null +++ b/MacPass/NSRunningApplication+MPAdditions.h @@ -0,0 +1,25 @@ +// +// NSRunningApplication+MPAdditions.h +// MacPass +// +// Created by Michael Starke on 15.01.20. +// Copyright © 2020 HicknHack Software GmbH. All rights reserved. +// + +#import + + +#import + +NS_ASSUME_NONNULL_BEGIN + +APPKIT_EXTERN NSString *const MPWindowTitleKey; +APPKIT_EXTERN NSString *const MPProcessIdentifierKey; + +@interface NSRunningApplication (MPAdditions) + +@property (readonly, copy) NSDictionary *mp_infoDictionary; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MacPass/NSRunningApplication+MPAdditions.m b/MacPass/NSRunningApplication+MPAdditions.m new file mode 100644 index 00000000..40839a9a --- /dev/null +++ b/MacPass/NSRunningApplication+MPAdditions.m @@ -0,0 +1,50 @@ +// +// NSRunningApplication+MPAdditions.m +// MacPass +// +// Created by Michael Starke on 15.01.20. +// Copyright © 2020 HicknHack Software GmbH. All rights reserved. +// + +#import "NSRunningApplication+MPAdditions.h" + +#import + + +NSString *const MPWindowTitleKey = @"MPWindowTitleKey"; +NSString *const MPProcessIdentifierKey = @"MPProcessIdentifierKey"; + +@implementation NSRunningApplication (MPAdditions) + +- (NSDictionary *)mp_infoDictionary { + NSArray *currentWindows = CFBridgingRelease(CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)); + NSArray *windowNumbers = [NSWindow windowNumbersWithOptions:NSWindowNumberListAllApplications]; + NSUInteger minZIndex = NSNotFound; + NSDictionary *infoDict = nil; + for(NSDictionary *windowDict in currentWindows) { + NSString *windowTitle = windowDict[(NSString *)kCGWindowName]; + if(windowTitle.length <= 0) { + continue; + } + NSNumber *processId = windowDict[(NSString *)kCGWindowOwnerPID]; + if(processId && [processId isEqualToNumber:@(self.processIdentifier)]) { + + NSNumber *number = (NSNumber *)windowDict[(NSString *)kCGWindowNumber]; + NSUInteger zIndex = [windowNumbers indexOfObject:number]; + if(zIndex < minZIndex) { + minZIndex = zIndex; + infoDict = @{ + MPWindowTitleKey: windowTitle, + MPProcessIdentifierKey : processId + }; + } + } + } + if(currentWindows.count > 0 && infoDict.count == 0) { + // show some information about not being able to determine any windows + NSLog(@"Unable to retrieve any window names. If you encounter this issue you might be running 10.15 and MacPass has no permission for screen recording."); + } + return infoDict; +} + +@end From 77d96976ec525e8d1eb0aa0fdd0f7162d4516ede Mon Sep 17 00:00:00 2001 From: Michael Starke Date: Wed, 22 Jan 2020 15:11:28 +0100 Subject: [PATCH 2/2] Refactored Autotype to use environment for better encapsulation of parameters --- ...AutotypeCandidateSelectionViewController.h | 4 +- ...AutotypeCandidateSelectionViewController.m | 7 +- MacPass/MPAutotypeDaemon.h | 4 +- MacPass/MPAutotypeDaemon.m | 82 +++++++++---------- MacPass/MPAutotypeEnvironment.h | 14 ++-- MacPass/MPAutotypeEnvironment.m | 2 +- 6 files changed, 57 insertions(+), 56 deletions(-) diff --git a/MacPass/MPAutotypeCandidateSelectionViewController.h b/MacPass/MPAutotypeCandidateSelectionViewController.h index 863d7255..97259fdf 100644 --- a/MacPass/MPAutotypeCandidateSelectionViewController.h +++ b/MacPass/MPAutotypeCandidateSelectionViewController.h @@ -25,12 +25,12 @@ NS_ASSUME_NONNULL_BEGIN @class MPAutotypeEnvironment; +@class MPAutotypeContext; @interface MPAutotypeCandidateSelectionViewController : NSViewController @property (strong) MPAutotypeEnvironment *environment; -@property (copy) NSArray *candidates; -@property (copy) NSString *windowTitle; +@property (copy) NSArray *candidates; - (IBAction)selectAutotypeContext:(id)sender; - (IBAction)cancelSelection:(id)sender; diff --git a/MacPass/MPAutotypeCandidateSelectionViewController.m b/MacPass/MPAutotypeCandidateSelectionViewController.m index 8a6925c4..eb91cb9b 100644 --- a/MacPass/MPAutotypeCandidateSelectionViewController.m +++ b/MacPass/MPAutotypeCandidateSelectionViewController.m @@ -22,6 +22,7 @@ #import "MPAutotypeCandidateSelectionViewController.h" #import "MPAutotypeContext.h" #import "MPAutotypeDaemon.h" +#import "MPAutotypeEnvironment.h" #import "KPKNode+IconImage.h" @@ -43,7 +44,7 @@ - (void)viewDidLoad { [super viewDidLoad]; NSString *template = NSLocalizedString(@"AUTOTYPE_CANDIDATE_SELECTION_WINDOW_MESSAGE_%@", "Message text in the autotype selection window. Placeholder is %1 - windowTitle"); - self.messageTextField.stringValue = [NSString stringWithFormat:template, self.windowTitle]; + self.messageTextField.stringValue = [NSString stringWithFormat:template, self.environment.windowTitle]; self.selectAutotypeContextButton.enabled = NO; NSNotification *notification = [NSNotification notificationWithName:NSTableViewSelectionDidChangeNotification object:self.contextTableView]; [self tableViewSelectionDidChange:notification]; @@ -80,7 +81,7 @@ - (void)selectAutotypeContext:(id)sender { NSInteger selectedRow = self.contextTableView.selectedRow; if(selectedRow >= 0 && selectedRow < self.candidates.count) { - [MPAutotypeDaemon.defaultDaemon selectAutotypeCandiate:self.candidates[selectedRow]]; + [MPAutotypeDaemon.defaultDaemon selectAutotypeContext:self.candidates[selectedRow] forEnvironment:self.environment]; } else { [self cancelSelection:sender]; // cancel since the selection was invalid! @@ -88,7 +89,7 @@ } - (void)cancelSelection:(id)sender { - [MPAutotypeDaemon.defaultDaemon cancelAutotypeCandidateSelection]; + [MPAutotypeDaemon.defaultDaemon cancelAutotypeContextSelectionForEnvironment:self.environment]; } diff --git a/MacPass/MPAutotypeDaemon.h b/MacPass/MPAutotypeDaemon.h index 556ce554..14d532f3 100644 --- a/MacPass/MPAutotypeDaemon.h +++ b/MacPass/MPAutotypeDaemon.h @@ -40,7 +40,7 @@ - (void)performAutotypeForEntry:(KPKEntry *)entry; - (void)performAutotypeForEntry:(KPKEntry *)entry overrideSequence:(NSString *)sequence; -- (void)selectAutotypeCandiate:(MPAutotypeContext *)context forEnvironment:(MPAutotypeEnvironment *)environment; -- (void)cancelAutotypeCandidateSelectionForEnvironment:(MPAutotypeEnvironment *)environment; +- (void)selectAutotypeContext:(MPAutotypeContext *)context forEnvironment:(MPAutotypeEnvironment *)environment; +- (void)cancelAutotypeContextSelectionForEnvironment:(MPAutotypeEnvironment *)environment; @end diff --git a/MacPass/MPAutotypeDaemon.m b/MacPass/MPAutotypeDaemon.m index 77599e62..1d577915 100644 --- a/MacPass/MPAutotypeDaemon.m +++ b/MacPass/MPAutotypeDaemon.m @@ -154,23 +154,25 @@ static MPAutotypeDaemon *_sharedInstance; } #pragma mark - -#pragma mark Actions -- (void)selectAutotypeCandiate:(MPAutotypeContext *)context forEnvironment:(MPAutotypeEnvironment *)environment { +#pragma mark Autotype Execution +- (void)selectAutotypeContext:(MPAutotypeContext *)context forEnvironment:(MPAutotypeEnvironment *)environment { [self.matchSelectionWindow orderOut:self]; self.matchSelectionWindow = nil; - [self _runAutotypeWithEnvirnment:environment forContext:context]; -} - -- (void)cancelAutotypeCandidateSelectionForEnvironment:(MPAutotypeEnvironment *)environment { - [self.matchSelectionWindow orderOut:self]; - self.matchSelectionWindow = nil; - if(self.targetPID) { - [self _orderApplicationToFront:self.targetPID forContext:nil]; + [self _runAutotypeWithEnvironment:environment forContext:context]; + if(environment.hidden) { + [NSApplication.sharedApplication hide:nil]; + } +} + +- (void)cancelAutotypeContextSelectionForEnvironment:(MPAutotypeEnvironment *)environment { + [self.matchSelectionWindow orderOut:self]; + self.matchSelectionWindow = nil; + [NSApplication.sharedApplication hide:nil]; + if(environment.pid) { + [self _orderApplicationToFront:environment.pid completionHandler:nil]; } } -#pragma mark - -#pragma mark Autotype Execution - (void)_runAutotypeAfterDatabaseUnlockWithEnvironment:(MPAutotypeEnvironment *)environment requestedAt:(NSTimeInterval)requestTime { NSTimeInterval now = NSDate.date.timeIntervalSinceReferenceDate; if(now - requestTime > 30) { @@ -249,12 +251,12 @@ static MPAutotypeDaemon *_sharedInstance; return; // wait for the unlock to happen } - MPAutotypeContext *context = [self _autotypeContextForDocuments:documents forWindowTitle:env.windowTitle preferredEntry:env.entry]; + MPAutotypeContext *context = [self _autotypeContextForDocuments:documents withEnvironment:env]; /* TODO: that's popping up if the multi selection dialog goes up! */ if(self.matchSelectionWindow) { return; // we present the match selection window, just return } - if(!env.entry) { + if(!env.preferredEntry) { NSUserNotification *notification = [[NSUserNotification alloc] init]; notification.title = NSApp.applicationName; notification.userInfo = @{ MPUserNotificationTypeKey: MPUserNotificationTypeAutotypeFeedback }; @@ -266,17 +268,17 @@ static MPAutotypeDaemon *_sharedInstance; } [NSUserNotificationCenter.defaultUserNotificationCenter deliverNotification:notification]; } - [self _performAutotypeForContext:context]; + [self _runAutotypeWithEnvironment:env forContext:context]; } -- (MPAutotypeContext *)_autotypeContextForDocuments:(NSArray *)documents forWindowTitle:(NSString *)windowTitle preferredEntry:(KPKEntry *)entry { +- (MPAutotypeContext *)_autotypeContextForDocuments:(NSArray *)documents withEnvironment:(MPAutotypeEnvironment *)environment { /* Query the document to generate a autotype command list for the window title We do not care where this came form, just get the autotype commands */ NSMutableArray *autotypeCandidates = [[NSMutableArray alloc] init]; for(MPDocument *document in documents) { - NSArray *contexts = [document autotypContextsForWindowTitle:windowTitle preferredEntry:entry]; + NSArray *contexts = [document autotypContextsForWindowTitle:environment.windowTitle preferredEntry:environment.preferredEntry]; if(contexts ) { [autotypeCandidates addObjectsFromArray:contexts]; } @@ -285,20 +287,22 @@ static MPAutotypeDaemon *_sharedInstance; if(autotypeCandidates.count <= 1) { return autotypeCandidates.lastObject; } - - [self _presentCandiadates:autotypeCandidates forWindowTitle:windowTitle]; + [self _presentCandiadates:autotypeCandidates forEnvironment:environment]; return nil; // Nothing to do, we get called back by the window } -- (void)_runAutotypeWithEnvirnment:(MPAutotypeEnvironment *)environment forContext:(MPAutotypeContext *)context { +- (void)_runAutotypeWithEnvironment:(MPAutotypeEnvironment *)environment forContext:(MPAutotypeContext *)context { if(nil == environment) { return; // no Environment to work in } if(nil == context) { return; // No context to work with } - - if(NO == [self _orderApplicationToFront:self.targetPID forContext:(MPAutotypeContext *)context]) { + __weak MPAutotypeDaemon *welf = self; + BOOL appIsFrontmost = [self _orderApplicationToFront:environment.pid completionHandler:^{ + [welf _runAutotypeWithEnvironment:environment forContext:context]; + }]; + if(!appIsFrontmost) { return; // We will get called back when the application is in front - hopfully } @@ -346,7 +350,7 @@ static MPAutotypeDaemon *_sharedInstance; } } -- (void)_presentCandiadates:(NSArray *)candidates forWindowTitle:(NSString *)windowTitle { +- (void)_presentCandiadates:(NSArray *)candidates forEnvironment:(MPAutotypeEnvironment *)environment { if(!self.matchSelectionWindow) { self.matchSelectionWindow = [[NSPanel alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100) styleMask:NSWindowStyleMaskNonactivatingPanel|NSWindowStyleMaskTitled @@ -355,12 +359,7 @@ static MPAutotypeDaemon *_sharedInstance; self.matchSelectionWindow.level = kCGAssistiveTechHighWindowLevel; MPAutotypeCandidateSelectionViewController *vc = [[MPAutotypeCandidateSelectionViewController alloc] init]; vc.candidates = candidates; - vc.windowTitle = windowTitle; - if(NSRunningApplication.currentApplication.isHidden) { - vc.completionHandler = ^{ - [NSRunningApplication.currentApplication hide]; - }; - } + vc.environment = environment; self.matchSelectionWindow.collectionBehavior |= (NSWindowCollectionBehaviorFullScreenAuxiliary | NSWindowCollectionBehaviorMoveToActiveSpace | NSWindowCollectionBehaviorTransient ); @@ -380,27 +379,24 @@ static MPAutotypeDaemon *_sharedInstance; #pragma mark - #pragma mark Application information -- (BOOL)_orderApplicationToFront:(pid_t)processIdentifier inEnvironment:(MPAutotypeEnvironment *) environment { -- (BOOL)_orderApplicationToFront:(pid_t)processIdentifier forContext:(MPAutotypeContext *)context { +//- (BOOL)_orderApplicationToFront:(pid_t)processIdentifier inEnvironment:(MPAutotypeEnvironment *) environment { +- (BOOL)_orderApplicationToFront:(pid_t)processIdentifier completionHandler:(void (^_Nullable)(void))completionHandler { NSRunningApplication *runingApplication = [NSRunningApplication runningApplicationWithProcessIdentifier:processIdentifier]; NSRunningApplication *frontApplication = NSWorkspace.sharedWorkspace.frontmostApplication; if(frontApplication.processIdentifier == processIdentifier) { return YES; } - - /* cleanup before to make sure everything is top notch */ - if(self.applicationActivationObserver) { - [NSWorkspace.sharedWorkspace.notificationCenter removeObserver:self.applicationActivationObserver name:NSWorkspaceDidActivateApplicationNotification object:nil]; - self.applicationActivationObserver = nil; - } - - self.applicationActivationObserver = [NSWorkspace.sharedWorkspace.notificationCenter addObserverForName:NSWorkspaceDidActivateApplicationNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { - if(self.applicationActivationObserver) { - [NSWorkspace.sharedWorkspace.notificationCenter removeObserver:self.applicationActivationObserver name:NSWorkspaceDidActivateApplicationNotification object:nil]; + + NSNotificationCenter * __weak nc = NSWorkspace.sharedWorkspace.notificationCenter; + id __block didActivateToken = [nc addObserverForName:NSWorkspaceDidActivateApplicationNotification + object:nil + queue:NSOperationQueue.mainQueue + usingBlock:^(NSNotification *notification) { + [nc removeObserver:didActivateToken]; + if(completionHandler) { + completionHandler(); } - [self _performAutotypeForContext:context]; }]; - [runingApplication activateWithOptions:NSApplicationActivateIgnoringOtherApps]; return NO; } diff --git a/MacPass/MPAutotypeEnvironment.h b/MacPass/MPAutotypeEnvironment.h index 0ce08abd..8c756475 100644 --- a/MacPass/MPAutotypeEnvironment.h +++ b/MacPass/MPAutotypeEnvironment.h @@ -15,11 +15,15 @@ NS_ASSUME_NONNULL_BEGIN @interface MPAutotypeEnvironment : NSObject -@property (readonly, weak) KPKEntry *entry; -@property (readonly) pid_t pid; -@property (readonly, copy) NSString *windowTitle; -@property (readonly) BOOL hidden; -@property (readonly) BOOL isSelfTargeting; +/** + The selected entry, if Autotype is run only for a single entry. + If autotype should search for entries, set this to nil. + */ +@property (readonly, weak, nullable) KPKEntry *preferredEntry; +@property (readonly) pid_t pid; // the PID of the target application to which the key strokes should be sent +@property (readonly, copy) NSString *windowTitle; /// The window title of the target application. +@property (readonly) BOOL hidden; /// If set to YES, MacPass was hidden when autotype was initiated +@property (readonly) BOOL isSelfTargeting; /// If MacPass should autotype to itself, YES, otherwise NO + (instancetype)environmentWithTargetApplication:(NSRunningApplication *)targetApplication entry:(KPKEntry * _Nullable)entry; - (instancetype)initWithTargetApplication:(NSRunningApplication *)targetApplication entry:(KPKEntry * _Nullable)entry NS_DESIGNATED_INITIALIZER; diff --git a/MacPass/MPAutotypeEnvironment.m b/MacPass/MPAutotypeEnvironment.m index 80a483a8..302f6fb1 100644 --- a/MacPass/MPAutotypeEnvironment.m +++ b/MacPass/MPAutotypeEnvironment.m @@ -20,7 +20,7 @@ - (instancetype)initWithTargetApplication:(NSRunningApplication *)targetApplication entry:(KPKEntry *)entry { self = [super init]; if(self) { - _entry = entry; + _preferredEntry = entry; if(!targetApplication) { _pid = -1; _windowTitle = @"";