bug fixes
mic process identification on macOS 14
This commit is contained in:
Patrick Wardle 2023-11-04 12:18:30 -10:00
parent 716e1e4b0a
commit e2396db29c
13 changed files with 429 additions and 323 deletions

View File

@ -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 = "<group>"; };
CD2F800B24455333009C3D77 /* AboutWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutWindowController.m; sourceTree = "<group>"; };
CD2F801624468A8C009C3D77 /* patrons.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = patrons.txt; path = ../Shared/patrons.txt; sourceTree = "<group>"; };
CD3052C62AF6D86600250347 /* OverSight Installer.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = "OverSight Installer.app"; sourceTree = "<group>"; };
CD6836672391DB6F00CF19C1 /* security.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = security.plist; sourceTree = "<group>"; };
CD8FD5D223BAE2D100EFE0FB /* PrefsWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrefsWindowController.h; sourceTree = "<group>"; };
CD8FD5D323BAE2D100EFE0FB /* Preferences.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = "<group>"; };
@ -95,7 +96,6 @@
CDC60993263CBEE7006D1332 /* Client.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Client.m; sourceTree = "<group>"; };
CDCBF0DC26499DFF001D9F9A /* Event.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Event.h; sourceTree = "<group>"; };
CDCBF0DD26499DFF001D9F9A /* Event.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Event.m; sourceTree = "<group>"; };
CDFFBC562AEBD35E00560604 /* OverSight Installer.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = "OverSight Installer.app"; sourceTree = "<group>"; };
/* 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 = "<group>";
@ -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;

View File

@ -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;

View File

@ -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 <private> endpoint <private> camera <private>'");
os_log_debug(logHandle, ">= macOS 14+: Using log monitor for AV events via w/ (camera): 'added <private> endpoint <private> camera <private>' AND (mic): '-[MXCoreSession beginInterruption]: Session <ID: xx, PID = xyz,...'");
//init regex
regex = [NSRegularExpression regularExpressionWithPattern:@"\\[\\{private\\}(\\d+)\\]" options:0 error:nil];
//init mic regex
micRegex = [NSRegularExpression regularExpressionWithPattern:@"PID = (\\d+)" options:0 error:nil];
//start logging
[self.logMonitor start:[NSPredicate predicateWithFormat:@"subsystem=='com.apple.cmio'"] level:Log_Level_Debug callback:^(OSLogEvent* logEvent) {
//init cam regex
cameraRegex = [NSRegularExpression regularExpressionWithPattern:@"\\[\\{private\\}(\\d+)\\]" options:0 error:nil];
//start log monitoring
[self.logMonitor start:[NSPredicate predicateWithFormat:@"subsystem=='com.apple.cmio' OR subsystem=='com.apple.coremedia'"] level:Log_Level_Debug callback:^(OSLogEvent* logEvent) {
//match
NSTextCheckingResult* match = nil;
//pid
NSInteger pid = 0;
//sync to process
@synchronized (self) {
//only interested msgs that end w/:
//match
NSTextCheckingResult* match = nil;
//pid
NSInteger pid = 0;
//camera:
// "added <private> endpoint <private> camera <private> = <pid>;"
if(YES != [logEvent.composedMessage hasSuffix:@"added <private> endpoint <private> camera <private>"])
if( (YES == [logEvent.subsystem isEqual:@"com.apple.cmio"]) &&
(YES == [logEvent.composedMessage hasSuffix:@"added <private> endpoint <private> camera <private>"]) )
{
//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 <ID: xx, PID = xyz, ...":
else if( (YES == [logEvent.subsystem isEqual:@"com.apple.coremedia"]) &&
(YES == [logEvent.composedMessage hasPrefix:@"-MXCoreSession- -[MXCoreSession beginInterruption]"]) &&
(YES == [logEvent.composedMessage hasSuffix:@"Recording = YES> 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;

View File

@ -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";

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -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 */

View File

@ -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

View File

