added preference handling (logging/new-version check)

added cmd+q hotkey for prefs app to close/quit
changed name of installer binary to match app
improved device detection (uses default if can't find apple camera/mic)
code cleanup/error handling
This commit is contained in:
Patrick Wardle 2016-09-19 21:29:40 -10:00
parent 9789a687ff
commit d9ae15b3bc
7 changed files with 220 additions and 77 deletions

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>

View File

@ -35,14 +35,21 @@
return self;
}
//grab first apple camera
//grab first apple camera, or default
// ->saves into iVar 'camera'
// note: could maybe use defaultDeviceWithDeviceType method() to default device...
-(void)findAppleCamera
{
//get cameras
// ->look for one that belongs to apple
for(AVCaptureDevice* currentCamera in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo])
//cameras
NSArray* cameras = nil;
//grab all cameras
cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"cameras: %@", cameras]);
//look for camera that belongs to apple
for(AVCaptureDevice* currentCamera in cameras)
{
//check if apple
if(YES == [currentCamera.manufacturer isEqualToString:@"Apple Inc."])
@ -55,6 +62,17 @@
}
}
//didn't find apple
// ->grab default camera
if(nil == self.camera)
{
//get default
self.camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"didn't find apple camera, grabbed default: %@", self.camera]);
}
return;
}
@ -62,9 +80,17 @@
// ->saves into iVar 'mic'
-(void)findAppleMic
{
//get mics
// ->loof for one that belongs to app
for(AVCaptureDevice* currentMic in [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio])
//mics
NSArray* mics = nil;
//grab all mics
mics = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"mics: %@", mics]);
//look for mic that belongs to apple
for(AVCaptureDevice* currentMic in mics)
{
//check if apple
// ->also check input source
@ -77,7 +103,17 @@
//exit loop
break;
}
}
//didn't find apple
// ->grab default camera
if(nil == self.mic)
{
//get default
self.mic = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
//dbg msg
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"didn't find apple 'imic', grabbed default: %@", self.mic]);
}
return;
@ -344,9 +380,15 @@ bail:
//wait semaphore
dispatch_semaphore_t waitSema = nil;
//init dictionary
//devices
NSMutableArray* devices = nil;
//init dictionary for event
event = [NSMutableDictionary dictionary];
//init array for devices
devices = [NSMutableArray array];
//sync
@synchronized (self)
{
@ -354,9 +396,23 @@ bail:
//set status
[self setVideoDevStatus:deviceID];
//add camera
if(nil != self.camera)
{
//add
[devices addObject:@{EVENT_DEVICE:self.camera, EVENT_DEVICE_STATUS:@(self.videoActive)}];
}
//add mic
if(nil != self.mic)
{
//add
[devices addObject:@{EVENT_DEVICE:self.mic, EVENT_DEVICE_STATUS:@(self.audioActive)}];
}
//send msg to status menu
// ->update menu to show (all) devices & their status
[((AppDelegate*)[[NSApplication sharedApplication] delegate]).statusBarMenuController updateStatusItemMenu:@[@{EVENT_DEVICE:self.camera, EVENT_DEVICE_STATUS:@(self.videoActive)},@{EVENT_DEVICE:self.mic, EVENT_DEVICE_STATUS:@(self.audioActive)}]];
[((AppDelegate*)[[NSApplication sharedApplication] delegate]).statusBarMenuController updateStatusItemMenu:devices];
//add device
event[EVENT_DEVICE] = self.camera;
@ -610,6 +666,9 @@ bail:
//ret var
BOOL bRegistered = NO;
//status var
OSStatus status = -1;
//property struct
AudioObjectPropertyAddress propertyStruct = {0};
@ -629,22 +688,24 @@ bail:
[self handleAudioNotification:deviceID];
};
OSStatus ret = AudioObjectAddPropertyListenerBlock(deviceID, &propertyStruct, dispatch_get_main_queue(), listenerBlock);
if (ret)
//add property listener for audio changes
status = AudioObjectAddPropertyListenerBlock(deviceID, &propertyStruct, dispatch_get_main_queue(), listenerBlock);
if(noErr != status)
{
abort(); // FIXME
//err msg
logMsg(LOG_ERR, [NSString stringWithFormat:@"AudioObjectAddPropertyListenerBlock() failed with %d", status]);
//bail
goto bail;
}
//happy
bRegistered = YES;
//bail
//bail
bail:
return bRegistered;
}
//build and display notification
@ -665,6 +726,9 @@ bail:
//log msg
NSMutableString* logMsg = nil;
//preferences
NSDictionary* preferences = nil;
//alloc notificaiton
notification = [[NSUserNotification alloc] init];
@ -677,6 +741,9 @@ bail:
//alloc log msg
logMsg = [NSMutableString string];
//always (manually) load preferences
preferences = [NSDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]];
//set title
// ->audio device
if(YES == [event[EVENT_DEVICE] isKindOfClass:NSClassFromString(@"AVCaptureHALDevice")])
@ -707,6 +774,10 @@ bail:
[title appendString:@" became active"];
}
//set details
// ->name of device
details = ((AVCaptureDevice*)event[EVENT_DEVICE]).localizedName;
//customize buttons
// ->for mic or inactive events, just say 'ok'
if( (YES == [event[EVENT_DEVICE] isKindOfClass:NSClassFromString(@"AVCaptureHALDevice")]) ||
@ -742,8 +813,7 @@ bail:
}
//log event?
// TODO: test will final apps/prefs
if(YES == [[NSUserDefaults standardUserDefaults] boolForKey:LOG_ACTIVITY])
if(YES == [preferences[PREF_LOG_ACTIVITY] boolValue])
{
//init msg
[logMsg appendString:@"OVERSIGHT: "];
@ -768,26 +838,11 @@ bail:
syslog(LOG_ERR, "%s\n", logMsg.UTF8String);
}
//icon issues
//http://stackoverflow.com/questions/11856766/osx-notification-center-icon
//contentImage for process icon?
//custom icon?
//http://stackoverflow.com/questions/24923979/nsusernotification-customisable-app-icon
//icon set automatically?
//[notification set]
//set title
[notification setTitle:title];
//set subtitle
[notification setSubtitle:((AVCaptureDevice*)event[EVENT_DEVICE]).localizedName];
//set details
// ->name of process using it / icon too?
//[notification setInformativeText:@"some process (1337)"];
[notification setSubtitle:details];
//set notification
[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];

