UI improvements to the Mac app.
This commit is contained in:
parent
651a398798
commit
6422084fa0
2
External/Pearl
vendored
2
External/Pearl
vendored
@ -1 +1 @@
|
||||
Subproject commit e50cad1411aaafe8083b3863e48b53a1f722c239
|
||||
Subproject commit a3f33c5f0cbafd6910ae55fb313764c18b48ad69
|
@ -26,7 +26,7 @@
|
||||
@property(nonatomic, strong) NSWindowController *initialWindow;
|
||||
@end
|
||||
|
||||
@implementation MPMacAppDelegate { NSWindow *_window; }
|
||||
@implementation MPMacAppDelegate
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wfour-char-constants"
|
||||
@ -66,71 +66,157 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
return eventNotHandledErr;
|
||||
}
|
||||
|
||||
- (void)updateUsers {
|
||||
#pragma mark - Life
|
||||
|
||||
[[[self.usersItem submenu] itemArray] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if (idx > 2)
|
||||
[[self.usersItem submenu] removeItem:obj];
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
|
||||
// Setup delegates and listeners.
|
||||
[MPConfig get].delegate = self;
|
||||
__weak id weakSelf = self;
|
||||
[self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
[weakSelf updateMenuItems];
|
||||
} );
|
||||
} forKeyPath:@"key" options:0 context:nil];
|
||||
[self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
[weakSelf updateMenuItems];
|
||||
} );
|
||||
} forKeyPath:@"activeUser" options:0 context:nil];
|
||||
[self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
[weakSelf updateMenuItems];
|
||||
} );
|
||||
} forKeyPath:@"storeManager.cloudAvailable" options:0 context:nil];
|
||||
|
||||
// Status item.
|
||||
self.statusView = [[RHStatusItemView alloc] initWithStatusBarItem:
|
||||
[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]];
|
||||
self.statusView.image = [NSImage imageNamed:@"menu-icon"];
|
||||
self.statusView.menu = self.statusMenu;
|
||||
self.statusView.target = self;
|
||||
self.statusView.action = @selector( showMenu );
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidChangeNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[self updateUsers];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidImportChangesNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[self updateUsers];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
NSString *key = note.object;
|
||||
if (!key || [key isEqualToString:NSStringFromSelector( @selector( rememberLogin ) )])
|
||||
self.rememberPasswordItem.state = [[MPConfig get].rememberLogin boolValue]? NSOnState: NSOffState;
|
||||
if (!key || [key isEqualToString:NSStringFromSelector( @selector( dialogStyleHUD ) )]) {
|
||||
self.dialogStyleRegular.state = ![[MPMacConfig get].dialogStyleHUD boolValue]? NSOnState: NSOffState;
|
||||
self.dialogStyleHUD.state = [[MPMacConfig get].dialogStyleHUD boolValue]? NSOnState: NSOffState;
|
||||
if (![self.passwordWindow.window isVisible])
|
||||
self.passwordWindow = nil;
|
||||
else {
|
||||
[self.passwordWindow close];
|
||||
self.passwordWindow = nil;
|
||||
[self showPasswordWindow:nil];
|
||||
}
|
||||
}
|
||||
}];
|
||||
[self updateUsers];
|
||||
|
||||
// Global hotkey.
|
||||
EventHotKeyRef hotKeyRef;
|
||||
EventTypeSpec hotKeyEvents[1] = { { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed } };
|
||||
OSStatus status = InstallApplicationEventHandler( NewEventHandlerUPP( MPHotKeyHander ), GetEventTypeCount( hotKeyEvents ),
|
||||
hotKeyEvents, (__bridge void *)self, NULL );
|
||||
if (status != noErr)
|
||||
err( @"Error installing application event handler: %i", (int)status );
|
||||
status = RegisterEventHotKey( 35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef );
|
||||
if (status != noErr)
|
||||
err( @"Error registering 'show' hotkey: %i", (int)status );
|
||||
status = RegisterEventHotKey( 35 /* p */, controlKey + optionKey + cmdKey, MPLockHotKey, GetApplicationEventTarget(), 0, &hotKeyRef );
|
||||
if (status != noErr)
|
||||
err( @"Error registering 'lock' hotkey: %i", (int)status );
|
||||
|
||||
// Initial display.
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
if ([[MPMacConfig get].firstRun boolValue]) {
|
||||
self.initialWindow = [[NSWindowController alloc] initWithWindowNibName:@"MPInitialWindow" owner:self];
|
||||
[self.initialWindow.window setLevel:NSFloatingWindowLevel];
|
||||
[self.initialWindow showWindow:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||
|
||||
if (![[MPConfig get].rememberLogin boolValue])
|
||||
[self lock:nil];
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
||||
// Save changes in the application's managed object context before the application terminates.
|
||||
|
||||
NSManagedObjectContext *context = [MPMacAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
if (!context) {
|
||||
self.createUserItem.title = @"New User (Not ready)";
|
||||
self.createUserItem.enabled = NO;
|
||||
self.createUserItem.toolTip = @"Please wait until the app is fully loaded.";
|
||||
self.deleteUserItem.title = @"Delete User (Not ready)";
|
||||
self.deleteUserItem.enabled = NO;
|
||||
self.deleteUserItem.toolTip = @"Please wait until the app is fully loaded.";
|
||||
[self.usersItem.submenu addItemWithTitle:@"Loading..." action:NULL keyEquivalent:@""].enabled = NO;
|
||||
if (!context)
|
||||
return NSTerminateNow;
|
||||
|
||||
return;
|
||||
if (![context commitEditing])
|
||||
return NSTerminateCancel;
|
||||
|
||||
if (![context hasChanges])
|
||||
return NSTerminateNow;
|
||||
|
||||
[context saveToStore];
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
MPUserEntity *activeUser = [self activeUserInContext:context];
|
||||
#pragma mark - State
|
||||
|
||||
self.createUserItem.title = @"New User";
|
||||
self.createUserItem.enabled = YES;
|
||||
self.createUserItem.toolTip = nil;
|
||||
- (void)setActiveUser:(MPUserEntity *)activeUser {
|
||||
|
||||
self.deleteUserItem.title = activeUser? @"Delete User": @"Delete User (None Selected)";
|
||||
self.deleteUserItem.enabled = activeUser != nil;
|
||||
self.deleteUserItem.toolTip = activeUser? nil: @"First select the user to delete.";
|
||||
[super setActiveUser:activeUser];
|
||||
|
||||
NSError *error = nil;
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
||||
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
|
||||
NSArray *users = [context executeFetchRequest:fetchRequest error:&error];
|
||||
if (!users)
|
||||
err( @"Failed to load users: %@", error );
|
||||
[MPMacConfig get].usedUserName = activeUser.name;
|
||||
|
||||
if (![users count]) {
|
||||
NSMenuItem *noUsersItem = [self.usersItem.submenu addItemWithTitle:@"No users" action:NULL keyEquivalent:@""];
|
||||
noUsersItem.enabled = NO;
|
||||
noUsersItem.toolTip = @"Use the iOS app to create users and make sure iCloud is enabled in its preferences as well. "
|
||||
@"Then give iCloud some time to sync the new user to your Mac.";
|
||||
PearlMainQueue( ^{
|
||||
[self updateUsers];
|
||||
} );
|
||||
}
|
||||
|
||||
self.usersItem.state = NSMixedState;
|
||||
for (MPUserEntity *user in users) {
|
||||
NSMenuItem *userItem = [[NSMenuItem alloc] initWithTitle:user.name action:@selector( selectUser: ) keyEquivalent:@""];
|
||||
[userItem setTarget:self];
|
||||
[userItem setRepresentedObject:[user objectID]];
|
||||
[[self.usersItem submenu] addItem:userItem];
|
||||
- (void)setLoginItemEnabled:(BOOL)enabled {
|
||||
|
||||
if (!activeUser && [user.name isEqualToString:[MPMacConfig get].usedUserName])
|
||||
[super setActiveUser:activeUser = user];
|
||||
|
||||
if ([activeUser isEqual:user]) {
|
||||
userItem.state = NSOnState;
|
||||
self.usersItem.state = NSOffState;
|
||||
}
|
||||
BOOL loginItemEnabled = [self loginItemEnabled];
|
||||
if (loginItemEnabled != enabled) {
|
||||
if (SMLoginItemSetEnabled( (__bridge CFStringRef)LOGIN_HELPER_BUNDLE_ID, (Boolean)enabled ) == true)
|
||||
loginItemEnabled = enabled;
|
||||
else
|
||||
userItem.state = NSOffState;
|
||||
wrn( @"Failed to set login item." );
|
||||
}
|
||||
|
||||
[self updateMenuItems];
|
||||
self.openAtLoginItem.state = loginItemEnabled? NSOnState: NSOffState;
|
||||
self.openAtLoginButton.state = loginItemEnabled? NSOnState: NSOffState;
|
||||
}
|
||||
|
||||
- (BOOL)loginItemEnabled {
|
||||
|
||||
// The easy and sane method (SMJobCopyDictionary) can pose problems when the app is sandboxed. -_-
|
||||
NSArray *jobs = (__bridge_transfer NSArray *)SMCopyAllJobDictionaries( kSMDomainUserLaunchd );
|
||||
|
||||
for (NSDictionary *job in jobs)
|
||||
if ([LOGIN_HELPER_BUNDLE_ID isEqualToString:[job objectForKey:@"Label"]]) {
|
||||
dbg( @"loginItemEnabled: %@", @([[job objectForKey:@"OnDemand"] boolValue]) );
|
||||
return [[job objectForKey:@"OnDemand"] boolValue];
|
||||
}
|
||||
|
||||
dbg( @"loginItemEnabled: not found" );
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)selectUser:(NSMenuItem *)item {
|
||||
|
||||
[self signOutAnimated:NO];
|
||||
@ -143,13 +229,6 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
err( @"While looking up selected user: %@", error );
|
||||
}
|
||||
|
||||
- (void)showMenu {
|
||||
|
||||
[self updateMenuItems];
|
||||
|
||||
[self.statusView popUpMenu];
|
||||
}
|
||||
|
||||
- (IBAction)togglePreference:(id)sender {
|
||||
|
||||
if (sender == self.enableCloudButton) {
|
||||
@ -278,104 +357,99 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
self.initialWindow = nil;
|
||||
}
|
||||
|
||||
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue {
|
||||
- (IBAction)showPasswordWindow:(id)sender {
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey )];
|
||||
}
|
||||
|
||||
#pragma mark - NSApplicationDelegate
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
|
||||
// Setup delegates and listeners.
|
||||
[MPConfig get].delegate = self;
|
||||
__weak id weakSelf = self;
|
||||
[self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
[weakSelf updateMenuItems];
|
||||
} );
|
||||
} forKeyPath:@"key" options:0 context:nil];
|
||||
[self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
[weakSelf updateMenuItems];
|
||||
} );
|
||||
} forKeyPath:@"activeUser" options:0 context:nil];
|
||||
[self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
[weakSelf updateMenuItems];
|
||||
} );
|
||||
} forKeyPath:@"storeManager.cloudAvailable" options:0 context:nil];
|
||||
|
||||
// Status item.
|
||||
self.statusView = [[RHStatusItemView alloc] initWithStatusBarItem:
|
||||
[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]];
|
||||
self.statusView.image = [NSImage imageNamed:@"menu-icon"];
|
||||
self.statusView.menu = self.statusMenu;
|
||||
self.statusView.target = self;
|
||||
self.statusView.action = @selector( showMenu );
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidChangeNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[self updateUsers];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidImportChangesNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[self updateUsers];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
NSString *key = note.object;
|
||||
if (!key || [key isEqualToString:NSStringFromSelector( @selector( rememberLogin ) )])
|
||||
self.rememberPasswordItem.state = [[MPConfig get].rememberLogin boolValue]? NSOnState: NSOffState;
|
||||
if (!key || [key isEqualToString:NSStringFromSelector( @selector( dialogStyleHUD ) )]) {
|
||||
self.dialogStyleRegular.state = ![[MPMacConfig get].dialogStyleHUD boolValue]? NSOnState: NSOffState;
|
||||
self.dialogStyleHUD.state = [[MPMacConfig get].dialogStyleHUD boolValue]? NSOnState: NSOffState;
|
||||
if (![self.passwordWindow.window isVisible])
|
||||
self.passwordWindow = nil;
|
||||
else {
|
||||
[self.passwordWindow close];
|
||||
self.passwordWindow = nil;
|
||||
[self showPasswordWindow:nil];
|
||||
}
|
||||
}
|
||||
}];
|
||||
[self updateUsers];
|
||||
|
||||
// Global hotkey.
|
||||
EventHotKeyRef hotKeyRef;
|
||||
EventTypeSpec hotKeyEvents[1] = { { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed } };
|
||||
OSStatus status = InstallApplicationEventHandler( NewEventHandlerUPP( MPHotKeyHander ), GetEventTypeCount( hotKeyEvents ),
|
||||
hotKeyEvents, (__bridge void *)self, NULL );
|
||||
if (status != noErr)
|
||||
err( @"Error installing application event handler: %i", (int)status );
|
||||
status = RegisterEventHotKey( 35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef );
|
||||
if (status != noErr)
|
||||
err( @"Error registering 'show' hotkey: %i", (int)status );
|
||||
status = RegisterEventHotKey( 35 /* p */, controlKey + optionKey + cmdKey, MPLockHotKey, GetApplicationEventTarget(), 0, &hotKeyRef );
|
||||
if (status != noErr)
|
||||
err( @"Error registering 'lock' hotkey: %i", (int)status );
|
||||
|
||||
// Initial display.
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
if ([[MPMacConfig get].firstRun boolValue]) {
|
||||
self.initialWindow = [[NSWindowController alloc] initWithWindowNibName:@"MPInitialWindow" owner:self];
|
||||
[self.initialWindow.window setLevel:NSFloatingWindowLevel];
|
||||
[self.initialWindow showWindow:self];
|
||||
}
|
||||
|
||||
// If no user, can't activate.
|
||||
if (![self activeUserForMainThread]) {
|
||||
[[NSAlert alertWithMessageText:@"No User Selected" defaultButton:[PearlStrings get].commonButtonOkay alternateButton:nil
|
||||
otherButton:nil informativeTextWithFormat:
|
||||
@"Begin by selecting or creating your user from the status menu (●●●|) next to the clock."]
|
||||
runModal];
|
||||
[self.statusView popUpMenu];
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)setActiveUser:(MPUserEntity *)activeUser {
|
||||
// Don't show window if we weren't already running (ie. if we haven't been activated before).
|
||||
if (!self.passwordWindow)
|
||||
self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"];
|
||||
|
||||
[super setActiveUser:activeUser];
|
||||
[self.passwordWindow showWindow:self];
|
||||
}
|
||||
|
||||
[MPMacConfig get].usedUserName = activeUser.name;
|
||||
#pragma mark - Private
|
||||
|
||||
PearlMainQueue( ^{
|
||||
[self updateUsers];
|
||||
} );
|
||||
- (void)updateUsers {
|
||||
|
||||
[[[self.usersItem submenu] itemArray] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if (idx > 2)
|
||||
[[self.usersItem submenu] removeItem:obj];
|
||||
}];
|
||||
|
||||
NSManagedObjectContext *context = [MPMacAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
if (!context) {
|
||||
self.createUserItem.title = @"New User (Not ready)";
|
||||
self.createUserItem.enabled = NO;
|
||||
self.createUserItem.toolTip = @"Please wait until the app is fully loaded.";
|
||||
self.deleteUserItem.title = @"Delete User (Not ready)";
|
||||
self.deleteUserItem.enabled = NO;
|
||||
self.deleteUserItem.toolTip = @"Please wait until the app is fully loaded.";
|
||||
[self.usersItem.submenu addItemWithTitle:@"Loading..." action:NULL keyEquivalent:@""].enabled = NO;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
MPUserEntity *activeUser = [self activeUserInContext:context];
|
||||
|
||||
self.createUserItem.title = @"New User";
|
||||
self.createUserItem.enabled = YES;
|
||||
self.createUserItem.toolTip = nil;
|
||||
|
||||
self.deleteUserItem.title = activeUser? @"Delete User": @"Delete User (None Selected)";
|
||||
self.deleteUserItem.enabled = activeUser != nil;
|
||||
self.deleteUserItem.toolTip = activeUser? nil: @"First select the user to delete.";
|
||||
|
||||
NSError *error = nil;
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
|
||||
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
|
||||
NSArray *users = [context executeFetchRequest:fetchRequest error:&error];
|
||||
if (!users)
|
||||
err( @"Failed to load users: %@", error );
|
||||
|
||||
if (![users count]) {
|
||||
NSMenuItem *noUsersItem = [self.usersItem.submenu addItemWithTitle:@"No users" action:NULL keyEquivalent:@""];
|
||||
noUsersItem.enabled = NO;
|
||||
noUsersItem.toolTip = @"Use the iOS app to create users and make sure iCloud is enabled in its preferences as well. "
|
||||
@"Then give iCloud some time to sync the new user to your Mac.";
|
||||
}
|
||||
|
||||
self.usersItem.state = NSMixedState;
|
||||
for (MPUserEntity *user in users) {
|
||||
NSMenuItem *userItem = [[NSMenuItem alloc] initWithTitle:user.name action:@selector( selectUser: ) keyEquivalent:@""];
|
||||
[userItem setTarget:self];
|
||||
[userItem setRepresentedObject:[user objectID]];
|
||||
[[self.usersItem submenu] addItem:userItem];
|
||||
|
||||
if (!activeUser && [user.name isEqualToString:[MPMacConfig get].usedUserName])
|
||||
[super setActiveUser:activeUser = user];
|
||||
|
||||
if ([activeUser isEqual:user]) {
|
||||
userItem.state = NSOnState;
|
||||
self.usersItem.state = NSOffState;
|
||||
}
|
||||
else
|
||||
userItem.state = NSOffState;
|
||||
}
|
||||
|
||||
[self updateMenuItems];
|
||||
}
|
||||
|
||||
- (void)showMenu {
|
||||
|
||||
[self updateMenuItems];
|
||||
|
||||
[self.statusView popUpMenu];
|
||||
}
|
||||
|
||||
- (void)updateMenuItems {
|
||||
@ -440,77 +514,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)showPasswordWindow:(id)sender {
|
||||
#pragma mark - PearlConfigDelegate
|
||||
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue {
|
||||
|
||||
// If no user, can't activate.
|
||||
if (![self activeUserForMainThread]) {
|
||||
[[NSAlert alertWithMessageText:@"No User Selected" defaultButton:[PearlStrings get].commonButtonOkay alternateButton:nil
|
||||
otherButton:nil informativeTextWithFormat:
|
||||
@"Begin by selecting or creating your user from the status menu (●●●|) next to the clock."]
|
||||
runModal];
|
||||
[self.statusView popUpMenu];
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't show window if we weren't already running (ie. if we haven't been activated before).
|
||||
if (!self.passwordWindow)
|
||||
self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"];
|
||||
|
||||
[self.passwordWindow showWindow:self];
|
||||
}
|
||||
|
||||
- (void)setLoginItemEnabled:(BOOL)enabled {
|
||||
|
||||
BOOL loginItemEnabled = [self loginItemEnabled];
|
||||
if (loginItemEnabled != enabled) {
|
||||
if (SMLoginItemSetEnabled( (__bridge CFStringRef)LOGIN_HELPER_BUNDLE_ID, (Boolean)enabled ) == true)
|
||||
loginItemEnabled = enabled;
|
||||
else
|
||||
wrn( @"Failed to set login item." );
|
||||
}
|
||||
|
||||
self.openAtLoginItem.state = loginItemEnabled? NSOnState: NSOffState;
|
||||
self.openAtLoginButton.state = loginItemEnabled? NSOnState: NSOffState;
|
||||
}
|
||||
|
||||
- (BOOL)loginItemEnabled {
|
||||
|
||||
// The easy and sane method (SMJobCopyDictionary) can pose problems when the app is sandboxed. -_-
|
||||
NSArray *jobs = (__bridge_transfer NSArray *)SMCopyAllJobDictionaries( kSMDomainUserLaunchd );
|
||||
|
||||
for (NSDictionary *job in jobs)
|
||||
if ([LOGIN_HELPER_BUNDLE_ID isEqualToString:[job objectForKey:@"Label"]]) {
|
||||
dbg( @"loginItemEnabled: %@", @([[job objectForKey:@"OnDemand"] boolValue]) );
|
||||
return [[job objectForKey:@"OnDemand"] boolValue];
|
||||
}
|
||||
|
||||
dbg( @"loginItemEnabled: not found" );
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||
|
||||
if (![[MPConfig get].rememberLogin boolValue])
|
||||
[self lock:nil];
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
|
||||
// Save changes in the application's managed object context before the application terminates.
|
||||
|
||||
NSManagedObjectContext *context = [MPMacAppDelegate managedObjectContextForMainThreadIfReady];
|
||||
if (!context)
|
||||
return NSTerminateNow;
|
||||
|
||||
if (![context commitEditing])
|
||||
return NSTerminateCancel;
|
||||
|
||||
if (![context hasChanges])
|
||||
return NSTerminateNow;
|
||||
|
||||
[context saveToStore];
|
||||
return NSTerminateNow;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey )];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -22,6 +22,7 @@
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPElementModel.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "PearlProfiler.h"
|
||||
|
||||
#define MPAlertIncorrectMP @"MPAlertIncorrectMP"
|
||||
#define MPAlertCreateSite @"MPAlertCreateSite"
|
||||
@ -357,12 +358,15 @@
|
||||
if ([self.window isOnActiveSpace] && self.window.alphaValue)
|
||||
return;
|
||||
|
||||
PearlProfiler *profiler = [PearlProfiler new];
|
||||
CGWindowID windowID = (CGWindowID)[self.window windowNumber];
|
||||
CGImageRef capturedImage = CGWindowListCreateImage( CGRectInfinite, kCGWindowListOptionOnScreenBelowWindow, windowID,
|
||||
kCGWindowImageBoundsIgnoreFraming );
|
||||
[profiler finishJob:@"captured window: %d, on screen: %@", windowID, self.window.screen];
|
||||
NSImage *screenImage = [[NSImage alloc] initWithCGImage:capturedImage size:NSMakeSize(
|
||||
CGImageGetWidth( capturedImage ) / self.window.backingScaleFactor,
|
||||
CGImageGetHeight( capturedImage ) / self.window.backingScaleFactor )];
|
||||
[profiler finishJob:@"image size: %@, bytes: %ld", NSStringFromSize( screenImage.size ), screenImage.TIFFRepresentation.length ];
|
||||
|
||||
NSImage *smallImage = [[NSImage alloc] initWithSize:NSMakeSize(
|
||||
CGImageGetWidth( capturedImage ) / 20,
|
||||
@ -373,12 +377,16 @@
|
||||
operation:NSCompositeSourceOver
|
||||
fraction:1.0];
|
||||
[smallImage unlockFocus];
|
||||
[profiler finishJob:@"small image size: %@, bytes: %ld", NSStringFromSize( screenImage.size ), screenImage.TIFFRepresentation.length];
|
||||
|
||||
self.blurView.image = smallImage;
|
||||
[profiler finishJob:@"assigned image"];
|
||||
|
||||
[self.window setFrame:self.window.screen.frame display:YES];
|
||||
[profiler finishJob:@"assigned frame"];
|
||||
[[NSAnimationContext currentContext] setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
|
||||
[[self.window animator] setAlphaValue:1.0];
|
||||
[profiler finishJob:@"animating window"];
|
||||
}
|
||||
|
||||
- (void)fadeOut {
|
||||
|
@ -42,25 +42,15 @@
|
||||
<real key="inputRadius" value="20"/>
|
||||
</configuration>
|
||||
</ciFilter>
|
||||
<ciFilter name="CIColorControls">
|
||||
<ciFilter name="CIPhotoEffectProcess">
|
||||
<configuration>
|
||||
<real key="inputBrightness" value="-0.29999999999999999"/>
|
||||
<real key="inputContrast" value="1"/>
|
||||
<null key="inputImage"/>
|
||||
<real key="inputSaturation" value="0.0"/>
|
||||
</configuration>
|
||||
</ciFilter>
|
||||
<ciFilter name="CIFalseColor">
|
||||
<configuration>
|
||||
<ciColor key="inputColor0" red="0.70196080207824707" green="0.70196080207824707" blue="0.70196080207824707" alpha="1"/>
|
||||
<ciColor key="inputColor1" red="0.0" green="0.25098040699958801" blue="0.50196081399917603" alpha="1"/>
|
||||
<null key="inputImage"/>
|
||||
</configuration>
|
||||
</ciFilter>
|
||||
</contentFilters>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="small-screen" id="ArA-2w-I56"/>
|
||||
</imageView>
|
||||
<secureTextField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iGR-wo-ual" userLabel="Password Field">
|
||||
<secureTextField hidden="YES" focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iGR-wo-ual" userLabel="Password Field">
|
||||
<rect key="frame" x="18" y="243" width="612" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<shadow key="shadow">
|
||||
@ -113,9 +103,9 @@
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="pTl-fc-MSY">
|
||||
<rect key="frame" x="-2" y="0.0" width="516" height="29"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||
<shadow key="shadow">
|
||||
<shadow key="shadow" blurRadius="1">
|
||||
<size key="offset" width="0.0" height="1"/>
|
||||
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="color" white="0.0" alpha="0.80000000000000004" colorSpace="calibratedWhite"/>
|
||||
</shadow>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="center" title="apple.com" id="5Ug-eU-C9Y">
|
||||
<font key="font" size="24" name="HelveticaNeue-Thin"/>
|
||||
@ -170,9 +160,9 @@
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="OnR-s6-d4P" userLabel="Input Label">
|
||||
<rect key="frame" x="213" y="295" width="223" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<shadow key="shadow">
|
||||
<shadow key="shadow" blurRadius="1">
|
||||
<size key="offset" width="0.0" height="1"/>
|
||||
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="color" red="0.0" green="0.0" blue="0.0" alpha="0.80000000000000004" colorSpace="calibratedRGB"/>
|
||||
</shadow>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Maarten Billemont's password for:" id="1Lb-d0-fQD">
|
||||
<font key="font" size="14" name="HelveticaNeue"/>
|
||||
@ -186,9 +176,9 @@
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="512" id="rW7-Vq-4Xy"/>
|
||||
</constraints>
|
||||
<shadow key="shadow">
|
||||
<shadow key="shadow" blurRadius="1">
|
||||
<size key="offset" width="0.0" height="1"/>
|
||||
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="color" red="0.0" green="0.0" blue="0.0" alpha="0.80000000000000004" colorSpace="calibratedRGB"/>
|
||||
</shadow>
|
||||
<searchFieldCell key="cell" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" focusRingType="none" alignment="center" placeholderString="Site Name" id="ppl-2c-1E9">
|
||||
<font key="font" size="36" name="HelveticaNeue-Thin"/>
|
||||
@ -328,6 +318,19 @@
|
||||
</configuration>
|
||||
</ciFilter>
|
||||
</backgroundFilters>
|
||||
<animations>
|
||||
<caTransition key="subviews">
|
||||
<mutableData key="keyedArchiveRepresentation">
|
||||
YnBsaXN0MDDUAQIDBAUGICFYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKcHCA8Q
|
||||
CRUbVSRudWxs0wkKCwwNDlR0eXBlViRjbGFzc18QEl9fQ0FDb2RpbmdDb250ZW50c4ACgAaAA1RmYWRl
|
||||
0hEKEhRaTlMub2JqZWN0c6ETgASABdIWFxgZWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNBcnJheaIYGlhO
|
||||
U09iamVjdNIWFxwdXENBVHJhbnNpdGlvbqMeHxpcQ0FUcmFuc2l0aW9uW0NBQW5pbWF0aW9uXxAPTlNL
|
||||
ZXllZEFyY2hpdmVy0SIjVHJvb3SAAQAIABEAGgAjAC0AMgA3AD8ARQBMAFEAWABtAG8AcQBzAHgAfQCI
|
||||
AIoAjACOAJMAngCnAK8AsgC7AMAAzQDRAN4A6gD8AP8BBAAAAAAAAAIBAAAAAAAAACQAAAAAAAAAAAAA
|
||||
AAAAAAEGA
|
||||
</mutableData>
|
||||
</caTransition>
|
||||
</animations>
|
||||
</view>
|
||||
</window>
|
||||
<userDefaultsController representsSharedInstance="YES" id="yy2-3W-Ocj"/>
|
||||
|
@ -10,9 +10,11 @@
|
||||
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */; };
|
||||
93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; };
|
||||
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; };
|
||||
93D3970BCF85F7902E611168 /* PearlProfiler.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DB3A8ADED08C39A6228 /* PearlProfiler.m */; };
|
||||
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
|
||||
93D39C5789EFA607CF788082 /* MPElementModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */; };
|
||||
93D39C7C2BE7C0E0763B0177 /* MPElementCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D394495528B10D1B61A2C3 /* MPElementCollectionView.m */; };
|
||||
93D39D304F73B3BBA031522A /* PearlProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D394EEFF5BF555A55AF361 /* PearlProfiler.h */; };
|
||||
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
|
||||
93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */; };
|
||||
DA0933CC1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */; };
|
||||
@ -309,12 +311,14 @@
|
||||
93D392C3918763B3B72CF366 /* MPPasswordWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindowController.h; sourceTree = "<group>"; };
|
||||
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; };
|
||||
93D394495528B10D1B61A2C3 /* MPElementCollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementCollectionView.m; sourceTree = "<group>"; };
|
||||
93D394EEFF5BF555A55AF361 /* PearlProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PearlProfiler.h; path = ../../../External/Pearl/Pearl/PearlProfiler.h; sourceTree = "<group>"; };
|
||||
93D3960D320FF8A072B092E3 /* MPElementCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementCollectionView.h; sourceTree = "<group>"; };
|
||||
93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = "<group>"; };
|
||||
93D3977484534E99F9BA579D /* MPPasswordWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindow.h; sourceTree = "<group>"; };
|
||||
93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordWindowController.m; sourceTree = "<group>"; };
|
||||
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
|
||||
93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordWindow.m; sourceTree = "<group>"; };
|
||||
93D39DB3A8ADED08C39A6228 /* PearlProfiler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PearlProfiler.m; path = ../../../External/Pearl/Pearl/PearlProfiler.m; sourceTree = "<group>"; };
|
||||
93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementModel.m; sourceTree = "<group>"; };
|
||||
DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPInitialWindow.xib; sourceTree = "<group>"; };
|
||||
DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shot-laptop-leaning-iphone.png"; sourceTree = "<group>"; };
|
||||
@ -702,6 +706,8 @@
|
||||
DACA22121705DDC5002C6C22 /* External */,
|
||||
DA5BFA47147E415C00F98B1E /* Frameworks */,
|
||||
DA5BFA45147E415C00F98B1E /* Products */,
|
||||
93D39DB3A8ADED08C39A6228 /* PearlProfiler.m */,
|
||||
93D394EEFF5BF555A55AF361 /* PearlProfiler.h */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -1343,6 +1349,7 @@
|
||||
DAEB93E718AB0FFD000490CC /* crypto.h in Headers */,
|
||||
DAEB941318AB0FFD000490CC /* ssl2.h in Headers */,
|
||||
DAEB940D18AB0FFD000490CC /* ripemd.h in Headers */,
|
||||
93D39D304F73B3BBA031522A /* PearlProfiler.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1715,6 +1722,7 @@
|
||||
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */,
|
||||
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */,
|
||||
DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */,
|
||||
93D3970BCF85F7902E611168 /* PearlProfiler.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user