diff --git a/Application/Application.xcodeproj/project.pbxproj b/Application/Application.xcodeproj/project.pbxproj index 9c030d4..4b8a790 100644 --- a/Application/Application.xcodeproj/project.pbxproj +++ b/Application/Application.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ CD2F800C24455333009C3D77 /* AboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CD2F800924455333009C3D77 /* AboutWindow.xib */; }; CD2F800D24455333009C3D77 /* AboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CD2F800B24455333009C3D77 /* AboutWindowController.m */; }; CD2F801724468A8C009C3D77 /* patrons.txt in Resources */ = {isa = PBXBuildFile; fileRef = CD2F801624468A8C009C3D77 /* patrons.txt */; }; + CD3052C72AF6D86600250347 /* OverSight Installer.app in Resources */ = {isa = PBXBuildFile; fileRef = CD3052C62AF6D86600250347 /* OverSight Installer.app */; }; CD6836682391DB6F00CF19C1 /* security.plist in Resources */ = {isa = PBXBuildFile; fileRef = CD6836672391DB6F00CF19C1 /* security.plist */; }; CD8FD5D523BAE2D200EFE0FB /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CD8FD5D323BAE2D100EFE0FB /* Preferences.xib */; }; CD8FD5D623BAE2D200EFE0FB /* PrefsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = CD8FD5D423BAE2D200EFE0FB /* PrefsWindowController.m */; }; @@ -33,7 +34,6 @@ CDC60991263CBD36006D1332 /* AVMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = CDC60990263CBD36006D1332 /* AVMonitor.m */; }; CDC60994263CBEE7006D1332 /* Client.m in Sources */ = {isa = PBXBuildFile; fileRef = CDC60993263CBEE7006D1332 /* Client.m */; }; CDCBF0DE26499DFF001D9F9A /* Event.m in Sources */ = {isa = PBXBuildFile; fileRef = CDCBF0DD26499DFF001D9F9A /* Event.m */; }; - CDFFBC572AEBD35E00560604 /* OverSight Installer.app in Resources */ = {isa = PBXBuildFile; fileRef = CDFFBC562AEBD35E00560604 /* OverSight Installer.app */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -76,6 +76,7 @@ CD2F800A24455333009C3D77 /* AboutWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutWindowController.h; sourceTree = ""; }; CD2F800B24455333009C3D77 /* AboutWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutWindowController.m; sourceTree = ""; }; CD2F801624468A8C009C3D77 /* patrons.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = patrons.txt; path = ../Shared/patrons.txt; sourceTree = ""; }; + CD3052C62AF6D86600250347 /* OverSight Installer.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = "OverSight Installer.app"; sourceTree = ""; }; CD6836672391DB6F00CF19C1 /* security.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = security.plist; sourceTree = ""; }; CD8FD5D223BAE2D100EFE0FB /* PrefsWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrefsWindowController.h; sourceTree = ""; }; CD8FD5D323BAE2D100EFE0FB /* Preferences.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = ""; }; @@ -95,7 +96,6 @@ CDC60993263CBEE7006D1332 /* Client.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Client.m; sourceTree = ""; }; CDCBF0DC26499DFF001D9F9A /* Event.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Event.h; sourceTree = ""; }; CDCBF0DD26499DFF001D9F9A /* Event.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Event.m; sourceTree = ""; }; - CDFFBC562AEBD35E00560604 /* OverSight Installer.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = "OverSight Installer.app"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -214,7 +214,7 @@ CDE09AF12919829000561CFF /* Uninstaller */ = { isa = PBXGroup; children = ( - CDFFBC562AEBD35E00560604 /* OverSight Installer.app */, + CD3052C62AF6D86600250347 /* OverSight Installer.app */, ); path = Uninstaller; sourceTree = ""; @@ -292,7 +292,7 @@ buildActionMask = 2147483647; files = ( CD8FD5D523BAE2D200EFE0FB /* Preferences.xib in Resources */, - CDFFBC572AEBD35E00560604 /* OverSight Installer.app in Resources */, + CD3052C72AF6D86600250347 /* OverSight Installer.app in Resources */, CD6836682391DB6F00CF19C1 /* security.plist in Resources */, CD2F800C24455333009C3D77 /* AboutWindow.xib in Resources */, 7D16D6951F64E43300DB3161 /* UpdateWindow.xib in Resources */, @@ -465,7 +465,7 @@ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO; CODE_SIGN_IDENTITY = "Developer ID Application"; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2.1.8; + CURRENT_PROJECT_VERSION = 2.2.0; DEVELOPMENT_TEAM = VBG97UB4TA; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac"; @@ -476,7 +476,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(LD_RUNPATH_SEARCH_PATHS_$(IS_MACCATALYST)) @executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ""; MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 2.1.8; + MARKETING_VERSION = 2.2.0; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.oversight"; PRODUCT_NAME = OverSight; @@ -491,7 +491,7 @@ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO; CODE_SIGN_IDENTITY = "Developer ID Application"; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2.1.8; + CURRENT_PROJECT_VERSION = 2.2.0; DEVELOPMENT_TEAM = VBG97UB4TA; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac"; @@ -502,7 +502,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(LD_RUNPATH_SEARCH_PATHS_$(IS_MACCATALYST)) @executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ""; MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 2.1.8; + MARKETING_VERSION = 2.2.0; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.oversight"; PRODUCT_NAME = OverSight; diff --git a/Application/Application/AVMonitor.h b/Application/Application/AVMonitor.h index f449a47..e7595b3 100644 --- a/Application/Application/AVMonitor.h +++ b/Application/Application/AVMonitor.h @@ -43,15 +43,18 @@ //initial camera state @property NSControlStateValue initialCameraState; -//last (camera) pid +//last camera client @property NSInteger lastCameraClient; -//last mic off -@property(nonatomic, retain)AVCaptureDevice* lastMicOff; - //last camera off @property(nonatomic, retain)AVCaptureDevice* lastCameraOff; +//last mic client +@property NSInteger lastMicClient; + +//last mic off +@property(nonatomic, retain)AVCaptureDevice* lastMicOff; + //audio listeners @property(nonatomic, retain)NSMutableDictionary* audioListeners; @@ -61,18 +64,6 @@ //per device events @property(nonatomic, retain)NSMutableDictionary* deviceEvents; -//audio event queue -@property(nonatomic, retain)dispatch_queue_t audioEventQueue; - -//audio event timer -@property(nonatomic, retain)dispatch_source_t audioEventTimer; - -//camera event queue -@property(nonatomic, retain)dispatch_queue_t cameraEventQueue; - -//camera event timer -@property(nonatomic, retain)dispatch_source_t cameraEventTimer; - //last alert (default) interaction @property(nonatomic, retain)NSDate* lastNotificationDefaultAction; diff --git a/Application/Application/AVMonitor.m b/Application/Application/AVMonitor.m index a915941..fd05605 100644 --- a/Application/Application/AVMonitor.m +++ b/Application/Application/AVMonitor.m @@ -89,12 +89,6 @@ extern os_log_t logHandle; //per device events self.deviceEvents = [NSMutableDictionary dictionary]; - //init audio event queue - self.audioEventQueue = dispatch_queue_create("audio.event.timer", 0); - - //init camera event queue - self.cameraEventQueue = dispatch_queue_create("camera.event.timer", 0); - //enumerate active devices // then update status menu (on main thread) dispatch_async(dispatch_get_main_queue(), ^{ @@ -115,7 +109,6 @@ extern os_log_t logHandle; //dbg msg os_log_debug(logHandle, "built-in camera: %{public}@ (device ID: %d)", self.builtInCamera.localizedName, [self getAVObjectID:self.builtInCamera]); - } return self; @@ -157,53 +150,97 @@ extern os_log_t logHandle; //macOS 14+ if(@available(macOS 14.0, *)) { - //regex - NSRegularExpression* regex = nil; + //regex for mic log msg + NSRegularExpression* micRegex = nil; + + //regex for camera log msg + NSRegularExpression* cameraRegex = nil; //dbg msg - os_log_debug(logHandle, ">= macOS 14+: Using log monitor for AV events via w/ 'added endpoint camera '"); + os_log_debug(logHandle, ">= macOS 14+: Using log monitor for AV events via w/ (camera): 'added endpoint camera ' AND (mic): '-[MXCoreSession beginInterruption]: Session endpoint camera = ;" - if(YES != [logEvent.composedMessage hasSuffix:@"added endpoint camera "]) + if( (YES == [logEvent.subsystem isEqual:@"com.apple.cmio"]) && + (YES == [logEvent.composedMessage hasSuffix:@"added endpoint camera "]) ) + { + //reset + self.lastCameraClient = 0; + + //match on pid + match = [cameraRegex firstMatchInString:logEvent.composedMessage options:0 range:NSMakeRange(0, logEvent.composedMessage.length)]; + if( (nil == match) || + (NSNotFound == match.range.location) ) + { + return; + } + + //extract/convert pid + pid = [[logEvent.composedMessage substringWithRange:[match rangeAtIndex:1]] integerValue]; + if( (0 == pid) || + (-1 == pid) ) + { + return; + } + + //save + self.lastCameraClient = pid; + } + + //mic: + // "-[MXCoreSession beginInterruption]: Session is going active"]) ) + { + + //reset + self.lastMicClient = 0; + + //match on pid + match = [micRegex firstMatchInString:logEvent.composedMessage options:0 range:NSMakeRange(0, logEvent.composedMessage.length)]; + if( (nil == match) || + (NSNotFound == match.range.location) ) + { + return; + } + + //extract/convert pid + pid = [[logEvent.composedMessage substringWithRange:[match rangeAtIndex:1]] integerValue]; + if( (0 == pid) || + (-1 == pid) ) + { + return; + } + + //save + self.lastMicClient = pid; + } + + //msg not of interest + else { return; } - - //match on pid - match = [regex firstMatchInString:logEvent.composedMessage options:0 range:NSMakeRange(0, logEvent.composedMessage.length)]; - if( (nil == match) || - (NSNotFound == match.range.location) ) - { - return; - } - - //extract/convert pid - pid = [[logEvent.composedMessage substringWithRange:[match rangeAtIndex:1]] integerValue]; - if( (0 == pid) || - (-1 == pid) ) - { - return; - } - - //save - self.lastCameraClient = pid; - + //(re)enumerate active devices // delayed need as device deactiavation // then update status menu (on main thread) @@ -218,6 +255,7 @@ extern os_log_t logHandle; }); }); //dispatch for delay + } }]; @@ -232,7 +270,7 @@ extern os_log_t logHandle; NSRegularExpression* regex = nil; //dbg msg - os_log_debug(logHandle, ">= macOS 13.3+: uUsing 'CMIOExtensionPropertyDeviceControlPID'"); + os_log_debug(logHandle, ">= macOS 13.3+: Using 'CMIOExtensionPropertyDeviceControlPID'"); //init regex regex = [NSRegularExpression regularExpressionWithPattern:@"=\\s*(\\d+)\\s*;" options:0 error:nil]; @@ -255,6 +293,9 @@ extern os_log_t logHandle; return; } + //reset + self.lastCameraClient = 0; + //match on pid match = [regex firstMatchInString:logEvent.composedMessage options:0 range:NSMakeRange(0, logEvent.composedMessage.length)]; if( (nil == match) || @@ -294,7 +335,7 @@ extern os_log_t logHandle; } - //previous versions of macoS + //previous versions of macOS // use predicate: "subsystem=='com.apple.SystemStatus'" else { @@ -518,97 +559,78 @@ extern os_log_t logHandle; //dbg msg os_log_debug(logHandle, "new audio event"); - //cancel prev timer - if(nil != self.audioEventTimer) + //active mic + AVCaptureDevice* activeMic = nil; + + //audio off? + // sent event + if(0 == audioDifferences.insertions.count) { - //cancel - dispatch_source_cancel(self.audioEventTimer); - self.audioEventTimer = nil; + //dbg msg + os_log_debug(logHandle, "audio event: off"); + + //init event + // process (client) and device are nil + event = [[Event alloc] init:nil device:nil deviceType:Device_Microphone state:NSControlStateValueOff]; + + //handle event + [self handleEvent:event]; } - //re-init timer - self.audioEventTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.audioEventQueue); - dispatch_source_set_timer(self.audioEventTimer, dispatch_walltime(NULL, 1.0 * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0.1 * NSEC_PER_SEC); - - //set handler - dispatch_source_set_event_handler(self.audioEventTimer, ^{ + //audio on? + // send event + else + { + //dbg msg + os_log_debug(logHandle, "audio event: on"); - //active mic - AVCaptureDevice* activeMic = nil; - - //audio off? - // sent event - if(0 == audioDifferences.insertions.count) + //send event for each process (attribution) + for(NSOrderedCollectionChange* audioAttribution in audioDifferences.insertions) { - //dbg msg - os_log_debug(logHandle, "audio event: off"); + //init client from attribution + client = [[Client alloc] init]; + client.pid = audioAttribution.object; + client.path = valueForStringItem(getProcessPath(client.pid.intValue)); + client.name = valueForStringItem(getProcessName(client.path)); - //init event - // process (client) and device are nil - event = [[Event alloc] init:nil device:nil deviceType:Device_Microphone state:NSControlStateValueOff]; - - //handle event - [self handleEvent:event]; - } - - //audio on? - // send event - else - { - //dbg msg - os_log_debug(logHandle, "audio event: on"); - - //send event for each process (attribution) - for(NSOrderedCollectionChange* audioAttribution in audioDifferences.insertions) + //look for active mic + for(AVCaptureDevice* microphone in [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio]) { - //init client from attribution - client = [[Client alloc] init]; - client.pid = audioAttribution.object; - client.path = valueForStringItem(getProcessPath(client.pid.intValue)); - client.name = valueForStringItem(getProcessName(client.path)); - - //look for active mic - for(AVCaptureDevice* microphone in [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio]) + //off? skip + if(NSControlStateValueOn != [self getMicState:microphone]) { - //off? skip - if(NSControlStateValueOn != [self getMicState:microphone]) - { - //skip - continue; - } - - //dbg msg - os_log_debug(logHandle, "audio device: %{public}@/%{public}@ is on", microphone.manufacturer, microphone.localizedName); - - //save - activeMic = microphone; - - //init event - // with client and (active) mic - event = [[Event alloc] init:client device:activeMic deviceType:Device_Microphone state:NSControlStateValueOn]; - - //handle event - [self handleEvent:event]; + //skip + continue; } - //no mic found? (e.g. headphones as input) - // show (limited) alert - if(nil == activeMic) - { - //init event - // devivce is nil - event = [[Event alloc] init:client device:nil deviceType:Device_Microphone state:NSControlStateValueOn]; - - //handle event - [self handleEvent:event]; - } + //dbg msg + os_log_debug(logHandle, "audio device: %{public}@/%{public}@ is on", microphone.manufacturer, microphone.localizedName); + + //save + activeMic = microphone; + + //init event + // with client and (active) mic + event = [[Event alloc] init:client device:activeMic deviceType:Device_Microphone state:NSControlStateValueOn]; + + //handle event + [self handleEvent:event]; + } + + //no mic found? (e.g. headphones as input) + // show (limited) alert + if(nil == activeMic) + { + //init event + // devivce is nil + event = [[Event alloc] init:client device:nil deviceType:Device_Microphone state:NSControlStateValueOn]; + + //handle event + [self handleEvent:event]; } } - }); - - //start audio event timer - dispatch_resume(self.audioEventTimer); - + } + } //audio event /* camera event logic */ @@ -620,104 +642,85 @@ extern os_log_t logHandle; //dbg msg os_log_debug(logHandle, "new camera event"); - //cancel prev timer - if(nil != self.cameraEventTimer) + //active camera + AVCaptureDevice* activeCamera = nil; + + //camera off? + // send event + if(0 == cameraDifferences.insertions.count) { - //cancel - dispatch_source_cancel(self.cameraEventTimer); - self.cameraEventTimer = nil; + //dbg msg + os_log_debug(logHandle, "camera event: off"); + + //init event + // process (client) and device are nil + event = [[Event alloc] init:nil device:nil deviceType:Device_Camera state:NSControlStateValueOff]; + + //handle event + [self handleEvent:event]; } - //re-init timer - self.cameraEventTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.cameraEventQueue); - dispatch_source_set_timer(self.cameraEventTimer, dispatch_walltime(NULL, 1.0 * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0.1 * NSEC_PER_SEC); - - //set handler - dispatch_source_set_event_handler(self.cameraEventTimer, ^{ + //camera on? + // send event + else + { + //dbg msg + os_log_debug(logHandle, "camera event: on"); - //active camera - AVCaptureDevice* activeCamera = nil; - - //camera off? - // send event - if(0 == cameraDifferences.insertions.count) + //send event for each process (attribution) + for(NSOrderedCollectionChange* cameraAttribution in cameraDifferences.insertions) { - //dbg msg - os_log_debug(logHandle, "camera event: off"); + //init client from attribution + client = [[Client alloc] init]; + client.pid = cameraAttribution.object; + client.path = valueForStringItem(getProcessPath(client.pid.intValue)); + client.name = valueForStringItem(getProcessName(client.path)); - //init event - // process (client) and device are nil - event = [[Event alloc] init:nil device:nil deviceType:Device_Camera state:NSControlStateValueOff]; - - //handle event - [self handleEvent:event]; - } - - //camera on? - // send event - else - { - //dbg msg - os_log_debug(logHandle, "camera event: on"); - - //send event for each process (attribution) - for(NSOrderedCollectionChange* cameraAttribution in cameraDifferences.insertions) + //look for active camera + for(AVCaptureDevice* camera in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) { - //init client from attribution - client = [[Client alloc] init]; - client.pid = cameraAttribution.object; - client.path = valueForStringItem(getProcessPath(client.pid.intValue)); - client.name = valueForStringItem(getProcessName(client.path)); - - //look for active camera - for(AVCaptureDevice* camera in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) + //off? skip + if(NSControlStateValueOn != [self getCameraState:camera]) { - //off? skip - if(NSControlStateValueOn != [self getCameraState:camera]) - { - //skip - continue; - } - - //virtual - // TODO: is there a better way to determine this? - if(YES == [camera.localizedName containsString:@"Virtual"]) - { - //skip - continue; - } - - //dbg msg - os_log_debug(logHandle, "camera device: %{public}@/%{public}@ is on", camera.manufacturer, camera.localizedName); - - //save - activeCamera = camera; - - //init event - // with client and (active) camera - event = [[Event alloc] init:client device:activeCamera deviceType:Device_Camera state:NSControlStateValueOn]; - - //handle event - [self handleEvent:event]; + //skip + continue; } - //no camera found? - // show (limited) alert - if(nil == activeCamera) + //virtual + // TODO: is there a better way to determine this? + if(YES == [camera.localizedName containsString:@"Virtual"]) { - //init event - // devivce is nil - event = [[Event alloc] init:client device:nil deviceType:Device_Camera state:NSControlStateValueOn]; - - //handle event - [self handleEvent:event]; + //skip + continue; } + + //dbg msg + os_log_debug(logHandle, "camera device: %{public}@/%{public}@ is on", camera.manufacturer, camera.localizedName); + + //save + activeCamera = camera; + + //init event + // with client and (active) camera + event = [[Event alloc] init:client device:activeCamera deviceType:Device_Camera state:NSControlStateValueOn]; + + //handle event + [self handleEvent:event]; + } + + //no camera found? + // show (limited) alert + if(nil == activeCamera) + { + //init event + // devivce is nil + event = [[Event alloc] init:client device:nil deviceType:Device_Camera state:NSControlStateValueOn]; + + //handle event + [self handleEvent:event]; } } - }); - - //start camera timer - dispatch_resume(self.cameraEventTimer); + } } //camera event @@ -780,64 +783,62 @@ extern os_log_t logHandle; self.lastMicOff = device; } - //macOS 13.3 + //macOS 13.3+ // use this as trigger + // older version send event via log monitor if (@available(macOS 13.3, *)) { //dbg msg os_log_debug(logHandle, "new audio event"); - //cancel prev timer - if(nil != self.audioEventTimer) + //audio off? + if(NSControlStateValueOff == state) { - //cancel - dispatch_source_cancel(self.audioEventTimer); - self.audioEventTimer = nil; + //dbg msg + os_log_debug(logHandle, "audio event: off"); + + //init event + // process (client) and device are nil + event = [[Event alloc] init:nil device:device deviceType:Device_Microphone state:NSControlStateValueOff]; + + //handle event + [self handleEvent:event]; } - //re-init timer - self.audioEventTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.audioEventQueue); - dispatch_source_set_timer(self.audioEventTimer, dispatch_walltime(NULL, 0.5 * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0.1 * NSEC_PER_SEC); - - //set handler - dispatch_source_set_event_handler(self.audioEventTimer, ^{ + //audio on? + else if(NSControlStateValueOn == state) + { + //dbg msg + os_log_debug(logHandle, "audio event: on"); - //audio off? - // send event - if(NSControlStateValueOff == state) - { - //dbg msg - os_log_debug(logHandle, "audio event: off"); - - //init event - // process (client) and device are nil - event = [[Event alloc] init:nil device:nil deviceType:Device_Microphone state:NSControlStateValueOff]; - - //handle event - [self handleEvent:event]; - } + //delay + // need time for logging to grab responsible process + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - //audio on? - // send event - else if(NSControlStateValueOn == state) - { - //dbg msg - os_log_debug(logHandle, "audio event: on"); + //client + Client* client = nil; + //have client? + if(0 != self.lastMicClient) + { + //init client from attribution + client = [[Client alloc] init]; + client.pid = [NSNumber numberWithInteger:self.lastMicClient]; + client.path = valueForStringItem(getProcessPath(client.pid.intValue)); + client.name = valueForStringItem(getProcessName(client.path)); + } + //init event // devivce is nil - event = [[Event alloc] init:nil device:nil deviceType:Device_Microphone state:NSControlStateValueOn]; + event = [[Event alloc] init:client device:device deviceType:Device_Microphone state:NSControlStateValueOn]; //handle event [self handleEvent:event]; - } - }); + }); + } - //start audio event timer - dispatch_resume(self.audioEventTimer); - - } //macOS 13.3 + } //macOS 13.3+ }; //add property listener for audio changes @@ -914,6 +915,7 @@ bail: //camera on? // macOS 13.3, use this as trigger + // older version send event via log monitor if (@available(macOS 13.3, *)) { //event @@ -928,29 +930,22 @@ bail: //dbg msg os_log_debug(logHandle, "camera event: on"); - //cancel prev timer - if(nil != self.cameraEventTimer) - { - //cancel - dispatch_cancel(self.cameraEventTimer); - self.cameraEventTimer = nil; - } + //delay + // need time for logging to grab responsible process + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - //re-init timer - self.cameraEventTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.cameraEventQueue); - dispatch_source_set_timer(self.cameraEventTimer, dispatch_walltime(NULL, 1.0 * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0.1 * NSEC_PER_SEC); - - //set handler - dispatch_source_set_event_handler(self.cameraEventTimer, ^{ - //client Client* client = nil; - //init client from attribution - client = [[Client alloc] init]; - client.pid = [NSNumber numberWithInteger:self.lastCameraClient]; - client.path = valueForStringItem(getProcessPath(client.pid.intValue)); - client.name = valueForStringItem(getProcessName(client.path)); + //have a client? + if(0 != self.lastCameraClient) + { + //init client from attribution + client = [[Client alloc] init]; + client.pid = [NSNumber numberWithInteger:self.lastCameraClient]; + client.path = valueForStringItem(getProcessPath(client.pid.intValue)); + client.name = valueForStringItem(getProcessName(client.path)); + } //init event // with client and (active) camera @@ -960,9 +955,6 @@ bail: [self handleEvent:event]; }); - - //start camera timer - dispatch_resume(self.cameraEventTimer); } //camera: off @@ -1356,14 +1348,14 @@ bail: //macOS sometimes toggles / delivers 2x events for same device if(deviceLastEvent.deviceType == event.deviceType) { - //ignore if last event was < 0.5 ago - if([event.timestamp timeIntervalSinceDate:deviceLastEvent.timestamp] < 0.5f) + //ignore if last event was < 1.0s ago + if([event.timestamp timeIntervalSinceDate:deviceLastEvent.timestamp] < 1.0f) { //set result result = NOTIFICATION_SPURIOUS; //dbg msg - os_log_debug(logHandle, "ignoring event, as it happened <0.5s ago"); + os_log_debug(logHandle, "ignoring event, as it happened <1.0s ago"); //bail goto bail; @@ -1373,7 +1365,6 @@ bail: if( (deviceLastEvent.state == event.state) && ([event.timestamp timeIntervalSinceDate:deviceLastEvent.timestamp] < 2.0f) ) { - //set result result = NOTIFICATION_SPURIOUS; @@ -1385,7 +1376,6 @@ bail: } } //same device - //client provided? // check if its allowed @@ -1498,7 +1488,7 @@ bail: if(nil != event.client) { //set body - content.body = [NSString stringWithFormat:@"\r\nProcess: %@ (%@)", event.client.name, (0 != event.client.pid.intValue) ? event.client.pid : @"pid: unknown"]; + content.body = [NSString stringWithFormat:@"Process: %@ (%@)", event.client.name, (0 != event.client.pid.intValue) ? event.client.pid : @"pid: unknown"]; //set category content.categoryIdentifier = CATEGORY_ACTION; diff --git a/Installer/Installer.xcodeproj/project.pbxproj b/Installer/Installer.xcodeproj/project.pbxproj index 9ef1880..07b03dd 100644 --- a/Installer/Installer.xcodeproj/project.pbxproj +++ b/Installer/Installer.xcodeproj/project.pbxproj @@ -329,7 +329,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#Note: To build uninstaller for app (menu)\n# 0. Delete this script and Installer.app from App's on-disk folder \n# 1. Build Installer\n# 2. Copy into App \"Uninstaller\" folder (& proj if needed)\n# 3. Re-add script, and then build (archive) for deployment\n\n#archive\n# copy in main application \nif [[ $BUILT_PRODUCTS_DIR = *\"ArchiveIntermediates\"* ]]; then\n cp -R -f \"$PROJECT_TEMP_ROOT/UninstalledProducts/macosx/OverSight.app\" \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Resources\"\n \n#normal build\n# delete and copy in main application \nelse\n\nrm -rf \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Resources/OverSight.app\"\ncp -R -f \"$BUILT_PRODUCTS_DIR/OverSight.app\" \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Resources\"\n\nfi\n\n"; + shellScript = "#Note: To build uninstaller for app (menu)\n# 0. Delete this script and Installer.app from App's on-disk folder \n# 1. Build Installer\n# 2. Copy into App \"Uninstaller\" folder (& proj if needed)\n# 3. Re-add script, and then build (archive) for deployment\n\n#archive\n# copy in main application \nif [[ $BUILT_PRODUCTS_DIR = *\"ArchiveIntermediates\"* ]]; then\n cp -R -f \"$PROJECT_TEMP_ROOT/UninstalledProducts/macosx/OverSight.app\" \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Resources\"\n \n#normal build\n# delete and copy in main application \nelse\n\nrm -rf \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Resources/OverSight.app\"\ncp -R -f \"$BUILT_PRODUCTS_DIR/OverSight.app\" \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Resources\"\n\nfi\n\n\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -380,14 +380,14 @@ CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2.1.8; + CURRENT_PROJECT_VERSION = 2.2.0; DEVELOPMENT_TEAM = VBG97UB4TA; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac"; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; INFOPLIST_FILE = Helper/Info.plist; MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 2.1.8; + MARKETING_VERSION = 2.2.0; ONLY_ACTIVE_ARCH = NO; OTHER_CODE_SIGN_FLAGS = ""; OTHER_LDFLAGS = ( @@ -415,14 +415,14 @@ CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2.1.8; + CURRENT_PROJECT_VERSION = 2.2.0; DEVELOPMENT_TEAM = VBG97UB4TA; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac"; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; INFOPLIST_FILE = Helper/Info.plist; MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 2.1.8; + MARKETING_VERSION = 2.2.0; ONLY_ACTIVE_ARCH = NO; OTHER_CODE_SIGN_FLAGS = ""; OTHER_LDFLAGS = ( @@ -451,7 +451,7 @@ CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2.1.8; + CURRENT_PROJECT_VERSION = 2.2.0; DEVELOPMENT_TEAM = VBG97UB4TA; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac"; @@ -463,7 +463,7 @@ ); LIBRARY_SEARCH_PATHS = ""; MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 2.1.8; + MARKETING_VERSION = 2.2.0; OTHER_CODE_SIGN_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.oversight.installer"; PRODUCT_NAME = "OverSight Installer"; @@ -480,7 +480,7 @@ CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2.1.8; + CURRENT_PROJECT_VERSION = 2.2.0; DEVELOPMENT_TEAM = VBG97UB4TA; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/Mac"; @@ -492,7 +492,7 @@ ); LIBRARY_SEARCH_PATHS = ""; MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 2.1.8; + MARKETING_VERSION = 2.2.0; OTHER_CODE_SIGN_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.oversight.installer"; PRODUCT_NAME = "OverSight Installer"; diff --git a/Installer/Source/Assets.xcassets/DoNotDisturb.imageset/Contents.json b/Installer/Source/Assets.xcassets/DoNotDisturb.imageset/Contents.json new file mode 100644 index 0000000..28e5e0e --- /dev/null +++ b/Installer/Source/Assets.xcassets/DoNotDisturb.imageset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "mac", + "filename" : "darkMode.png" + }, + { + "idiom" : "mac", + "filename" : "lightMode.png", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ] + }, + { + "idiom" : "mac", + "filename" : "darkMode.png", + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ] + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Installer/Source/Assets.xcassets/DoNotDisturb.imageset/darkMode.png b/Installer/Source/Assets.xcassets/DoNotDisturb.imageset/darkMode.png new file mode 100644 index 0000000..4ac5f1d Binary files /dev/null and b/Installer/Source/Assets.xcassets/DoNotDisturb.imageset/darkMode.png differ diff --git a/Installer/Source/Assets.xcassets/DoNotDisturb.imageset/lightMode.png b/Installer/Source/Assets.xcassets/DoNotDisturb.imageset/lightMode.png new file mode 100644 index 0000000..257e030 Binary files /dev/null and b/Installer/Source/Assets.xcassets/DoNotDisturb.imageset/lightMode.png differ diff --git a/Installer/Source/ConfigureWindowController.h b/Installer/Source/ConfigureWindowController.h index e3d82a0..8d19173 100644 --- a/Installer/Source/ConfigureWindowController.h +++ b/Installer/Source/ConfigureWindowController.h @@ -41,7 +41,13 @@ @property (strong, nonatomic) IBOutlet NSView *notificationsView; //support us -@property (weak, nonatomic) IBOutlet NSButton *gotoSupportViewButton; +@property (weak, nonatomic) IBOutlet NSButton *gotoDNDView; + +//do not disturb view +@property (strong) IBOutlet NSView *doNotDisturbView; + +//support us +@property (weak, nonatomic) IBOutlet NSButton *gotoSupportView; /* SUPPORT US */ diff --git a/Installer/Source/ConfigureWindowController.m b/Installer/Source/ConfigureWindowController.m index 2c93868..043118d 100644 --- a/Installer/Source/ConfigureWindowController.m +++ b/Installer/Source/ConfigureWindowController.m @@ -198,13 +198,28 @@ extern os_log_t logHandle; } //show 'support' view - case ACTION_SHOW_NOTIFICATIONS: + case ACTION_SHOW_NOTIFICATION_VIEW: { //dbg msg os_log_debug(logHandle, "showing 'notifcations' view"); //show view - [self showView:self.notificationsView firstResponder:self.gotoSupportViewButton]; + [self showView:self.notificationsView firstResponder:self.gotoDNDView]; + + //unset window title + self.window.title = @""; + + break; + } + + //show 'support' view + case ACTION_SHOW_DND_VIEW: + { + //dbg msg + os_log_debug(logHandle, "showing 'do not disturb' view"); + + //show view + [self showView:self.doNotDisturbView firstResponder:self.gotoSupportView]; //unset window title self.window.title = @""; @@ -213,7 +228,7 @@ extern os_log_t logHandle; } //show 'support' view - case ACTION_SHOW_SUPPORT: + case ACTION_SHOW_SUPPORT_VIEW: { //dbg msg os_log_debug(logHandle, "showing 'support' view"); @@ -523,7 +538,7 @@ extern os_log_t logHandle; self.installButton.title = ACTION_NEXT; //set tag - self.installButton.tag = ACTION_SHOW_NOTIFICATIONS; + self.installButton.tag = ACTION_SHOW_NOTIFICATION_VIEW; } //otherwise // set button and tag for close/exit diff --git a/Installer/Source/ConfigureWindowController.xib b/Installer/Source/ConfigureWindowController.xib index 6df3908..4495480 100644 --- a/Installer/Source/ConfigureWindowController.xib +++ b/Installer/Source/ConfigureWindowController.xib @@ -1,15 +1,17 @@ - + - + - + + + @@ -25,7 +27,7 @@ - + @@ -140,6 +142,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -162,7 +207,7 @@ -