diff --git a/MacPass.xcodeproj/project.pbxproj b/MacPass.xcodeproj/project.pbxproj index 4d4272b8..312a0694 100644 --- a/MacPass.xcodeproj/project.pbxproj +++ b/MacPass.xcodeproj/project.pbxproj @@ -319,6 +319,7 @@ 6021FE9818E1650F00C3BC51 /* DatabaseSettingsWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6021FE9A18E1650F00C3BC51 /* DatabaseSettingsWindow.xib */; }; 7837112C225540D1009BD28D /* PluginRepositoryBrowserView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7837112E225540D1009BD28D /* PluginRepositoryBrowserView.xib */; }; 78E1F8B022E3A5D600E738AE /* AutotypeDoctorReportViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 78E1F8B222E3A5D600E738AE /* AutotypeDoctorReportViewController.xib */; }; + 7E5D230927889AE400C0ECCF /* MPHotkey.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E5D230827889AE400C0ECCF /* MPHotkey.m */; }; FA13910C1F9CD9EB0033D256 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = FA13910A1F9CD9EB0033D256 /* Localizable.stringsdict */; }; FA9FD3271FB5E8F4003CEDD6 /* AutotypeCandidateSelectionView.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA9FD3291FB5E8F4003CEDD6 /* AutotypeCandidateSelectionView.xib */; }; FA9FD32C1FB5EDD3003CEDD6 /* AutotypeBuilderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA9FD32E1FB5EDD3003CEDD6 /* AutotypeBuilderView.xib */; }; @@ -1095,6 +1096,8 @@ 78E1F8BE22E3B1BF00E738AE /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/AutotypeCandidateSelectionView.strings; sourceTree = ""; }; 78E1F8C022E3B22500E738AE /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/PluginDataView.strings; sourceTree = ""; }; 78E1F8C122E3B32D00E738AE /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = ""; }; + 7E5D230727889ACE00C0ECCF /* MPHotkey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPHotkey.h; sourceTree = ""; }; + 7E5D230827889AE400C0ECCF /* MPHotkey.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPHotkey.m; sourceTree = ""; }; A019D80F22DC6B3C0085FD54 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.lproj/Localizable.stringsdict; sourceTree = ""; }; A083E27922DF467B0020E0D5 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/PickcharsView.strings; sourceTree = ""; }; A083E27A22DF467B0020E0D5 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/PluginDataView.strings; sourceTree = ""; }; @@ -1337,6 +1340,8 @@ 4C2E382016D141F700037A9D /* Helper */ = { isa = PBXGroup; children = ( + 7E5D230727889ACE00C0ECCF /* MPHotkey.h */, + 7E5D230827889AE400C0ECCF /* MPHotkey.m */, 4C888C9516EB754B003D34A1 /* MPActionHelper.h */, 4C888C9616EB754B003D34A1 /* MPActionHelper.m */, 4C01C2401764D8980016D5D0 /* MPContextMenuHelper.h */, @@ -2276,6 +2281,7 @@ 4C3BD51516D276F800389F1F /* MPToolbarDelegate.m in Sources */, 4C8F0C711FCEF91400BE157F /* MPPickcharsParser.m in Sources */, 4C735FC02035FCBF00708D53 /* MPPluginEntryActionContext.m in Sources */, + 7E5D230927889AE400C0ECCF /* MPHotkey.m in Sources */, 4C61EA0316D2FD0800AC519E /* MPOutlineViewController.m in Sources */, 4C65C79C16DD283900E32CFF /* MPToolbarButton.m in Sources */, 4C49CFD624252389004092E7 /* KPKEntry+MPTags.m in Sources */, @@ -2724,7 +2730,6 @@ 4C9328D1273E6A83000DCBEE /* Base */, 4C9328D4273E6A85000DCBEE /* zh-Hans */, 4C9328D6273E6A85000DCBEE /* de */, - 69385E36274ACFDF001AB1E9 /* cs */, ); name = MPTOTPViewController.xib; sourceTree = ""; @@ -2753,7 +2758,6 @@ isa = PBXVariantGroup; children = ( 4CF14962224B623700D1CE1C /* Base */, - 69385E4F274ACFE0001AB1E9 /* cs */, ); name = Credits.rtf; sourceTree = ""; diff --git a/MacPass/Base.lproj/IntegrationPreferences.xib b/MacPass/Base.lproj/IntegrationPreferences.xib index 8705ad7d..a81bfa02 100644 --- a/MacPass/Base.lproj/IntegrationPreferences.xib +++ b/MacPass/Base.lproj/IntegrationPreferences.xib @@ -1,8 +1,8 @@ - + - + @@ -27,19 +27,19 @@ - + - + - + - + - + Autotype might not work properly. Some issues where found that prevent Autotype or Global Autotype to work. Please run the Autotype Doctor to fix those issues. @@ -48,7 +48,7 @@ - + @@ -107,14 +107,14 @@ - + If enabled, a dialog will show up before Autotype is executed even if only a single match was found to prevent accidental input and wrong matches @@ -123,42 +123,42 @@ - + @@ -205,20 +205,20 @@ - + - + - + diff --git a/MacPass/Base.lproj/WorkflowPreferences.xib b/MacPass/Base.lproj/WorkflowPreferences.xib index 45b17bb9..9de71fc2 100644 --- a/MacPass/Base.lproj/WorkflowPreferences.xib +++ b/MacPass/Base.lproj/WorkflowPreferences.xib @@ -14,6 +14,9 @@ + + + @@ -21,10 +24,10 @@ - + - + @@ -92,7 +95,7 @@ - + @@ -124,7 +127,7 @@ - + @@ -146,7 +149,7 @@ - + @@ -168,7 +171,7 @@ - + @@ -206,15 +209,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + @@ -222,10 +273,13 @@ + + + - + diff --git a/MacPass/DDHotKey+MacPassAdditions.m b/MacPass/DDHotKey+MacPassAdditions.m index ab9c7f3f..1f4f7a82 100644 --- a/MacPass/DDHotKey+MacPassAdditions.m +++ b/MacPass/DDHotKey+MacPassAdditions.m @@ -37,8 +37,10 @@ + (NSData *)hotKeyDataWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInte + (NSData *)defaultHotKeyData { return [self hotKeyDataWithKeyCode:kVK_ANSI_M modifierFlags:kCGEventFlagMaskControl|kCGEventFlagMaskAlternate]; + } + + (instancetype)defaultHotKey { return [DDHotKey defaultHotKeyWithTask:nil]; } diff --git a/MacPass/MPAppDelegate.m b/MacPass/MPAppDelegate.m index 84d1e5a4..b48f4a33 100644 --- a/MacPass/MPAppDelegate.m +++ b/MacPass/MPAppDelegate.m @@ -43,6 +43,7 @@ #import "MPPlugin.h" #import "MPEntryContextMenuDelegate.h" #import "MPAutotypeDoctor.h" +#import "MPHotkey.h" #import "NSApplication+MPAdditions.h" #import "NSTextView+MPTouchBarExtension.h" @@ -214,6 +215,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification { /* Initalizes Global Daemons */ [MPLockDaemon defaultDaemon]; [MPAutotypeDaemon defaultDaemon]; + [MPHotkeyDaemon hotkeyDaemon]; [MPPluginHost sharedHost]; #if !defined(DEBUG) && !defined(NO_SPARKLE) /* Disable updates if in debug or nosparkle */ diff --git a/MacPass/MPHotkey.h b/MacPass/MPHotkey.h new file mode 100644 index 00000000..c290b38f --- /dev/null +++ b/MacPass/MPHotkey.h @@ -0,0 +1,36 @@ +// +// MPHotkey.h +// MacPass +// +// Created by George Snow on 7.1.2022. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#import + +@class DDHotKey; +/** + * The hotkey is responsible for registering the global hotkey and to to show/hide MacPass + */ +@interface MPHotkeyDaemon : NSObject + +@property (strong) IBOutlet NSWindow *matchSelectionWindow; +@property (weak) IBOutlet NSPopUpButton *matchSelectionButton; +@property (readonly, strong) DDHotKey *registredHotKey; +@property (readonly, strong, class) MPHotkeyDaemon *hotkeyDaemon; + + +@end diff --git a/MacPass/MPHotkey.m b/MacPass/MPHotkey.m new file mode 100644 index 00000000..6259d853 --- /dev/null +++ b/MacPass/MPHotkey.m @@ -0,0 +1,146 @@ +// +// MPHotkey.m +// MacPass +// +// Created by George Snow on 7.1.2022. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#import "MPHotkey.h" +#import "MPSettingsHelper.h" + +#import "MPPluginHost.h" +#import "MPPlugin.h" + +#import "NSApplication+MPAdditions.h" +#import "NSUserNotification+MPAdditions.h" + +#import "DDHotKeyCenter.h" +#import "DDHotKey+MacPassAdditions.h" + +#import "KeePassKit/KeePassKit.h" +#import + +@interface MPHotkeyDaemon () + +@property (nonatomic, assign) BOOL enabled; +@property (nonatomic, copy) NSData *hotKeyData; +@property (strong) DDHotKey *registredHotKey; + +@end + +@implementation MPHotkeyDaemon + +#pragma mark - +#pragma mark Lifecylce + +static MPHotkeyDaemon *_sharedInstance; + ++ (instancetype)hotkeyDaemon { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _sharedInstance = [[MPHotkeyDaemon alloc] _init]; + }); + return _sharedInstance; +} + +- (instancetype)init { + return nil; +} + +- (instancetype)_init { + NSAssert(_sharedInstance == nil, @"Multiple initializations not allowed on singleton"); + self = [super init]; + if (self) { + _enabled = NO; + //_userActionRequested = NSDate.distantPast.timeIntervalSinceReferenceDate; + [self bind:NSStringFromSelector(@selector(enabled)) + toObject:NSUserDefaultsController.sharedUserDefaultsController + withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyShowOrHideMacPass] + options:nil]; + + [self bind:NSStringFromSelector(@selector(hotKeyData)) + toObject:NSUserDefaultsController.sharedUserDefaultsController + withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyShowHideKeyDataKey] + options:nil]; + + } + return self; +} + +- (void)dealloc { + + [self unbind:NSStringFromSelector(@selector(enabled))]; + [self unbind:NSStringFromSelector(@selector(hotKeyData))]; +} + +#pragma mark - +#pragma mark Properties + +- (void)setEnabled:(BOOL)enabled { + if(_enabled != enabled) { + _enabled = enabled; + self.enabled ? [self _registerHotKey] : [self _unregisterHotKey]; + } +} + +- (void)setHotKeyData:(NSData *)hotKeyData { + if(![_hotKeyData isEqualToData:hotKeyData]) { + [self _unregisterHotKey]; + _hotKeyData = [hotKeyData copy]; + if(self.enabled) { + [self _registerHotKey]; + } + } +} + +#pragma mark - +#pragma mark Hotkey Registration +- (void)_registerHotKey { + if(!self.hotKeyData) { + return; + } + __weak MPHotkeyDaemon *welf = self; + DDHotKeyTask aTask = ^(NSEvent *event) { + [welf _didPressHotKey]; + }; + self.registredHotKey = [[DDHotKeyCenter sharedHotKeyCenter] registerHotKey:[DDHotKey hotKeyWithKeyData:self.hotKeyData task:aTask]]; +} + +- (void)_unregisterHotKey { + if(self.registredHotKey) { + [[DDHotKeyCenter sharedHotKeyCenter] unregisterHotKey:self.registredHotKey]; + self.registredHotKey = nil; + } +} + +#pragma mark - +#pragma mark Show/Hide Invocation + + +- (void)_didPressHotKey { + + bool mpActive = [NSApp isActive]; + if(mpActive) { + [NSApplication.sharedApplication hide:nil]; + } + else { + [NSApplication.sharedApplication activateIgnoringOtherApps:YES]; + + } + +} +@end diff --git a/MacPass/MPSettingsHelper.h b/MacPass/MPSettingsHelper.h index f97aba44..28cd7b7a 100644 --- a/MacPass/MPSettingsHelper.h +++ b/MacPass/MPSettingsHelper.h @@ -35,6 +35,9 @@ APPKIT_EXTERN NSString *const kMPSettingsKeyQuitOnLastWindowClose; // Q APPKIT_EXTERN NSString *const kMPSettingsKeyFileChangeStrategy; APPKIT_EXTERN NSString *const kMPSettingsKeyEnableAutosave; // if set to YES MacPass support Autosaving for documents APPKIT_EXTERN NSString *const kMPSettingsKeyFocusSearchAfterUnlock; // Enter search after unlocking the database +APPKIT_EXTERN NSString *const kMPSettingsKeyShowOrHideMacPass; + // Enable hotkey for showing or hiding MacPass + /* Entry Table Display */ APPKIT_EXTERN NSString *const kMPSettingsKeyDisplayClearTextPasswordsInEntryList; @@ -94,6 +97,7 @@ APPKIT_EXTERN NSString *const kMPSettingsKeyLoadUnsecurePlugins; // I APPKIT_EXTERN NSString *const kMPSettingsKeyDisabledPlugins; // NSArray of bundle identifiers of disabled plugins APPKIT_EXTERN NSString *const kMPSettingsKeyLoadIncompatiblePlugins; // If set to YES incompatible plugins (no version info, marked as incompatible, etc) will be loaded regardless APPKIT_EXTERN NSString *const kMPSettingsKeyHideIncopatiblePluginsWarning; // Do not show an alert, when MacPass encounteres incompatible plugins +APPKIT_EXTERN NSString *const kMPSettingsKeyShowHideKeyDataKey; // The stored Data for the user defined show/hide key for MacPass APPKIT_EXTERN NSString *const kMPSettingsKeyAllowRemoteFetchOfPluginRepository; // Allow the download of the plugin repository file /* Network */ diff --git a/MacPass/MPSettingsHelper.m b/MacPass/MPSettingsHelper.m index af0803d0..0ba0926d 100644 --- a/MacPass/MPSettingsHelper.m +++ b/MacPass/MPSettingsHelper.m @@ -37,6 +37,8 @@ NSString *const kMPSettingsKeyFileChangeStrategy = @"FileChangeStrategy"; NSString *const kMPSettingsKeyEnableAutosave = @"EnableAutosave"; NSString *const kMPSettingsKeyFocusSearchAfterUnlock = @"FocusSearchAfterUnlock"; +NSString *const kMPSettingsKeyShowOrHideMacPass = @"ShowOrHideMacPass"; +NSString *const kMPSettingsKeyShowHideKeyDataKey = @"ShowHideKeyDataKey"; NSString *const kMPSettingsKeyDisplayClearTextPasswordsInEntryList = @"DisplayClearTextPasswordsInEntryList"; @@ -180,6 +182,8 @@ + (NSDictionary *)_standardDefaults { kMPSettingsKeyGloablAutotypeAlwaysShowCandidateSelection: @NO, kMPSettingsKeyUseUnifiedToolbar: @YES, // Do not use unified toolbar under Big Sur and above kMPSettingsKeyFocusSearchAfterUnlock: @NO, // Do not enter search directly after unlocking the database + kMPSettingsKeyShowOrHideMacPass: @NO, // Do not enable hotkey by default + kMPSettingsKeyShowHideKeyDataKey: DDHotKey.defaultHotKeyData, // Empty - No default hotkey kMPSettingsKeyUsePrivateBrowsingWhenOpeningURLs: @NO // No private mode when option URLs by default }; }); diff --git a/MacPass/MPWorkflowPreferencesController.h b/MacPass/MPWorkflowPreferencesController.h index 58a44ec1..78ba115c 100644 --- a/MacPass/MPWorkflowPreferencesController.h +++ b/MacPass/MPWorkflowPreferencesController.h @@ -23,8 +23,13 @@ #import "MPViewController.h" #import "MPPreferencesTab.h" +@class DDHotKeyTextField; + @interface MPWorkflowPreferencesController : MPViewController +@property (strong) IBOutlet DDHotKeyTextField *hotkeyTextField; +@property (strong) IBOutlet NSTextField *hotkeyWarningTextField; + @end diff --git a/MacPass/MPWorkflowPreferencesController.m b/MacPass/MPWorkflowPreferencesController.m index f7859dcd..2dc1d80e 100644 --- a/MacPass/MPWorkflowPreferencesController.m +++ b/MacPass/MPWorkflowPreferencesController.m @@ -24,8 +24,15 @@ #import "MPSettingsHelper.h" +#import "DDHotKeyCenter.h" +#import "DDHotKey+MacPassAdditions.h" +#import "DDHotKeyTextField.h" + + @interface MPWorkflowPreferencesController () + + @property (strong) IBOutlet NSPopUpButton *browserPopup; @property (strong) IBOutlet NSPopUpButton *doubleClickURLPopup; @property (strong) IBOutlet NSPopUpButton *doubleClickTitlePopup; @@ -33,7 +40,10 @@ @interface MPWorkflowPreferencesController () @property (strong) IBOutlet NSButton *generatePasswordOnEntriesCheckButton; @property (strong) IBOutlet NSButton *hideAfterCopyToClipboardCheckButton; @property (strong) IBOutlet NSButton *focusSearchAfterUnlockCheckButton; + //@property (strong) IBOutlet NSButton *privateBrowsingCheckButton; +@property (strong) IBOutlet NSButton *showOrHideMacPassCheckButton; +@property (nonatomic, strong) DDHotKey *hotKey; - (IBAction)_showCustomBrowserSelection:(id)sender; @@ -76,6 +86,15 @@ - (void)viewDidLoad { // toObject:defaultsController // withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyUsePrivateBrowsingWhenOpeningURLs] // options:nil]; + + [self.showOrHideMacPassCheckButton bind:NSValueBinding + toObject:defaultsController + withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyShowOrHideMacPass] + options:nil]; + + [self.hotkeyTextField bind:NSEnabledBinding toObject:defaultsController withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyShowOrHideMacPass] options:nil]; + self.hotkeyTextField.delegate = self; + [self _updateBrowserSelection]; } @@ -94,8 +113,34 @@ - (NSString *)label { - (void)willShowTab { [self _updateBrowserSelection]; + if(!_hotKey) { + _hotKey = [DDHotKey hotKeyWithKeyData:[NSUserDefaults.standardUserDefaults dataForKey:kMPSettingsKeyShowHideKeyDataKey]]; + } + /* Only call the setter if the hotkeys are different, otherwise the dealloc call will unregister them*/ + if(![self.hotkeyTextField.hotKey isEqual:self.hotKey]) { + self.hotkeyTextField.hotKey = self.hotKey; + } +} + +#pragma mark - +#pragma mark Properties +- (void)setHotKey:(DDHotKey *)hotKey { + if([self.hotKey isEqual:hotKey]) { + NSLog(@"hotkey IS already set"); + return; // Nothing of interest has changed; + } + NSLog(@"hotkey set"); + _hotKey = hotKey; + + [NSUserDefaults.standardUserDefaults setObject:self.hotKey.keyData forKey:kMPSettingsKeyShowHideKeyDataKey]; + } +- (void)_showKeyCodeMissingKeyWarning:(BOOL)show { + self.hotkeyWarningTextField.hidden = !show; +} + + #pragma mark Actions - (void)_selectBrowser:(id)sender { NSString *browserBundleId = [sender representedObject]; @@ -200,4 +245,15 @@ - (NSArray *)_bundleIdentifierForHTTPS { return browserBundles; } +- (void)controlTextDidChange:(NSNotification *)obj { + BOOL validHotKey = self.hotkeyTextField.hotKey.valid; + [self _showKeyCodeMissingKeyWarning:!validHotKey]; + if(validHotKey) { + self.hotKey = self.hotkeyTextField.hotKey; + } +} + + + + @end