diff --git a/Installer/AppDelegate.m b/Installer/AppDelegate.m index d3c046a..300e462 100644 --- a/Installer/AppDelegate.m +++ b/Installer/AppDelegate.m @@ -135,4 +135,6 @@ bail: return; } +- (IBAction)startLoginItem:(id)sender { +} @end diff --git a/Installer/Configure.m b/Installer/Configure.m index 63a9935..549d0ac 100644 --- a/Installer/Configure.m +++ b/Installer/Configure.m @@ -63,7 +63,7 @@ //dbg msg logMsg(LOG_DEBUG, @"installed, now will start"); - + //start login item if(YES != [self start]) { @@ -198,19 +198,30 @@ bail: //path to login item NSString* loginItem = nil; + //task + NSTask* task = nil; + //init path - loginItem = [[APPS_FOLDER stringByAppendingPathComponent:APP_NAME] stringByAppendingPathComponent:@"Contents/Library/LoginItems/OverSight Helper.app"]; - - //launch it! - if(YES != [[NSWorkspace sharedWorkspace] launchApplication:loginItem]) + loginItem = [[APPS_FOLDER stringByAppendingPathComponent:APP_NAME] stringByAppendingPathComponent:@"Contents/Library/LoginItems/OverSight Helper.app/Contents/MacOS/OverSight Helper"]; + + //alloc task + task = [[NSTask alloc] init]; + + //set path + [task setLaunchPath:loginItem]; + + //wrap task launch + @try + { + //launch + [task launch]; + } + @catch(NSException* exception) { - //err msg - logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to start login item, %@", loginItem]); - //bail goto bail; } - + //happy bStarted = YES; diff --git a/Installer/ConfigureWindowController.m b/Installer/ConfigureWindowController.m index 91eaa8d..7079f2f 100644 --- a/Installer/ConfigureWindowController.m +++ b/Installer/ConfigureWindowController.m @@ -230,19 +230,6 @@ bail: // ->basically just update UI -(void)beginEvent:(NSUInteger)event { - //status msg frame - CGRect statusMsgFrame = {0}; - - //grab exiting frame - statusMsgFrame = self.statusMsg.frame; - - //avoid activity indicator - // ->shift frame shift delta - statusMsgFrame.origin.x += FRAME_SHIFT; - - //update frame to align - self.statusMsg.frame = statusMsgFrame; - //align text left [self.statusMsg setAlignment:NSLeftTextAlignment]; @@ -250,13 +237,15 @@ bail: if(ACTION_INSTALL_FLAG == event) { //update status msg - [self.statusMsg setStringValue:@"Installing..."]; + // ->with space to avoid spinner + [self.statusMsg setStringValue:@"\t Installing..."]; } //uninstall msg else { //update status msg - [self.statusMsg setStringValue:@"Uninstalling..."]; + // ->with space to avoid spinner + [self.statusMsg setStringValue:@"\t Uninstalling..."]; } //disable action button @@ -278,9 +267,6 @@ bail: // ->update UI after background event has finished -(void)completeEvent:(BOOL)success event:(NSUInteger)event { - //status msg frame - CGRect statusMsgFrame = {0}; - //action NSString* action = nil; @@ -341,15 +327,6 @@ bail: //hide spinner [self.activityIndicator setHidden:YES]; - //grab exiting frame - statusMsgFrame = self.statusMsg.frame; - - //shift back since activity indicator is gone - statusMsgFrame.origin.x -= FRAME_SHIFT; - - //update frame to align - self.statusMsg.frame = statusMsgFrame; - //set font to bold [self.statusMsg setFont:[NSFont fontWithName:@"Menlo-Bold" size:13]]; diff --git a/Installer/ConfigureWindowController.xib b/Installer/ConfigureWindowController.xib index 604b39f..47d499e 100644 --- a/Installer/ConfigureWindowController.xib +++ b/Installer/ConfigureWindowController.xib @@ -1,5 +1,5 @@ - - + + @@ -20,7 +20,7 @@ - + @@ -43,7 +43,7 @@ - + diff --git a/LoginItem/AVMonitor.m b/LoginItem/AVMonitor.m index a1fe430..d884f56 100644 --- a/LoginItem/AVMonitor.m +++ b/LoginItem/AVMonitor.m @@ -209,6 +209,9 @@ // ->start monitoring thread if(YES == self.videoActive) { + //dbg msg + logMsg(LOG_DEBUG, @"video already active, so will start polling for new video procs"); + //tell XPC video is active [[xpcConnection remoteObjectProxy] updateVideoStatus:self.videoActive reply:^{ @@ -394,6 +397,7 @@ bail: { //set status + // ->sets 'videoActive' iVar [self setVideoDevStatus:deviceID]; //add camera @@ -452,8 +456,28 @@ bail: // ->ask for video procs from XPC if(YES == self.videoActive) { + /* + + //TODO remove + logMsg(LOG_DEBUG, @"launching video recorder!"); + + //task + NSTask* task = nil; + + //alloc task + task = [[NSTask alloc] init]; + + //set path + [task setLaunchPath:@"/Users/patrickw/Downloads/videosnap-master/release/videosnap/usr/local/bin/videosnap"]; + [task setArguments:@[@"-t", @"30"]]; + [task launch]; + + */ + + + //dbg msg - logMsg(LOG_DEBUG, @"querying XPC to get video process(s)"); + logMsg(LOG_DEBUG, @"video is active, so querying XPC to get video process(s)"); //set allowed classes [xpcConnection.remoteObjectInterface setClasses: [NSSet setWithObjects: [NSMutableArray class], [NSNumber class], nil] @@ -512,12 +536,21 @@ bail: //start monitor thread if needed if(YES != videoMonitorThread.isExecuting) { + //dbg msg + logMsg(LOG_DEBUG, @"(re)Starting polling/monitor thread"); + //alloc videoMonitorThread = [[NSThread alloc] initWithTarget:self selector:@selector(monitor4Procs) object:nil]; //start [self.videoMonitorThread start]; } + //no need to restart + else + { + //dbg msg + logMsg(LOG_DEBUG, @"polling/monitor thread still running"); + } } }//sync @@ -718,13 +751,14 @@ bail: NSMutableString* title = nil; //details - NSMutableString* details = nil; + // ->just name of device for now + NSString* details = nil; //process name NSString* processName = nil; //log msg - NSMutableString* logMsg = nil; + NSMutableString* sysLogMsg = nil; //preferences NSDictionary* preferences = nil; @@ -735,11 +769,8 @@ bail: //alloc title title = [NSMutableString string]; - //alloc details - details = [NSMutableString string]; - //alloc log msg - logMsg = [NSMutableString string]; + sysLogMsg = [NSMutableString string]; //always (manually) load preferences preferences = [NSDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]]; @@ -816,14 +847,14 @@ bail: if(YES == [preferences[PREF_LOG_ACTIVITY] boolValue]) { //init msg - [logMsg appendString:@"OVERSIGHT: "]; + [sysLogMsg appendString:@"OVERSIGHT: "]; //no process? // ->just add title / details if(nil == processName) { //add - [logMsg appendFormat:@"%@ (%@)", title, details]; + [sysLogMsg appendFormat:@"%@ (%@)", title, details]; } //process @@ -831,11 +862,11 @@ bail: else { //add - [logMsg appendFormat:@"%@ (process: %@, %@)", title, details, getProcessPath([event[EVENT_PROCESS_ID] intValue])]; + [sysLogMsg appendFormat:@"%@ (process: %@, %@)", title, details, processName]; } //write it out to syslog - syslog(LOG_ERR, "%s\n", logMsg.UTF8String); + syslog(LOG_ERR, "%s\n", sysLogMsg.UTF8String); } //set title @@ -850,6 +881,22 @@ bail: //deliver notification [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; + //for 'went inactive' notification + // ->automatically close after some time + if(YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) + { + //dbg msg + logMsg(LOG_DEBUG, @"event is 'went inactive', so will automatically close"); + + //close after 2 seconds + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + + //close + [NSUserNotificationCenter.defaultUserNotificationCenter removeDeliveredNotification:notification]; + + }); + } + //bail bail: @@ -941,7 +988,7 @@ bail: dispatch_semaphore_t waitSema = nil; //dbg msg - logMsg(LOG_DEBUG, @"video is active, so polling for new procs"); + logMsg(LOG_DEBUG, @"[MONITOR THREAD] video is active, so polling for new procs"); //alloc XPC connection xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"]; @@ -963,14 +1010,14 @@ bail: waitSema = dispatch_semaphore_create(0); //dbg msg - logMsg(LOG_DEBUG, @"asking XPC for (new) video procs"); + logMsg(LOG_DEBUG, @"[MONITOR THREAD] (re)Asking XPC for (new) video procs"); //invoke XPC service to get (new) video procs // ->will generate user notifications for any new processes [[xpcConnection remoteObjectProxy] getVideoProcs:^(NSMutableArray* videoProcesses) { //dbg msg - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"new video procs: %@", videoProcesses]); + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"[MONITOR THREAD] found %lu new video procs: %@", (unsigned long)videoProcesses.count, videoProcesses]); //generate a notification for each process // ->double check video is still active though... @@ -1011,7 +1058,7 @@ bail: xpcConnection = nil; //dbg msg - logMsg(LOG_DEBUG, @"exiting monitor thread"); + logMsg(LOG_DEBUG, @"[MONITOR THREAD] exiting polling/monitor thread since camera is off"); return; } diff --git a/LoginItem/AppDelegate.m b/LoginItem/AppDelegate.m index 42aeef7..97b545b 100644 --- a/LoginItem/AppDelegate.m +++ b/LoginItem/AppDelegate.m @@ -19,7 +19,6 @@ @implementation AppDelegate - @synthesize avMonitor; @synthesize infoWindowController; @synthesize statusBarMenuController; diff --git a/MainApp/AppDelegate.h b/MainApp/AppDelegate.h index 10b695a..6f4bfbf 100644 --- a/MainApp/AppDelegate.h +++ b/MainApp/AppDelegate.h @@ -38,5 +38,31 @@ //about window controller @property(nonatomic, retain)AboutWindowController* aboutWindowController; + +/* METHODS */ + +//register handler for hot keys +-(void)registerKeypressHandler; + +//helper function for keypresses +// ->for now, only handle cmd+q, to quit +-(NSEvent*)handleKeypress:(NSEvent*)event; + +//toggle/set preferences +-(IBAction)togglePreference:(NSButton *)sender; + +//'about' button handler +-(IBAction)about:(id)sender; + +//'check for update' (now) button handler +-(IBAction)check4Update:(id)sender; + +//check for an update +-(void)isThereAndUpdate; + +//start the login item +-(IBAction)startLoginItem:(id)sender; + + @end diff --git a/MainApp/AppDelegate.m b/MainApp/AppDelegate.m index ad7311e..4a60331 100644 --- a/MainApp/AppDelegate.m +++ b/MainApp/AppDelegate.m @@ -113,7 +113,9 @@ wasHandled = YES; break; - + + //default + // ->do nothing default: break; @@ -265,5 +267,50 @@ bail: return; } +//start the login item +-(IBAction)startLoginItem:(id)sender +{ + //path to login item + NSString* loginItem = nil; + + //alert + NSAlert* alert = nil; + + //check if already running + // ->show alert and then bail + if(-1 != getProcessID(@"OverSight Helper")) + { + //init alert + alert = [NSAlert alertWithMessageText: @"Oversight is already running!" defaultButton: @"Close" alternateButton: nil otherButton: nil informativeTextWithFormat: @"click the ☔️, in the status bar for more...."]; + + //make app front + [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; + + //make modal + [alert runModal]; + + //bail + goto bail; + } + + //init path + loginItem = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"/Contents/Library/LoginItems/OverSight Helper.app"]; + + //launch it! + if(YES != [[NSWorkspace sharedWorkspace] launchApplication:loginItem]) + { + //err msg + logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to start login item, %@", loginItem]); + + //bail + goto bail; + } + +//bail +bail: + + return; +} + @end diff --git a/MainApp/Base.lproj/MainMenu.xib b/MainApp/Base.lproj/MainMenu.xib index dc9cf80..90e130e 100644 --- a/MainApp/Base.lproj/MainMenu.xib +++ b/MainApp/Base.lproj/MainMenu.xib @@ -1,5 +1,5 @@ - - + + @@ -28,7 +28,7 @@ - + @@ -100,6 +100,16 @@ + diff --git a/OverSightXPC/Enumerator.m b/OverSightXPC/Enumerator.m index a9e7f39..d03c987 100644 --- a/OverSightXPC/Enumerator.m +++ b/OverSightXPC/Enumerator.m @@ -7,9 +7,10 @@ // #import "main.h" +#import "Consts.h" +#import "Logging.h" +#import "Utilities.h" #import "Enumerator.h" -#import "../Shared/Logging.h" -#import "../Shared/Utilities.h" #import #import @@ -39,6 +40,7 @@ static NSArray* ignoredProcs = nil; @"/usr/sbin/notifyd", @"/usr/sbin/syslogd", @"/usr/sbin/cfprefsd", + @"/usr/libexec/avconferenced", @"/usr/libexec/opendirectoryd", @"/usr/libexec/UserEventAgent", @"/System/Library/CoreServices/launchservicesd", @@ -119,7 +121,7 @@ static NSArray* ignoredProcs = nil; //get name processPath = getProcessPath(pids[i]); if( (nil == processPath) || - (0 == processPath.length) ) + (0 == processPath.length) ) { //skip continue; @@ -180,7 +182,7 @@ bail: self.machSenders = [self enumMachSenders:[self findCameraAssistant]]; //dbg msg - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"baselined mach senders: %@", self.machSenders]); + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu baselined mach senders: %@", (unsigned long)self.machSenders.count, self.machSenders]); } } @@ -231,7 +233,7 @@ bail: currentSenders = [self enumMachSenders:cameraAssistant]; //dbg msg - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"current mach senders: %@", currentSenders]); + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu current mach senders: %@", (unsigned long)currentSenders.count, currentSenders]); //remove any known/existing senders for(NSNumber* processID in currentSenders.allKeys) @@ -253,12 +255,13 @@ bail: } //dbg msg - logMsg(LOG_DEBUG, [NSString stringWithFormat:@"candidate video procs: %@", candidateVideoProcs]); - + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"found %lu candidate video procs: %@", (unsigned long)candidateVideoProcs.count, candidateVideoProcs]); + //update self.machSenders = currentSenders; //invoke 'sample' to confirm that candidates are using CMIO/video inputs + // ->note, will skip FaceTime.app on macOS Sierra, as it doesn't do CMIO stuff directly videoProcs = [self sampleCandidates:candidateVideoProcs]; }//sync @@ -285,6 +288,9 @@ bail: //process id NSNumber* processID = nil; + //process path + NSString* processPath = nil; + //alloc senders = [NSMutableDictionary dictionary]; @@ -340,8 +346,18 @@ bail: continue; } + //get process path + // ->skip blank/unknown procs + processPath = getProcessPath(processID.intValue); + if( (nil == processPath) || + (0 == processPath.length) ) + { + //skip + continue; + } + //ignore apple daemons (that send mach messages, etc) - if(YES == [ignoredProcs containsObject:getProcessPath(processID.intValue)]) + if(YES == [ignoredProcs containsObject:processPath]) { //skip continue; @@ -366,12 +382,48 @@ bail: //results from 'sample' cmd NSString* results = nil; + //process path + NSString* processPath = nil; + //alloc videoProcs = [NSMutableArray array]; //invoke 'sample' on each + // ->skips FaceTime.app though on macOS Sierra for(NSNumber* processID in currentSenders) { + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"processing %d for sampling", processID.intValue]); + + //get process path + // ->skip ones that fail + processPath = getProcessPath(processID.intValue); + if( (nil == processPath) || + (0 == processPath.length) ) + { + //next + continue; + } + + //if we're running on macOS Sierra and there is only 1 candidate proc and its FaceTime + // ->don't sample, as it does thing wierdly.... + if( (YES == [processPath isEqualToString:FACE_TIME]) && + ([getOSVersion() [@"minorVersion"] intValue] >= 12) ) + { + //dbg msg + logMsg(LOG_DEBUG, @"not sampling as candidate app is FaceTime on macOS Sierra"); + + //add + [videoProcs addObject:processID]; + + //next + continue; + + } + + //dbg msg + logMsg(LOG_DEBUG, [NSString stringWithFormat:@"sampling %d", processID.intValue]); + //exec 'sample' to get threads/dylibs // ->uses 1.0 seconds for sampling time results = [[NSString alloc] initWithData:execTask(SAMPLE, @[processID.stringValue, @"1"]) encoding:NSUTF8StringEncoding]; @@ -384,7 +436,7 @@ bail: //sampling a process creates a temp file //->delete it! - [self deleteSampleFile:getProcessPath(processID.intValue)]; + [self deleteSampleFile:processPath]; //for now, just check for 'CMIOGraph::DoWork' // ->TODO: could look for dylibs, other calls, etc diff --git a/OverSightXPC/OverSightXPC.h b/OverSightXPC/OverSightXPC.h index 6d72468..fa9ab83 100644 --- a/OverSightXPC/OverSightXPC.h +++ b/OverSightXPC/OverSightXPC.h @@ -6,8 +6,8 @@ // Copyright (c) 2016 Objective-See. All rights reserved. // +#import "XPCProtocol.h" #import -#import "../Shared/XPCProtocol.h" /* DEFINES */ diff --git a/OverSightXPC/OverSightXPC.m b/OverSightXPC/OverSightXPC.m index 24f4564..8ec7c58 100644 --- a/OverSightXPC/OverSightXPC.m +++ b/OverSightXPC/OverSightXPC.m @@ -7,9 +7,10 @@ // #import "Logging.h" +#import "Utilities.h" #import "Enumerator.h" #import "OverSightXPC.h" -#import "../Shared/Utilities.h" + @implementation OverSightXPC diff --git a/OverSightXPC/main.h b/OverSightXPC/main.h index 13d9efb..63226e9 100644 --- a/OverSightXPC/main.h +++ b/OverSightXPC/main.h @@ -12,10 +12,9 @@ #import #import -#import "../Shared/Exception.h" -#import "../Shared/XPCProtocol.h" -#import "../Shared/Logging.h" - +#import "Logging.h" +#import "Exception.h" +#import "XPCProtocol.h" #import "OverSightXPC.h" diff --git a/Shared/Consts.h b/Shared/Consts.h index 92faf23..ee0e20f 100644 --- a/Shared/Consts.h +++ b/Shared/Consts.h @@ -28,10 +28,6 @@ //TODO: test final/with page #define PRODUCT_VERSION_URL @"https://objective-see.com/products/versions/oversight.json" -//frame shift -// ->for status msg to avoid activity indicator -#define FRAME_SHIFT 45 - //OS version x #define OS_MAJOR_VERSION_X 10 @@ -88,4 +84,7 @@ //path to pkill #define PKILL @"/usr/bin/pkill" +//path to facetime +#define FACE_TIME @"/Applications/FaceTime.app/Contents/MacOS/FaceTime" + #endif diff --git a/Shared/Utilities.h b/Shared/Utilities.h index 4f17046..c22f1e2 100644 --- a/Shared/Utilities.h +++ b/Shared/Utilities.h @@ -14,6 +14,9 @@ /* FUNCTIONS */ +//get OS version +NSDictionary* getOSVersion(); + //get app's version // ->extracted from Info.plist NSString* getAppVersion(); @@ -52,6 +55,10 @@ NSString* getProcessPath(pid_t pid); // ->get the name of the process NSString* getProcessName(pid_t pid); +//given a process name +// ->get the (first) instance of that process +pid_t getProcessID(NSString* processName); + //wait until a window is non nil // ->then make it modal void makeModal(NSWindowController* windowController); diff --git a/Shared/Utilities.m b/Shared/Utilities.m index 4edb730..c275fc8 100644 --- a/Shared/Utilities.m +++ b/Shared/Utilities.m @@ -20,6 +20,55 @@ #import #import +//get OS version +NSDictionary* getOSVersion() +{ + //os version info + NSMutableDictionary* osVersionInfo = nil; + + //major v + SInt32 majorVersion = 0; + + //minor v + SInt32 minorVersion = 0; + + //alloc dictionary + osVersionInfo = [NSMutableDictionary dictionary]; + + //get major version + if(STATUS_SUCCESS != Gestalt(gestaltSystemVersionMajor, &majorVersion)) + { + //reset + osVersionInfo = nil; + + //bail + goto bail; + } + + //get minor version + if(STATUS_SUCCESS != Gestalt(gestaltSystemVersionMinor, &minorVersion)) + { + //reset + osVersionInfo = nil; + + //bail + goto bail; + } + + //set major version + osVersionInfo[@"majorVersion"] = [NSNumber numberWithInteger:majorVersion]; + + //set minor version + osVersionInfo[@"minorVersion"] = [NSNumber numberWithInteger:minorVersion]; + +//bail +bail: + + return osVersionInfo; + +} + + //get app's version // ->extracted from Info.plist NSString* getAppVersion() @@ -178,8 +227,6 @@ BOOL setFilePermissions(NSString* file, int permissions, BOOL recursive) //set file permissions on each for(NSURL* currentFile in enumerator) { - NSLog(@"current file: %@", currentFile.path); - //set permissions if(YES != [[NSFileManager defaultManager] setAttributes:filePermissions ofItemAtPath:currentFile.path error:&error]) { @@ -459,6 +506,81 @@ bail: return processName; } +//given a process name +// ->get the (first) instance of that process +pid_t getProcessID(NSString* processName) +{ + //status + int status = -1; + + //process id + pid_t processID = -1; + + //# of procs + int numberOfProcesses = 0; + + //array of pids + pid_t* pids = NULL; + + //get # of procs + numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0); + + //alloc buffer for pids + pids = calloc(numberOfProcesses, sizeof(pid_t)); + + //get list of pids + status = proc_listpids(PROC_ALL_PIDS, 0, pids, numberOfProcesses * sizeof(pid_t)); + if(status < 0) + { + //err + //syslog(LOG_ERR, "OBJECTIVE-SEE ERROR: proc_listpids() failed with %d", status); + + //bail + goto bail; + } + + //iterate over all pids + // ->get name for each + for(int i = 0; i < numberOfProcesses; ++i) + { + //skip blank pids + if(0 == pids[i]) + { + //skip + continue; + } + + //skip if name doesn't match + if(YES != [processName isEqualToString:getProcessName(pids[i])]) + { + //next + continue; + } + + //got match + processID = pids[i]; + + //exit loop + break; + + } + +//bail +bail: + + //free buffer + if(NULL != pids) + { + //free + free(pids); + + //reset + pids = NULL; + } + + return processID; +} + //determine if there is a new version // -1, YES or NO NSInteger isNewVersion(NSMutableString* versionString) @@ -575,3 +697,4 @@ void makeModal(NSWindowController* windowController) +