From d9ae15b3bc3b9be1a9edb861b3fa705c549705a9 Mon Sep 17 00:00:00 2001 From: Patrick Wardle Date: Mon, 19 Sep 2016 21:29:40 -1000 Subject: [PATCH] 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 --- Installer/Info.plist | 2 +- LoginItem/AVMonitor.m | 127 ++++++++++++++++++++-------- LoginItem/AppDelegate.m | 20 ++++- LoginItem/StatusBarMenu.m | 46 +++++----- MainApp/AppDelegate.m | 84 +++++++++++++++++- OverSight.xcodeproj/project.pbxproj | 2 + Shared/Consts.h | 16 ++-- 7 files changed, 220 insertions(+), 77 deletions(-) diff --git a/Installer/Info.plist b/Installer/Info.plist index 5f54644..cd8dc2a 100644 --- a/Installer/Info.plist +++ b/Installer/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion en CFBundleExecutable - $(EXECUTABLE_NAME) + $(PRODUCT_NAME) CFBundleIconFile CFBundleIdentifier diff --git a/LoginItem/AVMonitor.m b/LoginItem/AVMonitor.m index 3ede48f..a1fe430 100644 --- a/LoginItem/AVMonitor.m +++ b/LoginItem/AVMonitor.m @@ -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]; diff --git a/LoginItem/AppDelegate.m b/LoginItem/AppDelegate.m index 8aef684..42aeef7 100644 --- a/LoginItem/AppDelegate.m +++ b/LoginItem/AppDelegate.m @@ -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 diff --git a/LoginItem/StatusBarMenu.m b/LoginItem/StatusBarMenu.m index 8a629ff..71abfcd 100644 --- a/LoginItem/StatusBarMenu.m +++ b/LoginItem/StatusBarMenu.m @@ -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; } diff --git a/MainApp/AppDelegate.m b/MainApp/AppDelegate.m index 598a795..ad7311e 100644 --- a/MainApp/AppDelegate.m +++ b/MainApp/AppDelegate.m @@ -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; } diff --git a/OverSight.xcodeproj/project.pbxproj b/OverSight.xcodeproj/project.pbxproj index 27dec0d..e50ab5a 100644 --- a/OverSight.xcodeproj/project.pbxproj +++ b/OverSight.xcodeproj/project.pbxproj @@ -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; diff --git a/Shared/Consts.h b/Shared/Consts.h index 623c297..92faf23 100644 --- a/Shared/Consts.h +++ b/Shared/Consts.h @@ -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"