View File

@ -28,6 +28,9 @@
// ->load status bar and kick off monitor
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
//default
NSDictionary* preferences = nil;
//dbg msg
logMsg(LOG_DEBUG, @"starting login item");
@ -37,9 +40,23 @@
//dbg msg
logMsg(LOG_DEBUG, @"initialized/loaded status bar (icon/menu)");
//first time, register defaults (manually cuz NSUserDefaults wasn't working - wtf)
// ->note: do this in here, since main app (with prefs) isn't run until user manually launches it
if(YES != [[NSFileManager defaultManager] fileExistsAtPath:[APP_PREFERENCES stringByExpandingTildeInPath]])
{
//dbg msg
logMsg(LOG_DEBUG, @"preference file not found; manually creating");
//write em out
[@{PREF_LOG_ACTIVITY:@YES, PREF_CHECK_4_UPDATES:@YES} writeToFile:[APP_PREFERENCES stringByExpandingTildeInPath] atomically:NO];
}
//always (manually) load preferences
preferences = [NSDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]];
//check for updates
// ->but only when user has not disabled that feature
if(YES == [[NSUserDefaults standardUserDefaults] boolForKey:CHECK_4_UPDATES])
if(YES == [preferences[PREF_CHECK_4_UPDATES] boolValue])
{
//after a minute
//->check for updates in background
@ -51,7 +68,6 @@
//check
[self isThereAndUpdate];
});
}
//create/init av event monitor

View File

@ -233,38 +233,32 @@
// ->launch main application which will show prefs
-(void)preferences:(id)sender
{
//path components
NSArray* pathComponents = nil;
//this works when packaged into Login Item into top-level app
NSArray *pathComponents = [[[NSBundle mainBundle] bundlePath] pathComponents];
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 4)];
NSString *path = [NSString pathWithComponents:pathComponents];
[[NSWorkspace sharedWorkspace] launchApplication:path];
//path to main app
NSString* mainApp = nil;
//init path components
pathComponents = [[[NSBundle mainBundle] bundlePath] pathComponents];
if(pathComponents.count <= 4)
{
//bail
goto bail;
}
/*
//controller for preferences window
PrefsWindowController* prefsWindowController = nil;
//init path to main app
// ->basically trim off last 4 path components
mainApp = [NSString pathWithComponents:[pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 4)]];
//dbg msg
logMsg(LOG_DEBUG, @"displaying preferences window");
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"launching main app; %@", mainApp]);
//grab controller
prefsWindowController = ((AppDelegate*)[[NSApplication sharedApplication] delegate]).prefsWindowController;
//show pref window
[prefsWindowController showWindow:sender];
//invoke function in background that will make window modal
// ->waits until window is non-nil
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//make modal
makeModal(prefsWindowController);
});
*/
//launch main app
[[NSWorkspace sharedWorkspace] launchApplication:mainApp];
//bail
bail:
return;
}

