OverSight/Application/Application/PrefsWindowController.m

488 lines
14 KiB
Objective-C

//
// file: PrefsWindowController.h
// project: OverSight (main app)
// description: preferences window controller (header)
//
// created by Patrick Wardle
// copyright (c) 2017 Objective-See. All rights reserved.
//
#import "consts.h"
#import "Update.h"
#import "utilities.h"
#import "AppDelegate.h"
#import "PrefsWindowController.h"
#import "UpdateWindowController.h"
/* GLOBALS */
//log handle
extern os_log_t logHandle;
@implementation PrefsWindowController
@synthesize toolbar;
@synthesize modesView;
@synthesize actionView;
@synthesize updateView;
@synthesize updateWindowController;
//start at login button
#define BUTTON_AUTOSTART_MODE 1
//'no-icon mode' button
#define BUTTON_NO_ICON_MODE 2
//no external devices mode
#define BUTTON_NO_EXTERNAL_DEVICES_MODE 3
//'disable inactive' button
#define BUTTON_DISABLE_INACTIVE_MODE 4
//action
#define BUTTON_EXECUTE_ACTION 5
//args for action
#define BUTTON_EXECUTE_ACTION_ARGS 6
//'update mode' button
#define BUTTON_NO_UPDATE_MODE 7
//init 'general' view
// add it, and make it selected
-(void)awakeFromNib
{
//set title
self.window.title = [NSString stringWithFormat:@"%@ v%@", PRODUCT_NAME, getAppVersion()];
//set rules prefs as default
[self toolbarButtonHandler:nil];
//set rules prefs as default
[self.toolbar setSelectedItemIdentifier:TOOLBAR_MODES_ID];
return;
}
//toolbar view handler
// toggle view based on user selection
-(IBAction)toolbarButtonHandler:(id)sender
{
//view
NSView* view = nil;
//when we've prev added a view
// remove the prev view cuz adding a new one
if(nil != sender)
{
//remove
[[[self.window.contentView subviews] lastObject] removeFromSuperview];
}
//assign view
switch(((NSToolbarItem*)sender).tag)
{
//modes
case TOOLBAR_MODES:
//set view
view = self.modesView;
//start at login
((NSButton*)[view viewWithTag:BUTTON_AUTOSTART_MODE]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_AUTOSTART_MODE];
//no icon
((NSButton*)[view viewWithTag:BUTTON_NO_ICON_MODE]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_NO_ICON_MODE];
//no external device monitoring
((NSButton*)[view viewWithTag:BUTTON_NO_EXTERNAL_DEVICES_MODE]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_NO_EXTERNAL_DEVICES_MODE];
//disable inactive alerts
((NSButton*)[view viewWithTag:BUTTON_DISABLE_INACTIVE_MODE]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_DISABLE_INACTIVE];
break;
//actions
case TOOLBAR_ACTION:
{
//set view
view = self.actionView;
//action
((NSButton*)[view viewWithTag:BUTTON_EXECUTE_ACTION]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION];
//set 'execute action' path
if(0 != [NSUserDefaults.standardUserDefaults objectForKey:PREF_EXECUTE_PATH])
{
//set
self.executePath.stringValue = [NSUserDefaults.standardUserDefaults objectForKey:PREF_EXECUTE_PATH];
}
//set state of 'execute action' to match
self.executePath.enabled = [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION];
//set action + args
((NSButton*)[view viewWithTag:BUTTON_EXECUTE_ACTION_ARGS]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION_ARGS];
//set state of 'execute action' to match
self.executeArgsButton.enabled = [NSUserDefaults.standardUserDefaults boolForKey:PREF_EXECUTE_ACTION];
//make 'Browse' button first responder
// calling this without a timeout, sometimes fails :/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (100 * NSEC_PER_MSEC)), dispatch_get_main_queue(),
^{
//set first responder
[self.window makeFirstResponder:self.browseButton];
});
break;
}
//update
case TOOLBAR_UPDATE:
//set view
view = self.updateView;
//set 'update' button state
((NSButton*)[view viewWithTag:BUTTON_NO_UPDATE_MODE]).state = [NSUserDefaults.standardUserDefaults boolForKey:PREF_NO_UPDATE_MODE];
break;
default:
//bail
goto bail;
}
//set frame rect
view.frame = CGRectMake(0, 75, self.window.contentView.frame.size.width, self.window.contentView.frame.size.height-75);
//add to window
[self.window.contentView addSubview:view];
bail:
return;
}
//invoked when user toggles button
// update preferences for that button
-(IBAction)togglePreference:(id)sender
{
//preferences
NSMutableDictionary* updatedPreferences = nil;
//button state
BOOL state = NO;
//init
updatedPreferences = [NSMutableDictionary dictionary];
//get button state
state = ((NSButton*)sender).state;
//set appropriate preference
switch(((NSButton*)sender).tag)
{
//autostart
case BUTTON_AUTOSTART_MODE:
{
//toggle login item
toggleLoginItem([NSURL fileURLWithPath:NSBundle.mainBundle.bundlePath], state);
//set
[NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_PASSIVE_MODE];
break;
}
//no icon mode
case BUTTON_NO_ICON_MODE:
{
//toggle
[((AppDelegate*)[[NSApplication sharedApplication] delegate]) toggleIcon:!state];
//set
[NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_NO_ICON_MODE];
break;
}
//no icon mode
case BUTTON_NO_EXTERNAL_DEVICES_MODE:
{
//set
[NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_NO_EXTERNAL_DEVICES_MODE];
break;
}
//disable inactive mode
case BUTTON_DISABLE_INACTIVE_MODE:
{
//set
[NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_DISABLE_INACTIVE];
break;
}
//execute action
// also toggle state of path
case BUTTON_EXECUTE_ACTION:
{
//set
[NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_EXECUTE_ACTION];
//set path field state to match
self.executePath.enabled = state;
//set path field state to match
self.executeArgsButton.enabled = state;
//enabled, but no path?
// launch 'browse' pane for user to select
if( (NSControlStateValueOn == state) &&
(0 == self.executePath.stringValue.length) )
{
//show 'browse'
[self browseButtonHandler:nil];
}
//disabled?
// reset path
if(NSControlStateValueOff == state)
{
//reset
self.executePath.stringValue = @"";
//save path & sync
[NSUserDefaults.standardUserDefaults setObject:nil forKey:PREF_EXECUTE_PATH];
[NSUserDefaults.standardUserDefaults synchronize];
}
break;
}
//execute action
// also toggle state of path
case BUTTON_EXECUTE_ACTION_ARGS:
{
//set
[NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_EXECUTE_ACTION_ARGS];
break;
}
//no update mode
case BUTTON_NO_UPDATE_MODE:
{
//set
[NSUserDefaults.standardUserDefaults setBool:state forKey:PREF_NO_UPDATE_MODE];
break;
}
default:
break;
}
//sync
[NSUserDefaults.standardUserDefaults synchronize];
return;
}
//'view rules' button handler
// call helper method to show rule's window
-(IBAction)viewRules:(id)sender
{
//call into app delegate to show app rules
[((AppDelegate*)[[NSApplication sharedApplication] delegate]) showRules:nil];
return;
}
//'check for update' button handler
-(IBAction)check4Update:(id)sender
{
//update obj
Update* update = nil;
//disable button
self.updateButton.enabled = NO;
//reset
self.updateLabel.stringValue = @"";
//show/start spinner
[self.updateIndicator startAnimation:self];
//init update obj
update = [[Update alloc] init];
//check for update
// 'updateResponse newVersion:' method will be called when check is done
[update checkForUpdate:^(NSUInteger result, NSString* newVersion) {
//process response
[self updateResponse:result newVersion:newVersion];
}];
return;
}
//process update response
// error, no update, update not compatible, update/new version
-(void)updateResponse:(NSInteger)result newVersion:(NSString*)newVersion
{
//re-enable button
self.updateButton.enabled = YES;
//stop/hide spinner
[self.updateIndicator stopAnimation:self];
switch(result)
{
//error
case Update_Error:
//set label
self.updateLabel.stringValue = @"Error: update check failed";
break;
//no updates
case Update_None:
//dbg msg
os_log_debug(logHandle, "no updates available");
//set label
self.updateLabel.stringValue = [NSString stringWithFormat:@"Installed version (%@),\r\nis the latest.", getAppVersion()];
break;
//this version of macOS, not supported
case Update_NotSupported:
//dbg msg
os_log_debug(logHandle, "update available, but not for this version of macOS");
//set label
self.updateLabel.stringValue = [NSString stringWithFormat:@"Update available, but isn't supported on macOS %ld.%ld", NSProcessInfo.processInfo.operatingSystemVersion.majorVersion, NSProcessInfo.processInfo.operatingSystemVersion.minorVersion];
break;
//new version
case Update_Available:
//dbg msg
os_log_debug(logHandle, "a new version (%@) is available", newVersion);
//alloc update window
updateWindowController = [[UpdateWindowController alloc] initWithWindowNibName:@"UpdateWindow"];
//configure
[self.updateWindowController configure:[NSString stringWithFormat:@"a new version (%@) is available!", newVersion] buttonTitle:@"Update"];
//center window
[[self.updateWindowController window] center];
//show it
[self.updateWindowController showWindow:self];
//invoke function in background that will make window modal
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//make modal
makeModal(self.updateWindowController);
});
break;
}
return;
}
//on window close
// set activation policy
-(void)windowWillClose:(NSNotification *)notification
{
//wait a bit, then set activation policy
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
//on main thread
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//set activation policy
[((AppDelegate*)[[NSApplication sharedApplication] delegate]) setActivationPolicy];
});
});
return;
}
//'browse' button handler
// open a panel for user to select file
-(IBAction)browseButtonHandler:(id)sender
{
//'browse' panel
NSOpenPanel *panel = nil;
//response to 'browse' panel
NSInteger response = 0;
//init panel
panel = [NSOpenPanel openPanel];
//allow files
panel.canChooseFiles = YES;
//don't allow directories
panel.canChooseDirectories = NO;
//disable multiple selections
panel.allowsMultipleSelection = NO;
//can open app bundles
panel.treatsFilePackagesAsDirectories = YES;
//start in user's home directory
panel.directoryURL = [NSURL fileURLWithPath:NSHomeDirectory()];
//show it
response = [panel runModal];
//cancel?
// uncheck if path is blank
if(NSModalResponseCancel == response)
{
//blank?
if(0 == self.executePath.stringValue.length)
{
//uncheck
self.executePathButton.state = NSControlStateValueOff;
}
//bail
goto bail;
}
//set path in ui
self.executePath.stringValue = panel.URL.path;
//save path & sync
[NSUserDefaults.standardUserDefaults setObject:self.executePath.stringValue forKey:PREF_EXECUTE_PATH];
[NSUserDefaults.standardUserDefaults synchronize];
bail:
return;
}
@end