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> <key>CFBundleDevelopmentRegion</key>
<string>en</string> <string>en</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(PRODUCT_NAME)</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string></string> <string></string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

View File

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

View File

@ -28,6 +28,9 @@
// ->load status bar and kick off monitor // ->load status bar and kick off monitor
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification -(void)applicationDidFinishLaunching:(NSNotification *)aNotification
{ {
//default
NSDictionary* preferences = nil;
//dbg msg //dbg msg
logMsg(LOG_DEBUG, @"starting login item"); logMsg(LOG_DEBUG, @"starting login item");
@ -37,9 +40,23 @@
//dbg msg //dbg msg
logMsg(LOG_DEBUG, @"initialized/loaded status bar (icon/menu)"); 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 //check for updates
// ->but only when user has not disabled that feature // ->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 //after a minute
//->check for updates in background //->check for updates in background
@ -51,7 +68,6 @@
//check //check
[self isThereAndUpdate]; [self isThereAndUpdate];
}); });
} }
//create/init av event monitor //create/init av event monitor

View File

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

View File

@ -46,10 +46,14 @@
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification -(void)applicationDidFinishLaunching:(NSNotification *)aNotification
{ {
//set 'log activity' button state //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 //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; return;
} }
@ -60,6 +64,75 @@
return YES; 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 //toggle/set preferences
-(IBAction)togglePreference:(NSButton *)sender -(IBAction)togglePreference:(NSButton *)sender
{ {
@ -67,16 +140,19 @@
if(sender == self.logActivity) if(sender == self.logActivity)
{ {
//set //set
[[NSUserDefaults standardUserDefaults] setBool:[sender state] forKey:LOG_ACTIVITY]; [[NSUserDefaults standardUserDefaults] setBool:[sender state] forKey:PREF_LOG_ACTIVITY];
} }
//set 'automatically check for updates' //set 'automatically check for updates'
else if (sender == self.check4Updates) else if (sender == self.check4Updates)
{ {
//set //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; return;
} }

View File

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

View File

@ -32,12 +32,6 @@
// ->for status msg to avoid activity indicator // ->for status msg to avoid activity indicator
#define FRAME_SHIFT 45 #define FRAME_SHIFT 45
//hotkey 'w'
#define KEYCODE_W 0xD
//hotkey 'q'
#define KEYCODE_Q 0xC
//OS version x //OS version x
#define OS_MAJOR_VERSION_X 10 #define OS_MAJOR_VERSION_X 10
@ -79,11 +73,17 @@
//general error URL //general error URL
#define FATAL_ERROR_URL @"https://objective-see.com/errors.html" #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 //log activity button
#define LOG_ACTIVITY @"logActivity" #define PREF_LOG_ACTIVITY @"logActivity"
//automatically check for updates button //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 //path to pkill
#define PKILL @"/usr/bin/pkill" #define PKILL @"/usr/bin/pkill"