Files
MacPass/MacPass/MPAutotypeDaemon.m
michael starke 0160b9055a Cleaned up logging
Added bagde icon to show timeout for clipboard clearing
Badge display not optimal if clipboard clearing is disabled
Autotype incovation on locked documents now activates MacPass to unlock.
If multipel entries match, the seleciton dialog will pop up as well
2014-03-27 22:06:00 +01:00

255 lines
8.3 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";
@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:@"enabled"
toObject:[NSUserDefaultsController sharedUserDefaultsController]
withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyEnableGlobalAutotype]
options:nil];
}
return self;
}
- (void)dealloc {
[self unbind:@"enabled"];
}
#pragma mark -
#pragma mark Properties
- (void)setEnabled:(BOOL)enabled {
if(_enabled != enabled) {
_enabled = enabled;
self.enabled ? [self _registerHotKey] : [self _unregisterHotKey];
}
}
#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