@ -1,15 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22154" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22155" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22154"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22155"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="ConfigureWindowController">
<connections>
<outlet property="activityIndicator" destination="b1w-5W-ayX" id="P5e-PO-Ozi"/>
<outlet property="gotoSupportViewButton" destination="8I9-ag-g5s" id="7ej-Ft-edA"/>
<outlet property="doNotDisturbView" destination="6y2-pv-Ibd" id="iO4-Zv-dm4"/>
<outlet property="gotoDNDView" destination="8I9-ag-g5s" id="zy3-3J-OSA"/>
<outlet property="gotoSupportView" destination="2V7-Bx-gVi" id="2hL-gj-ztg"/>
<outlet property="installButton" destination="553-2y-kvm" id="nxf-wO-EI3"/>
<outlet property="moreInfoButton" destination="QWu-qZ-Za2" id="8kC-Ga-5oJ"/>
<outlet property="notificationsView" destination="DoQ-oR-CLI" id="F1Y-Mo-2E6"/>
@ -25,7 +27,7 @@
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="196" y="240" width="523" height="237"/>
<rect key="screenRect" x="0.0" y="0.0" width="1512" height="944"/>
<rect key="screenRect" x="0.0" y="0.0" width="3440" height="1415"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="523" height="237"/>
<autoresizingMask key="autoresizingMask"/>
@ -140,6 +142,49 @@
</subviews>
<point key="canvasLocation" x="686" y="1393"/>
</customView>
<customView id="6y2-pv-Ibd" userLabel="Do Not Disturb">
<rect key="frame" x="0.0" y="0.0" width="812" height="500"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<box fixedFrame="YES" boxType="custom" borderType="none" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="nf3-DZ-8wh">
<rect key="frame" x="0.0" y="0.0" width="812" height="48"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<view key="contentView" id="30U-SP-9eW">
<rect key="frame" x="0.0" y="0.0" width="812" height="48"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button tag="4" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2V7-Bx-gVi">
<rect key="frame" x="718" y="7" width="81" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Next »" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="0wd-cy-xEw">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" size="13" name="Menlo-Bold"/>
</buttonCell>
<connections>
<action selector="configureButtonHandler:" target="-2" id="qnn-kG-aKi"/>
</connections>
</button>
</subviews>
</view>
<color key="fillColor" red="0.52700018810000004" green="0.69087679930000001" blue="0.21211786799999999" alpha="0.99595620600000001" colorSpace="custom" customColorSpace="sRGB"/>
</box>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fbL-Nf-kJy">
<rect key="frame" x="41" y="39" width="731" height="359"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="DoNotDisturb" id="E1E-Ha-z8D"/>
</imageView>
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" preferredMaxLayoutWidth="660" translatesAutoresizingMaskIntoConstraints="NO" id="FRR-wv-bYz">
<rect key="frame" x="177" y="370" width="459" height="110"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="center" title="If &quot;Do Not Disturb&quot; mode is activated, OverSight's alerts won't be shown" id="zdX-7L-hfX">
<font key="font" size="32" name="AvenirNextCondensed-Regular"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<point key="canvasLocation" x="686" y="1393"/>
</customView>
<customView id="bkk-rY-ALC" userLabel="Support">
<rect key="frame" x="0.0" y="0.0" width="812" height="500"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
@ -162,7 +207,7 @@
<action selector="configureButtonHandler:" target="-2" id="Ipm-LG-LXx"/>
</connections>
</button>
<button tag="4" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pM7-Wp-KdU">
<button tag="5" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pM7-Wp-KdU">
<rect key="frame" x="718" y="7" width="81" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Yes!" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="E5X-AO-6V1">
@ -253,6 +298,7 @@
</customView>
</objects>
<resources>
<image name="DoNotDisturb" width="1430" height="510"/>
<image name="FriendsJamf" width="328" height="114"/>
<image name="FriendsKandji" width="1944" height="494"/>
<image name="FriendsKolide" width="636" height="216"/>

View File

@ -196,13 +196,16 @@
#define ACTION_NEXT @"Next »"
//show info about notifications
#define ACTION_SHOW_NOTIFICATIONS 2
#define ACTION_SHOW_NOTIFICATION_VIEW 2
//show info about do not disturb
#define ACTION_SHOW_DND_VIEW 3
//show friends
#define ACTION_SHOW_SUPPORT 3
#define ACTION_SHOW_SUPPORT_VIEW 4
//support us
#define ACTION_SUPPORT 4
#define ACTION_SUPPORT 5
//path to chmod
#define CHMOD @"/bin/chmod"

View File

@ -130,4 +130,7 @@ NSModalResponse showAlert(NSString* messageText, NSString* informativeText, NSSt
//does console user have admin privs?
BOOL hasAdminPrivileges(void);
//get Do Not Distrub state
BOOL DNDState(void);
#endif

View File

@ -1395,6 +1395,9 @@ NSMutableDictionary* execTask(NSString* binaryPath, NSArray* arguments, BOOL sho
// NSTask throws if path isn't found...
if(YES != [NSFileManager.defaultManager fileExistsAtPath:binaryPath])
{
//err msg
os_log_error(logHandle, "ERROR: %{public}@ not found", binaryPath);
//bail
goto bail;
}
@ -1449,7 +1452,7 @@ NSMutableDictionary* execTask(NSString* binaryPath, NSArray* arguments, BOOL sho
@catch(NSException *exception)
{
//err msg
os_log_debug(logHandle, "failed to launch task (%{public}@)", exception);
os_log_error(logHandle, "failed to launch task (%{public}@)", exception);
//bail
goto bail;
@ -1585,7 +1588,7 @@ BOOL isFileRestricted(NSString* file)
}
//in dark mode?
BOOL isDarkMode()
BOOL isDarkMode(void)
{
return [[[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"] isEqualToString:@"Dark"];
}
@ -1630,7 +1633,7 @@ NSModalResponse showAlert(NSString* messageText, NSString* informativeText, NSSt
//checks if user has admin privs
// based off http://stackoverflow.com/questions/30000443/asking-for-admin-privileges-for-only-standard-accounts
BOOL hasAdminPrivileges()
BOOL hasAdminPrivileges(void)
{
//flag
BOOL isAdmin = NO;
@ -1667,3 +1670,17 @@ BOOL hasAdminPrivileges()
return isAdmin;
}
//get Do Not Distrub state
BOOL DNDState(void)
{
//default
NSUserDefaults* defaults = nil;
//load defaults
defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.notificationcenterui"];
//return status of DND
return [defaults boolForKey:@"doNotDisturb"];
}