mirror of
https://github.com/MacPass/MacPass.git
synced 2025-12-14 17:32:17 +00:00
The entry title will first be searched for within the window title. If the window title does not contain the entry title, then the window title will be searched for within the entry title.
262 lines
8.4 KiB
Objective-C
262 lines
8.4 KiB
Objective-C
//
|
|
// MPAutotypeDaemon.m
|
|
// MacPass
|
|
//
|
|
// Created by Michael Starke on 26.10.13.
|
|
// Copyright (c) 2013 HicknHack Software GmbH. All rights reserved.
|
|
//
|
|
|
|
#import "MPAutotypeDaemon.h"
|
|
#import "MPDocument.h"
|
|
|
|
#import "MPDocument+Autotype.h"
|
|
#import "MPAutotypeCommand.h"
|
|
#import "MPAutotypeContext.h"
|
|
#import "MPAutotypePaste.h"
|
|
|
|
#import "MPPasteBoardController.h"
|
|
#import "MPSettingsHelper.h"
|
|
|
|
|
|
#import "KPKEntry.h"
|
|
|
|
#import "DDHotKeyCenter.h"
|
|
#import <Carbon/Carbon.h>
|
|
|
|
NSString *const kMPWindowTitleKey = @"windowTitle";
|
|
NSString *const kMPApplciationNameKey = @"applicationName";
|
|
|
|
/*
|
|
Enable to activate autotype
|
|
#define MP_AUTOTYPE
|
|
*/
|
|
|
|
@interface MPAutotypeDaemon ()
|
|
|
|
@property (nonatomic, assign) BOOL enabled;
|
|
@property (copy) NSString *targetApplicationName;
|
|
@property (copy) NSString *targetWindowTitle;
|
|
|
|
@end
|
|
|
|
@implementation MPAutotypeDaemon
|
|
|
|
#pragma mark -
|
|
#pragma mark Lifecylce
|
|
|
|
- (id)init {
|
|
self = [super init];
|
|
if (self) {
|
|
_enabled = NO;
|
|
[self bind:NSStringFromSelector(@selector(enabled))
|
|
toObject:[NSUserDefaultsController sharedUserDefaultsController]
|
|
withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyEnableGlobalAutotype]
|
|
options:nil];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[self unbind:NSStringFromSelector(@selector(enabled))];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Properties
|
|
|
|
- (void)setEnabled:(BOOL)enabled {
|
|
if(_enabled != enabled) {
|
|
_enabled = enabled;
|
|
#ifdef MP_AUTOTYPE
|
|
self.enabled ? [self _registerHotKey] : [self _unregisterHotKey];
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Actions
|
|
|
|
- (void)executeAutotypeWithSelectedMatch:(id)sender {
|
|
NSMenuItem *item = [self.matchSelectionButton selectedItem];
|
|
MPAutotypeContext *context = [item representedObject];
|
|
[self.matchSelectionWindow orderOut:self];
|
|
[self _performAutotypeForContext:context];
|
|
}
|
|
|
|
- (void)cancelAutotypeSelection:(id)sender {
|
|
[self.matchSelectionWindow orderOut:sender];
|
|
if(self.targetApplicationName) {
|
|
[MPAutotypeDaemon _orderApplicationToFront:self.targetApplicationName];
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Hotkey evaluation
|
|
|
|
- (void)_didPressHotKey {
|
|
[self _performAutotypeUsingCurrentWindowAndApplication:YES];
|
|
}
|
|
|
|
- (void)_performAutotypeUsingCurrentWindowAndApplication:(BOOL)useCurrentWindowAndApplication {
|
|
if(useCurrentWindowAndApplication) {
|
|
[self _updateTargetApplicationAndWindow];
|
|
}
|
|
|
|
MPDocument *document = [self _findAutotypeDocument];
|
|
if(!document) {
|
|
return; // nothing to do
|
|
}
|
|
|
|
MPAutotypeContext *context = [self _autotypeContextInDocument:document forWindowTitle:self.targetWindowTitle];
|
|
[self _performAutotypeForContext:context];
|
|
}
|
|
|
|
- (MPDocument *)_findAutotypeDocument {
|
|
NSArray *documents = [NSApp orderedDocuments];
|
|
MPDocument *currentDocument = nil;
|
|
for(MPDocument *openDocument in documents) {
|
|
if(NO == openDocument.encrypted) {
|
|
currentDocument = openDocument;
|
|
break;
|
|
}
|
|
}
|
|
BOOL hasOpenDocuments = [documents count] > 0;
|
|
if(!currentDocument && hasOpenDocuments) {
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
[[NSApp mainWindow] makeKeyAndOrderFront:self];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didUnlockDatabase:) name:MPDocumentDidUnlockDatabaseNotification object:nil];
|
|
}
|
|
return currentDocument;
|
|
}
|
|
|
|
- (MPAutotypeContext *)_autotypeContextInDocument:(MPDocument *)document forWindowTitle:(NSString *)windowTitle {
|
|
/*
|
|
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
|
|
*/
|
|
NSArray *autotypeCandidates = [document autotypContextsForWindowTitle:windowTitle];
|
|
NSUInteger candidates = [autotypeCandidates count];
|
|
if(candidates == 0) {
|
|
return nil;
|
|
}
|
|
if(candidates == 1 ) {
|
|
return [autotypeCandidates lastObject];
|
|
}
|
|
[self _presentSelectionWindow:autotypeCandidates];
|
|
return nil; // Nothing to do, we get called back by the window
|
|
}
|
|
|
|
- (void)_performAutotypeForContext:(MPAutotypeContext *)context {
|
|
if(nil == context) {
|
|
return; // No context to work with
|
|
}
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
NSArray *commands = [MPAutotypeCommand commandsForContext:context];
|
|
[MPAutotypeDaemon _orderApplicationToFront:self.targetApplicationName];
|
|
BOOL lastCommandWasPaste = NO;
|
|
for(MPAutotypeCommand *command in commands) {
|
|
if(lastCommandWasPaste) {
|
|
usleep(1000*1000);
|
|
}
|
|
[command execute];
|
|
lastCommandWasPaste = [command isKindOfClass:[MPAutotypePaste class]];
|
|
}
|
|
});
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Hotkey Registration
|
|
|
|
- (void)_registerHotKey {
|
|
[[DDHotKeyCenter sharedHotKeyCenter] registerHotKeyWithKeyCode:kVK_ANSI_M
|
|
modifierFlags:(NSCommandKeyMask | NSAlternateKeyMask )
|
|
target:self
|
|
action:@selector(_didPressHotKey)
|
|
object:nil];
|
|
}
|
|
|
|
- (void)_unregisterHotKey {
|
|
[[DDHotKeyCenter sharedHotKeyCenter] unregisterHotKeysWithTarget:self action:@selector(_didPressHotKey)];
|
|
}
|
|
|
|
- (NSDictionary *)_frontMostApplicationInfoDict {
|
|
NSRunningApplication *frontApplication = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
|
NSString *name = frontApplication.localizedName;
|
|
|
|
NSArray *currentWindows = CFBridgingRelease(CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID));
|
|
for(NSDictionary *windowDict in currentWindows) {
|
|
NSString *windowTitle = windowDict[(NSString *)kCGWindowName];
|
|
if([windowTitle length] <= 0) {
|
|
continue;
|
|
}
|
|
NSNumber *processId = windowDict[(NSString *)kCGWindowOwnerPID];
|
|
if(processId && [processId isEqualToNumber:@(frontApplication.processIdentifier)]) {
|
|
return @{
|
|
kMPWindowTitleKey: windowDict[(NSString *)kCGWindowName],
|
|
kMPApplciationNameKey : name
|
|
};
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void)_presentSelectionWindow:(NSArray *)candidates {
|
|
if(!self.matchSelectionWindow) {
|
|
[[NSBundle mainBundle] loadNibNamed:@"AutotypeCandidateSelectionWindow" owner:self topLevelObjects:nil];
|
|
[self.matchSelectionWindow setLevel:NSFloatingWindowLevel];
|
|
}
|
|
NSMenu *associationMenu = [[NSMenu alloc] init];
|
|
[associationMenu addItemWithTitle:NSLocalizedString(@"SELECT_AUTOTYPE_CANDIDATE", "") action:NULL keyEquivalent:@""];
|
|
[associationMenu addItem:[NSMenuItem separatorItem]];
|
|
[associationMenu setAutoenablesItems:NO];
|
|
for(MPAutotypeContext *context in candidates) {
|
|
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:context.entry.title action:0 keyEquivalent:@""];
|
|
[item setRepresentedObject:context];
|
|
[associationMenu addItem:item];
|
|
NSArray *attributes = @[ context.entry.username, context.command ];
|
|
for(NSString *value in attributes) {
|
|
NSMenuItem *valueItem = [[NSMenuItem alloc] initWithTitle:value action:NULL keyEquivalent:@""];
|
|
[valueItem setIndentationLevel:1];
|
|
[valueItem setEnabled:NO];
|
|
[associationMenu addItem:valueItem];
|
|
}
|
|
}
|
|
[self.matchSelectionButton setMenu:associationMenu];
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
[self.matchSelectionWindow makeKeyAndOrderFront:self];
|
|
/* Setup Items in Popup */
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark MPDocument Notifications
|
|
|
|
- (void)_didUnlockDatabase:(NSNotification *)notification {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
[self _performAutotypeUsingCurrentWindowAndApplication:NO];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Application information
|
|
|
|
+ (void)_orderApplicationToFront:(NSString *)applicationName {
|
|
//NSLog(@"Moving %@ to the front.", applicationName);
|
|
NSString *appleScript = [[NSString alloc] initWithFormat:@"activate application \"%@\"", applicationName];
|
|
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:appleScript];
|
|
NSDictionary *error;
|
|
NSAppleEventDescriptor *descriptor = [script executeAndReturnError:&error];
|
|
if(!descriptor) {
|
|
NSLog(@"Error trying to execure %@: %@", script, error);
|
|
}
|
|
}
|
|
|
|
- (void)_updateTargetApplicationAndWindow {
|
|
/*
|
|
Determine the window title of the current front most application
|
|
Start searching the db for the best fit (based on title, then on window associations
|
|
*/
|
|
NSDictionary *frontApplicationInfoDict = [self _frontMostApplicationInfoDict];
|
|
self.targetApplicationName = frontApplicationInfoDict[kMPApplciationNameKey];
|
|
self.targetWindowTitle = frontApplicationInfoDict[kMPWindowTitleKey];
|
|
}
|
|
|
|
@end
|