View File

@ -46,10 +46,14 @@
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
//set 'log activity' button state
self.logActivity.state = [[NSUserDefaults standardUserDefaults] boolForKey:LOG_ACTIVITY];
self.logActivity.state = [[NSUserDefaults standardUserDefaults] boolForKey:PREF_LOG_ACTIVITY];
//set 'automatically check for updates' button state
self.check4Updates.state = [[NSUserDefaults standardUserDefaults] boolForKey:CHECK_4_UPDATES];
self.check4Updates.state = [[NSUserDefaults standardUserDefaults] boolForKey:PREF_CHECK_4_UPDATES];
//register for hotkey presses
// ->for now, just cmd+q to quit app
[self registerKeypressHandler];
return;
}
@ -60,6 +64,75 @@
return YES;
}
//register handler for hot keys
-(void)registerKeypressHandler
{
//event handler
NSEvent* (^keypressHandler)(NSEvent *) = nil;
//init handler block
// ->just call helper function
keypressHandler = ^NSEvent * (NSEvent * theEvent){
//invoke helper
return [self handleKeypress:theEvent];
};
//register for key-down events
[NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:keypressHandler];
return;
}
//helper function for keypresses
// ->for now, only handle cmd+q, to quit
-(NSEvent*)handleKeypress:(NSEvent*)event
{
//flag indicating event was handled
BOOL wasHandled = NO;
//only care about 'cmd' + something
if(NSCommandKeyMask != (event.modifierFlags & NSCommandKeyMask))
{
//bail
goto bail;
}
//handle key-code
// command+q: quite
switch ([event keyCode])
{
//'q' (quit)
case KEYCODE_Q:
//bye!
[[NSApplication sharedApplication] terminate:nil];
//set flag
wasHandled = YES;
break;
default:
break;
}
//bail
bail:
//nil out event if it was handled
if(YES == wasHandled)
{
//nil
event = nil;
}
return event;
}
//toggle/set preferences
-(IBAction)togglePreference:(NSButton *)sender
{
@ -67,16 +140,19 @@
if(sender == self.logActivity)
{
//set
[[NSUserDefaults standardUserDefaults] setBool:[sender state] forKey:LOG_ACTIVITY];
[[NSUserDefaults standardUserDefaults] setBool:[sender state] forKey:PREF_LOG_ACTIVITY];
}
//set 'automatically check for updates'
else if (sender == self.check4Updates)
{
//set
[[NSUserDefaults standardUserDefaults] setBool:[sender state] forKey:CHECK_4_UPDATES];
[[NSUserDefaults standardUserDefaults] setBool:[sender state] forKey:PREF_CHECK_4_UPDATES];
}
//save em
[[NSUserDefaults standardUserDefaults] synchronize];
return;
}

View File

@ -581,6 +581,7 @@
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
@ -617,6 +618,7 @@
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;

View File

@ -32,12 +32,6 @@
// ->for status msg to avoid activity indicator
#define FRAME_SHIFT 45
//hotkey 'w'
#define KEYCODE_W 0xD
//hotkey 'q'
#define KEYCODE_Q 0xC
//OS version x
#define OS_MAJOR_VERSION_X 10
@ -79,11 +73,17 @@
//general error URL
#define FATAL_ERROR_URL @"https://objective-see.com/errors.html"
//path to preferences
#define APP_PREFERENCES @"~/Library/Preferences/com.objective-see.OverSight.plist"
//log activity button
#define LOG_ACTIVITY @"logActivity"
#define PREF_LOG_ACTIVITY @"logActivity"
//automatically check for updates button
#define CHECK_4_UPDATES @"check4Updates"
#define PREF_CHECK_4_UPDATES @"check4Updates"
//keycode for 'q'
#define KEYCODE_Q 0x0C
//path to pkill
#define PKILL @"/usr/bin/pkill"