2016-09-12 01:21:14 +01:00
|
|
|
//
|
|
|
|
// AVMonitor.m
|
|
|
|
// OverSight
|
|
|
|
//
|
|
|
|
// Created by Patrick Wardle on 9/01/16.
|
|
|
|
// Copyright (c) 2015 Objective-See. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import "Consts.h"
|
|
|
|
#import "Logging.h"
|
|
|
|
#import "Utilities.h"
|
|
|
|
#import "AVMonitor.h"
|
|
|
|
#import "AppDelegate.h"
|
|
|
|
|
|
|
|
#import "../Shared/XPCProtocol.h"
|
|
|
|
|
2016-09-18 22:10:44 +01:00
|
|
|
@implementation AVMonitor
|
2016-09-12 01:21:14 +01:00
|
|
|
|
2016-09-18 22:10:44 +01:00
|
|
|
@synthesize mic;
|
|
|
|
@synthesize camera;
|
2016-12-21 07:38:26 +00:00
|
|
|
@synthesize lastEvent;
|
2017-03-28 09:00:11 +01:00
|
|
|
@synthesize whiteList;
|
2016-09-18 22:10:44 +01:00
|
|
|
@synthesize audioActive;
|
2017-09-25 07:29:24 +01:00
|
|
|
@synthesize rememberPopups;
|
2017-04-03 00:12:42 +01:00
|
|
|
@synthesize activationAlerts;
|
2016-12-21 07:38:26 +00:00
|
|
|
@synthesize lastNotification;
|
2016-09-18 22:10:44 +01:00
|
|
|
@synthesize videoMonitorThread;
|
|
|
|
|
|
|
|
//init
|
|
|
|
-(id)init
|
2016-09-12 01:21:14 +01:00
|
|
|
{
|
2016-09-18 22:10:44 +01:00
|
|
|
//init super
|
|
|
|
self = [super init];
|
|
|
|
if(nil != self)
|
|
|
|
{
|
2017-04-03 00:12:42 +01:00
|
|
|
//alloc
|
|
|
|
activationAlerts = [NSMutableDictionary dictionary];
|
|
|
|
|
2017-09-25 07:29:24 +01:00
|
|
|
//alloc
|
|
|
|
rememberPopups = [NSMutableArray array];
|
|
|
|
|
2017-03-28 09:00:11 +01:00
|
|
|
//load whitelist
|
|
|
|
[self loadWhitelist];
|
2016-09-18 22:10:44 +01:00
|
|
|
}
|
2016-09-12 01:21:14 +01:00
|
|
|
|
2016-09-18 22:10:44 +01:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2017-03-28 09:00:11 +01:00
|
|
|
//load whitelist
|
|
|
|
-(void)loadWhitelist
|
|
|
|
{
|
|
|
|
//path
|
|
|
|
NSString* path = nil;
|
|
|
|
|
|
|
|
//init path
|
2017-04-13 10:29:18 +01:00
|
|
|
path = [[[@"~" stringByAppendingPathComponent:APP_SUPPORT_DIRECTORY] stringByExpandingTildeInPath] stringByAppendingPathComponent:FILE_WHITELIST];
|
2017-03-28 09:00:11 +01:00
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2017-04-14 10:24:01 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"whitelist path %@", path]);
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2017-04-14 10:24:01 +01:00
|
|
|
|
|
|
|
//check if it exists
|
|
|
|
if(YES != [[NSFileManager defaultManager] fileExistsAtPath:path])
|
|
|
|
{
|
|
|
|
//dbg msg
|
|
|
|
#ifdef DEBUG
|
|
|
|
logMsg(LOG_DEBUG, @"nothing whitelisted yet, so won't load (file not found)");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//bail
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
|
2017-03-28 09:00:11 +01:00
|
|
|
//since file is created by priv'd XPC, it shouldn't be writeable
|
2017-04-08 08:37:23 +01:00
|
|
|
// ...unless somebody maliciously creates it, so we check that here
|
2017-03-28 09:00:11 +01:00
|
|
|
if(YES == [[NSFileManager defaultManager] isWritableFileAtPath:path])
|
|
|
|
{
|
|
|
|
//err msg
|
|
|
|
logMsg(LOG_ERR, @"whitelist is writable, so ignoring!");
|
|
|
|
|
|
|
|
//bail
|
|
|
|
goto bail;
|
|
|
|
}
|
2017-04-14 10:24:01 +01:00
|
|
|
|
2017-03-28 09:00:11 +01:00
|
|
|
//load
|
|
|
|
self.whiteList = [NSMutableArray arrayWithContentsOfFile:path];
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2017-03-28 09:00:11 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"whitelist: %@", self.whiteList]);
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2017-03-28 09:00:11 +01:00
|
|
|
|
|
|
|
//bail
|
|
|
|
bail:
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-09-20 08:29:40 +01:00
|
|
|
//grab first apple camera, or default
|
2016-09-18 22:10:44 +01:00
|
|
|
// ->saves into iVar 'camera'
|
|
|
|
-(void)findAppleCamera
|
|
|
|
{
|
2016-09-20 08:29:40 +01:00
|
|
|
//cameras
|
|
|
|
NSArray* cameras = nil;
|
|
|
|
|
|
|
|
//grab all cameras
|
|
|
|
cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-20 08:29:40 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"cameras: %@", cameras]);
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-20 08:29:40 +01:00
|
|
|
|
|
|
|
//look for camera that belongs to apple
|
|
|
|
for(AVCaptureDevice* currentCamera in cameras)
|
2016-09-12 01:21:14 +01:00
|
|
|
{
|
|
|
|
//check if apple
|
2016-09-18 22:10:44 +01:00
|
|
|
if(YES == [currentCamera.manufacturer isEqualToString:@"Apple Inc."])
|
2016-09-12 01:21:14 +01:00
|
|
|
{
|
|
|
|
//save
|
2016-09-18 22:10:44 +01:00
|
|
|
self.camera = currentCamera;
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//exit loop
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-20 08:29:40 +01:00
|
|
|
//didn't find apple
|
|
|
|
// ->grab default camera
|
|
|
|
if(nil == self.camera)
|
|
|
|
{
|
|
|
|
//get default
|
|
|
|
self.camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-20 08:29:40 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"didn't find apple camera, grabbed default: %@", self.camera]);
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-20 08:29:40 +01:00
|
|
|
}
|
|
|
|
|
2016-09-18 22:10:44 +01:00
|
|
|
return;
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
|
|
|
|
2016-09-18 22:10:44 +01:00
|
|
|
//grab first apple mic
|
|
|
|
// ->saves into iVar 'mic'
|
|
|
|
-(void)findAppleMic
|
2016-09-12 01:21:14 +01:00
|
|
|
{
|
2016-09-20 08:29:40 +01:00
|
|
|
//mics
|
|
|
|
NSArray* mics = nil;
|
|
|
|
|
|
|
|
//grab all mics
|
|
|
|
mics = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-20 08:29:40 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"mics: %@", mics]);
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-20 08:29:40 +01:00
|
|
|
|
|
|
|
//look for mic that belongs to apple
|
|
|
|
for(AVCaptureDevice* currentMic in mics)
|
2016-09-12 01:21:14 +01:00
|
|
|
{
|
|
|
|
//check if apple
|
|
|
|
// ->also check input source
|
2016-09-18 22:10:44 +01:00
|
|
|
if( (YES == [currentMic.manufacturer isEqualToString:@"Apple Inc."]) &&
|
|
|
|
(YES == [[[currentMic activeInputSource] inputSourceID] isEqualToString:@"imic"]) )
|
2016-09-12 01:21:14 +01:00
|
|
|
{
|
|
|
|
//save
|
2016-09-18 22:10:44 +01:00
|
|
|
self.mic = currentMic;
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//exit loop
|
|
|
|
break;
|
|
|
|
}
|
2016-09-20 08:29:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//didn't find apple
|
|
|
|
// ->grab default camera
|
|
|
|
if(nil == self.mic)
|
|
|
|
{
|
|
|
|
//get default
|
|
|
|
self.mic = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
|
2016-09-12 01:21:14 +01:00
|
|
|
|
2016-09-20 08:29:40 +01:00
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-20 08:29:40 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"didn't find apple 'imic', grabbed default: %@", self.mic]);
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
|
|
|
|
2016-09-18 22:10:44 +01:00
|
|
|
return;
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//initialiaze AV notifcations/callbacks
|
|
|
|
-(BOOL)monitor
|
|
|
|
{
|
|
|
|
//return var
|
|
|
|
BOOL bRet = NO;
|
|
|
|
|
2016-09-17 00:00:23 +01:00
|
|
|
//status/err var
|
|
|
|
BOOL wasErrors = NO;
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//xpc connection
|
|
|
|
__block NSXPCConnection* xpcConnection = nil;
|
|
|
|
|
|
|
|
//device's connection id
|
|
|
|
unsigned int connectionID = 0;
|
|
|
|
|
|
|
|
//selector for getting device id
|
|
|
|
SEL methodSelector = nil;
|
|
|
|
|
|
|
|
//array for devices + status
|
|
|
|
NSMutableArray* devices = nil;
|
|
|
|
|
|
|
|
//wait semaphore
|
|
|
|
dispatch_semaphore_t waitSema = nil;
|
|
|
|
|
|
|
|
//alloc XPC connection
|
|
|
|
xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"];
|
|
|
|
|
|
|
|
//alloc device array
|
|
|
|
devices = [NSMutableArray array];
|
|
|
|
|
|
|
|
//set remote object interface
|
|
|
|
xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)];
|
|
|
|
|
|
|
|
//resume
|
|
|
|
[xpcConnection resume];
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-12 01:21:14 +01:00
|
|
|
logMsg(LOG_DEBUG, @"telling XPC service to begin base-lining mach messages");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//init wait semaphore
|
|
|
|
waitSema = dispatch_semaphore_create(0);
|
|
|
|
|
|
|
|
//XPC service to begin baselining mach messages
|
|
|
|
// ->wait, since want this to compelete before doing other things!
|
|
|
|
[[xpcConnection remoteObjectProxy] initialize:^
|
|
|
|
{
|
|
|
|
//signal sema
|
|
|
|
dispatch_semaphore_signal(waitSema);
|
2017-09-25 07:45:02 +01:00
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
}];
|
|
|
|
|
|
|
|
//wait until XPC is done
|
|
|
|
// ->XPC reply block will signal semaphore
|
|
|
|
dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER);
|
|
|
|
|
|
|
|
//init selector
|
|
|
|
methodSelector = NSSelectorFromString(@"connectionID");
|
|
|
|
|
|
|
|
//find (first) apple camera
|
2016-09-18 22:10:44 +01:00
|
|
|
// ->saves camera into iVar, 'camera'
|
|
|
|
[self findAppleCamera];
|
2016-09-12 01:21:14 +01:00
|
|
|
|
2016-09-18 22:10:44 +01:00
|
|
|
//find (first) apple mic
|
|
|
|
// ->saves mic into iVar, 'mic'
|
|
|
|
[self findAppleMic];
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//got camera
|
|
|
|
// ->grab connection ID and invoke helper functions
|
|
|
|
if( (nil != self.camera) &&
|
|
|
|
(YES == [self.camera respondsToSelector:methodSelector]) )
|
|
|
|
{
|
|
|
|
//ignore leak warning
|
|
|
|
// ->we know what we're doing via this 'performSelector'
|
|
|
|
#pragma clang diagnostic push
|
|
|
|
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
|
|
|
|
|
|
|
//grab connection ID
|
|
|
|
connectionID = (unsigned int)[self.camera performSelector:methodSelector withObject:nil];
|
|
|
|
|
|
|
|
//restore
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
|
|
|
|
//set status
|
|
|
|
// ->will set 'videoActive' iVar
|
|
|
|
[self setVideoDevStatus:connectionID];
|
|
|
|
|
|
|
|
//if video is already active
|
2016-11-07 03:33:42 +00:00
|
|
|
// ->set status & start monitoring thread
|
2016-09-12 01:21:14 +01:00
|
|
|
if(YES == self.videoActive)
|
|
|
|
{
|
2016-09-25 04:08:38 +01:00
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-25 04:08:38 +01:00
|
|
|
logMsg(LOG_DEBUG, @"video already active, so will start polling for new video procs");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-25 04:08:38 +01:00
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//tell XPC video is active
|
|
|
|
[[xpcConnection remoteObjectProxy] updateVideoStatus:self.videoActive reply:^{
|
|
|
|
|
|
|
|
//signal sema
|
|
|
|
dispatch_semaphore_signal(waitSema);
|
|
|
|
}];
|
|
|
|
|
|
|
|
//wait until XPC is done
|
|
|
|
// ->XPC reply block will signal semaphore
|
|
|
|
dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER);
|
|
|
|
|
|
|
|
//alloc
|
|
|
|
videoMonitorThread = [[NSThread alloc] initWithTarget:self selector:@selector(monitor4Procs) object:nil];
|
|
|
|
|
|
|
|
//start
|
|
|
|
[self.videoMonitorThread start];
|
|
|
|
}
|
|
|
|
|
|
|
|
//save camera/status into device array
|
|
|
|
[devices addObject:@{EVENT_DEVICE:self.camera, EVENT_DEVICE_STATUS:@(self.videoActive)}];
|
|
|
|
|
|
|
|
//register for video events
|
|
|
|
if(YES != [self watchVideo:connectionID])
|
|
|
|
{
|
|
|
|
//err msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-17 00:00:23 +01:00
|
|
|
logMsg(LOG_DEBUG, @"failed to watch for video events");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-17 00:00:23 +01:00
|
|
|
|
|
|
|
//set err
|
|
|
|
wasErrors = YES;
|
|
|
|
|
|
|
|
//don't bail
|
|
|
|
// ->can still listen for audio events
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
//err msg
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//err msg
|
|
|
|
logMsg(LOG_ERR, @"failed to find (apple) camera :(");
|
2016-09-17 00:00:23 +01:00
|
|
|
|
|
|
|
//don't bail
|
|
|
|
// ->can still listen for audio events
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//watch mic
|
|
|
|
// ->grab connection ID and invoke helper function
|
|
|
|
if( (nil != self.mic) &&
|
|
|
|
(YES == [self.mic respondsToSelector:methodSelector]) )
|
|
|
|
{
|
|
|
|
//ignore leak warning
|
|
|
|
// ->we know what we're doing via this 'performSelector'
|
|
|
|
#pragma clang diagnostic push
|
|
|
|
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
|
|
|
|
|
|
|
//grab connection ID
|
|
|
|
connectionID = (unsigned int)[self.mic performSelector:NSSelectorFromString(@"connectionID") withObject:nil];
|
|
|
|
|
|
|
|
//restore
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
|
2016-11-07 03:33:42 +00:00
|
|
|
//set status
|
|
|
|
// ->will set 'videoActive' iVar
|
|
|
|
[self setAudioDevStatus:connectionID];
|
|
|
|
|
|
|
|
//if audio is already active
|
|
|
|
// ->tell XPC that it's active
|
2017-04-03 00:12:42 +01:00
|
|
|
// TODO: monitor for piggybacking?
|
2016-11-07 03:33:42 +00:00
|
|
|
if(YES == self.audioActive)
|
|
|
|
{
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
|
|
|
logMsg(LOG_DEBUG, @"audio already active");
|
|
|
|
#endif
|
2016-11-07 03:33:42 +00:00
|
|
|
|
|
|
|
//tell XPC audio is active
|
|
|
|
[[xpcConnection remoteObjectProxy] updateAudioStatus:self.audioActive reply:^{
|
|
|
|
|
|
|
|
//signal sema
|
|
|
|
dispatch_semaphore_signal(waitSema);
|
|
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
//wait until XPC is done
|
|
|
|
// ->XPC reply block will signal semaphore
|
|
|
|
dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER);
|
|
|
|
}
|
|
|
|
|
|
|
|
//save mic/status into device array
|
2016-09-12 01:21:14 +01:00
|
|
|
[devices addObject:@{EVENT_DEVICE:self.mic, EVENT_DEVICE_STATUS:@(self.audioActive)}];
|
|
|
|
|
|
|
|
//register for audio events
|
|
|
|
if(YES != [self watchAudio:connectionID])
|
|
|
|
{
|
|
|
|
//err msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-17 00:00:23 +01:00
|
|
|
logMsg(LOG_DEBUG, @"failed to watch for audio events");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-17 00:00:23 +01:00
|
|
|
|
|
|
|
//set err
|
|
|
|
wasErrors = YES;
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//err msg
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//err msg
|
|
|
|
logMsg(LOG_ERR, @"failed to find (apple) mic :(");
|
|
|
|
}
|
|
|
|
|
|
|
|
//send msg to status menu
|
|
|
|
// ->update menu to show devices & their status
|
|
|
|
[((AppDelegate*)[[NSApplication sharedApplication] delegate]).statusBarMenuController updateStatusItemMenu:devices];
|
|
|
|
|
2016-09-17 00:00:23 +01:00
|
|
|
//make sure no errors occured
|
|
|
|
if(YES != wasErrors)
|
|
|
|
{
|
|
|
|
//happy
|
|
|
|
bRet = YES;
|
|
|
|
}
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//cleanup XPC
|
|
|
|
if(nil != xpcConnection)
|
|
|
|
{
|
|
|
|
//close connection
|
|
|
|
[xpcConnection invalidate];
|
2017-09-25 07:45:02 +01:00
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return bRet;
|
|
|
|
}
|
|
|
|
|
|
|
|
//determine if video is active
|
|
|
|
// ->sets 'videoActive' iVar
|
|
|
|
-(void)setVideoDevStatus:(CMIODeviceID)deviceID
|
|
|
|
{
|
|
|
|
//status var
|
|
|
|
OSStatus status = -1;
|
|
|
|
|
|
|
|
//running flag
|
|
|
|
UInt32 isRunning = -1;
|
|
|
|
|
|
|
|
//size of query flag
|
|
|
|
UInt32 propertySize = 0;
|
|
|
|
|
|
|
|
//property address struct
|
|
|
|
CMIOObjectPropertyAddress propertyStruct = {0};
|
|
|
|
|
|
|
|
//init size
|
|
|
|
propertySize = sizeof(isRunning);
|
|
|
|
|
|
|
|
//init property struct's selector
|
|
|
|
propertyStruct.mSelector = kAudioDevicePropertyDeviceIsRunningSomewhere;
|
|
|
|
|
|
|
|
//init property struct's scope
|
|
|
|
propertyStruct.mScope = kCMIOObjectPropertyScopeGlobal;
|
|
|
|
|
|
|
|
//init property struct's element
|
|
|
|
propertyStruct.mElement = 0;
|
|
|
|
|
|
|
|
//query to get 'kAudioDevicePropertyDeviceIsRunningSomewhere' status
|
|
|
|
status = CMIOObjectGetPropertyData(deviceID, &propertyStruct, 0, NULL, sizeof(kAudioDevicePropertyDeviceIsRunningSomewhere), &propertySize, &isRunning);
|
|
|
|
if(noErr != status)
|
|
|
|
{
|
|
|
|
//err msg
|
|
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"getting status of video device failed with %d", status]);
|
|
|
|
|
|
|
|
//set error
|
|
|
|
isRunning = -1;
|
|
|
|
|
|
|
|
//bail
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
|
|
|
|
//set iVar
|
|
|
|
self.videoActive = isRunning;
|
|
|
|
|
|
|
|
//bail
|
|
|
|
bail:
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//helper function
|
2017-09-25 07:45:02 +01:00
|
|
|
// determines if video went active/inactive then invokes notification generator method
|
2016-09-12 01:21:14 +01:00
|
|
|
-(void)handleVideoNotification:(CMIOObjectID)deviceID addresses:(const CMIOObjectPropertyAddress[]) addresses
|
|
|
|
{
|
|
|
|
//event dictionary
|
|
|
|
NSMutableDictionary* event = nil;
|
|
|
|
|
|
|
|
//xpc connection
|
|
|
|
__block NSXPCConnection* xpcConnection = nil;
|
|
|
|
|
|
|
|
//wait semaphore
|
|
|
|
dispatch_semaphore_t waitSema = nil;
|
|
|
|
|
2016-09-20 08:29:40 +01:00
|
|
|
//devices
|
|
|
|
NSMutableArray* devices = nil;
|
|
|
|
|
|
|
|
//init dictionary for event
|
2016-09-12 01:21:14 +01:00
|
|
|
event = [NSMutableDictionary dictionary];
|
|
|
|
|
2016-09-20 08:29:40 +01:00
|
|
|
//init array for devices
|
|
|
|
devices = [NSMutableArray array];
|
|
|
|
|
2016-09-17 00:00:23 +01:00
|
|
|
//sync
|
2016-09-12 01:21:14 +01:00
|
|
|
@synchronized (self)
|
|
|
|
{
|
|
|
|
|
|
|
|
//set status
|
2016-09-25 04:08:38 +01:00
|
|
|
// ->sets 'videoActive' iVar
|
2016-09-12 01:21:14 +01:00
|
|
|
[self setVideoDevStatus:deviceID];
|
|
|
|
|
2016-09-20 08:29:40 +01:00
|
|
|
//add camera
|
|
|
|
if(nil != self.camera)
|
|
|
|
{
|
|
|
|
//add
|
|
|
|
[devices addObject:@{EVENT_DEVICE:self.camera, EVENT_DEVICE_STATUS:@(self.videoActive)}];
|
|
|
|
}
|
|
|
|
|
|
|
|
//add mic
|
|
|
|
if(nil != self.mic)
|
|
|
|
{
|
|
|
|
//add
|
|
|
|
[devices addObject:@{EVENT_DEVICE:self.mic, EVENT_DEVICE_STATUS:@(self.audioActive)}];
|
|
|
|
}
|
|
|
|
|
2017-09-25 07:45:02 +01:00
|
|
|
//update status menu
|
|
|
|
// run on main thread, since its a UI update
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
|
|
//update status menu
|
|
|
|
[((AppDelegate*)[[NSApplication sharedApplication] delegate]).statusBarMenuController updateStatusItemMenu:devices];
|
|
|
|
|
|
|
|
});
|
2016-09-12 01:21:14 +01:00
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
//add timestamp
|
|
|
|
event[EVENT_TIMESTAMP] = [NSDate date];
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//add device
|
|
|
|
event[EVENT_DEVICE] = self.camera;
|
|
|
|
|
|
|
|
//set device status
|
|
|
|
event[EVENT_DEVICE_STATUS] = [NSNumber numberWithInt:self.videoActive];
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-12 01:21:14 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"got video change notification; is running? %x", self.videoActive]);
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//alloc XPC connection
|
|
|
|
xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"];
|
|
|
|
|
|
|
|
//set remote object interface
|
|
|
|
xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)];
|
|
|
|
|
|
|
|
//resume
|
|
|
|
[xpcConnection resume];
|
|
|
|
|
|
|
|
//init wait semaphore
|
|
|
|
waitSema = dispatch_semaphore_create(0);
|
|
|
|
|
|
|
|
//tell XPC about video status
|
|
|
|
// ->for example, when video is active, will stop baselining
|
|
|
|
[[xpcConnection remoteObjectProxy] updateVideoStatus:self.videoActive reply:^{
|
|
|
|
|
|
|
|
//signal sema
|
|
|
|
dispatch_semaphore_signal(waitSema);
|
|
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
//wait until XPC is done
|
|
|
|
// ->XPC reply block will signal semaphore
|
|
|
|
dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER);
|
|
|
|
|
|
|
|
//if video just started
|
|
|
|
// ->ask for video procs from XPC
|
|
|
|
if(YES == self.videoActive)
|
|
|
|
{
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-25 04:08:38 +01:00
|
|
|
logMsg(LOG_DEBUG, @"video is active, so querying XPC to get video process(s)");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//set allowed classes
|
|
|
|
[xpcConnection.remoteObjectInterface setClasses: [NSSet setWithObjects: [NSMutableArray class], [NSNumber class], nil]
|
2017-09-25 07:29:24 +01:00
|
|
|
forSelector: @selector(getVideoProcs:reply:) argumentIndex: 0 ofReply: YES];
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//invoke XPC service
|
2017-09-25 07:29:24 +01:00
|
|
|
[[xpcConnection remoteObjectProxy] getVideoProcs:NO reply:^(NSMutableArray* videoProcesses)
|
2016-09-12 01:21:14 +01:00
|
|
|
{
|
|
|
|
//close connection
|
|
|
|
[xpcConnection invalidate];
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-12 01:21:14 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"video procs from XPC: %@", videoProcesses]);
|
2017-04-09 03:18:29 +01:00
|
|
|
#endif
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//generate notification for each process
|
|
|
|
for(NSNumber* processID in videoProcesses)
|
|
|
|
{
|
|
|
|
//set pid
|
|
|
|
event[EVENT_PROCESS_ID] = processID;
|
|
|
|
|
|
|
|
//generate notification
|
2017-09-25 07:45:02 +01:00
|
|
|
// do on main thread since its a UI event
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
|
|
//generate notification
|
|
|
|
[self generateNotification:event];
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
|
|
|
|
2016-10-05 05:03:41 +01:00
|
|
|
//if no consumer process was found
|
|
|
|
// ->still alert user that webcam was activated, but without details/ability to block
|
|
|
|
if(0 == videoProcesses.count)
|
|
|
|
{
|
|
|
|
//set pid
|
|
|
|
event[EVENT_PROCESS_ID] = @0;
|
|
|
|
|
|
|
|
//generate notification
|
2017-09-25 07:45:02 +01:00
|
|
|
// do on main thread since its a UI event
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
|
|
//generate notification
|
|
|
|
[self generateNotification:event];
|
|
|
|
|
|
|
|
});
|
2016-10-05 05:03:41 +01:00
|
|
|
}
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//signal sema
|
|
|
|
dispatch_semaphore_signal(waitSema);
|
|
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
//wait until XPC is done
|
|
|
|
// ->XPC reply block will signal semaphore
|
|
|
|
dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER);
|
|
|
|
}
|
|
|
|
|
|
|
|
//video deactivated
|
|
|
|
// ->close XPC connection and alert user
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//close connection
|
|
|
|
[xpcConnection invalidate];
|
|
|
|
|
|
|
|
//generate notification
|
2017-09-25 07:45:02 +01:00
|
|
|
// do on main thread since its a UI event
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
|
|
//generate notification
|
|
|
|
[self generateNotification:event];
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//poll for new video procs
|
|
|
|
// ->this thread will exit itself as its checks the 'videoActive' iVar
|
|
|
|
if(YES == self.videoActive)
|
|
|
|
{
|
|
|
|
//start monitor thread if needed
|
|
|
|
if(YES != videoMonitorThread.isExecuting)
|
|
|
|
{
|
2016-09-25 04:08:38 +01:00
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-25 04:08:38 +01:00
|
|
|
logMsg(LOG_DEBUG, @"(re)Starting polling/monitor thread");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-25 04:08:38 +01:00
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//alloc
|
|
|
|
videoMonitorThread = [[NSThread alloc] initWithTarget:self selector:@selector(monitor4Procs) object:nil];
|
|
|
|
|
|
|
|
//start
|
|
|
|
[self.videoMonitorThread start];
|
|
|
|
}
|
2016-09-25 04:08:38 +01:00
|
|
|
//no need to restart
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-25 04:08:38 +01:00
|
|
|
logMsg(LOG_DEBUG, @"polling/monitor thread still running");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-25 04:08:38 +01:00
|
|
|
}
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}//sync
|
|
|
|
|
|
|
|
//bail
|
|
|
|
bail:
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//register for video notifcations
|
|
|
|
// ->block will invoke method on event
|
|
|
|
-(BOOL)watchVideo:(CMIOObjectID)deviceID
|
|
|
|
{
|
|
|
|
//ret var
|
|
|
|
BOOL bRegistered = NO;
|
|
|
|
|
|
|
|
//status var
|
|
|
|
OSStatus status = -1;
|
|
|
|
|
|
|
|
//property struct
|
|
|
|
CMIOObjectPropertyAddress propertyStruct = {0};
|
|
|
|
|
|
|
|
//init property struct's selector
|
|
|
|
propertyStruct.mSelector = kAudioDevicePropertyDeviceIsRunningSomewhere;
|
|
|
|
|
|
|
|
//init property struct's scope
|
|
|
|
propertyStruct.mScope = kAudioObjectPropertyScopeGlobal;
|
|
|
|
|
|
|
|
//init property struct's element
|
|
|
|
propertyStruct.mElement = kAudioObjectPropertyElementMaster;
|
|
|
|
|
|
|
|
//block
|
|
|
|
// ->invoked when video changes & just calls helper function
|
|
|
|
CMIOObjectPropertyListenerBlock listenerBlock = ^(UInt32 inNumberAddresses, const CMIOObjectPropertyAddress addresses[])
|
|
|
|
{
|
2017-09-25 07:45:02 +01:00
|
|
|
//on main thread?
|
|
|
|
// handle on background thread
|
|
|
|
if(YES == [NSThread isMainThread])
|
|
|
|
{
|
|
|
|
//invoke in background
|
|
|
|
// XPC stuff might be slow!
|
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
|
|
|
^{
|
|
|
|
|
|
|
|
//handle notification
|
|
|
|
[self handleVideoNotification:deviceID addresses:addresses];
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
//on background thread
|
|
|
|
// just can invoke as it
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//handle notification
|
|
|
|
[self handleVideoNotification:deviceID addresses:addresses];
|
|
|
|
}
|
2016-09-12 01:21:14 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
//register (add) property block listener
|
|
|
|
status = CMIOObjectAddPropertyListenerBlock(deviceID, &propertyStruct, dispatch_get_main_queue(), listenerBlock);
|
|
|
|
if(noErr != status)
|
|
|
|
{
|
|
|
|
//err msg
|
2016-09-18 22:10:44 +01:00
|
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"CMIOObjectAddPropertyListenerBlock() failed with %d", status]);
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//bail
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
|
|
|
|
//happy
|
|
|
|
bRegistered = YES;
|
|
|
|
|
|
|
|
//bail
|
|
|
|
bail:
|
|
|
|
|
|
|
|
return bRegistered;
|
|
|
|
}
|
|
|
|
|
|
|
|
//determine if audio is active
|
|
|
|
// ->sets 'audioActive' iVar
|
|
|
|
-(void)setAudioDevStatus:(AudioObjectID)deviceID
|
|
|
|
{
|
|
|
|
//status var
|
|
|
|
OSStatus status = -1;
|
|
|
|
|
|
|
|
//running flag
|
|
|
|
UInt32 isRunning = -1;
|
|
|
|
|
|
|
|
//size of query flag
|
|
|
|
UInt32 propertySize = 0;
|
|
|
|
|
|
|
|
//init size
|
|
|
|
propertySize = sizeof(isRunning);
|
|
|
|
|
|
|
|
//query to get 'kAudioDevicePropertyDeviceIsRunningSomewhere' status
|
|
|
|
status = AudioDeviceGetProperty(deviceID, 0, false, kAudioDevicePropertyDeviceIsRunningSomewhere, &propertySize, &isRunning);
|
|
|
|
if(noErr != status)
|
|
|
|
{
|
|
|
|
//err msg
|
|
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"getting status of audio device failed with %d", status]);
|
|
|
|
|
|
|
|
//set error
|
|
|
|
isRunning = -1;
|
|
|
|
|
|
|
|
//bail
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
|
|
|
|
//set iVar
|
|
|
|
self.audioActive = isRunning;
|
|
|
|
|
|
|
|
//bail
|
|
|
|
bail:
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//helper function
|
|
|
|
// ->determines if audio went active/inactive then invokes notification generator method
|
|
|
|
-(void)handleAudioNotification:(AudioObjectID)deviceID
|
|
|
|
{
|
|
|
|
//event dictionary
|
|
|
|
NSMutableDictionary* event = nil;
|
|
|
|
|
2017-09-25 07:45:02 +01:00
|
|
|
//devices
|
|
|
|
NSMutableArray* devices = nil;
|
|
|
|
|
2016-11-07 03:33:42 +00:00
|
|
|
//xpc connection
|
|
|
|
__block NSXPCConnection* xpcConnection = nil;
|
|
|
|
|
|
|
|
//wait semaphore
|
|
|
|
dispatch_semaphore_t waitSema = nil;
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//init dictionary
|
|
|
|
event = [NSMutableDictionary dictionary];
|
2017-09-25 07:45:02 +01:00
|
|
|
|
|
|
|
//init array for devices
|
|
|
|
devices = [NSMutableArray array];
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//sync
|
|
|
|
@synchronized (self)
|
|
|
|
{
|
|
|
|
|
|
|
|
//set status
|
2016-11-07 03:33:42 +00:00
|
|
|
// ->updates 'audioActive' iVar
|
2016-09-12 01:21:14 +01:00
|
|
|
[self setAudioDevStatus:deviceID];
|
|
|
|
|
2017-09-25 07:45:02 +01:00
|
|
|
//add camera
|
|
|
|
if(nil != self.camera)
|
|
|
|
{
|
|
|
|
//add
|
|
|
|
[devices addObject:@{EVENT_DEVICE:self.camera, EVENT_DEVICE_STATUS:@(self.videoActive)}];
|
|
|
|
}
|
|
|
|
|
|
|
|
//add mic
|
|
|
|
if(nil != self.mic)
|
|
|
|
{
|
|
|
|
//add
|
|
|
|
[devices addObject:@{EVENT_DEVICE:self.mic, EVENT_DEVICE_STATUS:@(self.audioActive)}];
|
|
|
|
}
|
|
|
|
|
|
|
|
//update status menu
|
|
|
|
// run on main thread, since its a UI update
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
|
|
//update status menu
|
|
|
|
[((AppDelegate*)[[NSApplication sharedApplication] delegate]).statusBarMenuController updateStatusItemMenu:devices];
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
//add timestamp
|
|
|
|
event[EVENT_TIMESTAMP] = [NSDate date];
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//add device
|
|
|
|
event[EVENT_DEVICE] = self.mic;
|
|
|
|
|
|
|
|
//set device status
|
|
|
|
event[EVENT_DEVICE_STATUS] = [NSNumber numberWithInt:self.audioActive];
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-12 01:21:14 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"got audio change notification; is running? %x", self.audioActive]);
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-11-07 03:33:42 +00:00
|
|
|
|
|
|
|
//alloc XPC connection
|
|
|
|
xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"];
|
2016-09-12 01:21:14 +01:00
|
|
|
|
2016-11-07 03:33:42 +00:00
|
|
|
//set remote object interface
|
|
|
|
xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)];
|
2017-09-25 07:45:02 +01:00
|
|
|
|
2016-11-07 03:33:42 +00:00
|
|
|
//resume
|
|
|
|
[xpcConnection resume];
|
2017-09-25 07:45:02 +01:00
|
|
|
|
2016-11-07 03:33:42 +00:00
|
|
|
//init wait semaphore
|
|
|
|
waitSema = dispatch_semaphore_create(0);
|
|
|
|
|
|
|
|
//tell XPC about audio status
|
|
|
|
// ->for example, when audio is active, will stop baselining
|
|
|
|
[[xpcConnection remoteObjectProxy] updateAudioStatus:self.audioActive reply:^{
|
2017-09-25 07:45:02 +01:00
|
|
|
|
2016-11-07 03:33:42 +00:00
|
|
|
//signal sema
|
|
|
|
dispatch_semaphore_signal(waitSema);
|
|
|
|
|
|
|
|
}];
|
2017-09-25 07:45:02 +01:00
|
|
|
|
2016-11-07 03:33:42 +00:00
|
|
|
//wait until XPC is done
|
|
|
|
// ->XPC reply block will signal semaphore
|
|
|
|
dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER);
|
2017-09-25 07:45:02 +01:00
|
|
|
|
|
|
|
//if audio just started
|
|
|
|
// ->ask for audio procs from XPC
|
2016-11-07 03:33:42 +00:00
|
|
|
if(YES == self.audioActive)
|
|
|
|
{
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-11-07 03:33:42 +00:00
|
|
|
logMsg(LOG_DEBUG, @"audio is active, so querying XPC to get audio process(s)");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-11-07 03:33:42 +00:00
|
|
|
|
|
|
|
//set allowed classes
|
|
|
|
[xpcConnection.remoteObjectInterface setClasses: [NSSet setWithObjects: [NSMutableArray class], [NSNumber class], nil]
|
|
|
|
forSelector: @selector(getAudioProcs:) argumentIndex: 0 ofReply: YES];
|
2016-09-12 01:21:14 +01:00
|
|
|
|
2016-11-07 03:33:42 +00:00
|
|
|
//invoke XPC service
|
|
|
|
[[xpcConnection remoteObjectProxy] getAudioProcs:^(NSMutableArray* audioProcesses)
|
|
|
|
{
|
|
|
|
//close connection
|
|
|
|
[xpcConnection invalidate];
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-11-07 03:33:42 +00:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"audio procs from XPC: %@", audioProcesses]);
|
2017-04-08 08:37:23 +01:00
|
|
|
#endif
|
2016-11-07 03:33:42 +00:00
|
|
|
|
|
|
|
//generate notification for each process
|
|
|
|
for(NSNumber* processID in audioProcesses)
|
|
|
|
{
|
|
|
|
//set pid
|
|
|
|
event[EVENT_PROCESS_ID] = processID;
|
|
|
|
|
|
|
|
//generate notification
|
2017-09-25 07:45:02 +01:00
|
|
|
// do on main thread since its a UI event
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
|
|
//generate notification
|
|
|
|
[self generateNotification:event];
|
|
|
|
|
|
|
|
});
|
2016-11-07 03:33:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//if no consumer process was found
|
|
|
|
// ->still alert user that webcam was activated, but without details/ability to block
|
|
|
|
if(0 == audioProcesses.count)
|
|
|
|
{
|
|
|
|
//set pid
|
|
|
|
event[EVENT_PROCESS_ID] = @0;
|
|
|
|
|
|
|
|
//generate notification
|
2017-09-25 07:45:02 +01:00
|
|
|
// do on main thread since its a UI event
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
|
|
//generate notification
|
|
|
|
[self generateNotification:event];
|
|
|
|
|
|
|
|
});
|
2016-11-07 03:33:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//signal sema
|
|
|
|
dispatch_semaphore_signal(waitSema);
|
|
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
//wait until XPC is done
|
|
|
|
// ->XPC reply block will signal semaphore
|
|
|
|
dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER);
|
|
|
|
}
|
|
|
|
|
|
|
|
//audio deactivated
|
|
|
|
// ->close XPC connection and alert user
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//close connection
|
|
|
|
[xpcConnection invalidate];
|
|
|
|
|
|
|
|
//generate notification
|
2017-09-25 07:45:02 +01:00
|
|
|
// do on main thread since its a UI event
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
|
|
//generate notification
|
|
|
|
[self generateNotification:event];
|
|
|
|
|
|
|
|
});
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
2016-11-07 03:33:42 +00:00
|
|
|
|
|
|
|
}//sync
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//bail
|
|
|
|
bail:
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//register for audio notifcations
|
|
|
|
// ->block will invoke method on event
|
|
|
|
-(BOOL)watchAudio:(AudioObjectID)deviceID
|
|
|
|
{
|
|
|
|
//ret var
|
|
|
|
BOOL bRegistered = NO;
|
|
|
|
|
2016-09-20 08:29:40 +01:00
|
|
|
//status var
|
|
|
|
OSStatus status = -1;
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//property struct
|
|
|
|
AudioObjectPropertyAddress propertyStruct = {0};
|
|
|
|
|
|
|
|
//init property struct's selector
|
|
|
|
propertyStruct.mSelector = kAudioDevicePropertyDeviceIsRunningSomewhere;
|
|
|
|
|
|
|
|
//init property struct's scope
|
|
|
|
propertyStruct.mScope = kAudioObjectPropertyScopeGlobal;
|
|
|
|
|
|
|
|
//init property struct's element
|
|
|
|
propertyStruct.mElement = kAudioObjectPropertyElementMaster;
|
|
|
|
|
|
|
|
//block
|
|
|
|
// ->invoked when audio changes & just calls helper function
|
|
|
|
AudioObjectPropertyListenerBlock listenerBlock = ^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses)
|
|
|
|
{
|
2017-09-25 07:45:02 +01:00
|
|
|
//on main thread?
|
|
|
|
// handle on background thread
|
|
|
|
if(YES == [NSThread isMainThread])
|
|
|
|
{
|
|
|
|
//invoke in background
|
|
|
|
// XPC stuff might be slow!
|
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
|
|
|
^{
|
|
|
|
|
|
|
|
//handle notification
|
|
|
|
[self handleAudioNotification:deviceID];
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
//on background thread
|
|
|
|
// just can invoke as it
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//handle notification
|
|
|
|
[self handleAudioNotification:deviceID];
|
|
|
|
}
|
2016-09-12 01:21:14 +01:00
|
|
|
};
|
|
|
|
|
2016-09-20 08:29:40 +01:00
|
|
|
//add property listener for audio changes
|
|
|
|
status = AudioObjectAddPropertyListenerBlock(deviceID, &propertyStruct, dispatch_get_main_queue(), listenerBlock);
|
|
|
|
if(noErr != status)
|
2016-09-12 01:21:14 +01:00
|
|
|
{
|
2016-09-20 08:29:40 +01:00
|
|
|
//err msg
|
|
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"AudioObjectAddPropertyListenerBlock() failed with %d", status]);
|
|
|
|
|
|
|
|
//bail
|
|
|
|
goto bail;
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
2016-09-20 08:29:40 +01:00
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//happy
|
|
|
|
bRegistered = YES;
|
|
|
|
|
2016-09-20 08:29:40 +01:00
|
|
|
//bail
|
2016-09-12 01:21:14 +01:00
|
|
|
bail:
|
|
|
|
|
|
|
|
return bRegistered;
|
|
|
|
}
|
|
|
|
|
|
|
|
//build and display notification
|
2017-03-28 09:00:11 +01:00
|
|
|
// ->handles extra logic like ignore whitelisted apps, disable alerts (if user has turned that off), etc
|
|
|
|
-(void)generateNotification:(NSMutableDictionary*)event
|
2016-09-12 01:21:14 +01:00
|
|
|
{
|
2017-09-25 07:45:02 +01:00
|
|
|
//pool
|
|
|
|
@autoreleasepool {
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//notification
|
|
|
|
NSUserNotification* notification = nil;
|
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
//device
|
|
|
|
// ->audio or video
|
|
|
|
NSNumber* deviceType = nil;
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//title
|
|
|
|
NSMutableString* title = nil;
|
|
|
|
|
|
|
|
//details
|
2016-09-25 04:08:38 +01:00
|
|
|
// ->just name of device for now
|
|
|
|
NSString* details = nil;
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//process name
|
|
|
|
NSString* processName = nil;
|
|
|
|
|
2017-03-28 09:00:11 +01:00
|
|
|
//process path
|
|
|
|
NSString* processPath = nil;
|
|
|
|
|
2016-09-13 08:27:45 +01:00
|
|
|
//log msg
|
2017-04-13 10:29:18 +01:00
|
|
|
NSMutableString* logMessage = nil;
|
2016-09-13 08:27:45 +01:00
|
|
|
|
2016-09-20 08:29:40 +01:00
|
|
|
//preferences
|
|
|
|
NSDictionary* preferences = nil;
|
|
|
|
|
2017-04-03 00:12:42 +01:00
|
|
|
//flag
|
2017-04-09 03:18:29 +01:00
|
|
|
// ->was an activation alert shown
|
2017-04-03 00:12:42 +01:00
|
|
|
BOOL matchingActivationAlert = NO;
|
|
|
|
|
2017-04-09 03:18:29 +01:00
|
|
|
//flag
|
|
|
|
// ->is the activation alert still visible
|
|
|
|
BOOL activationAlertVisible = NO;
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//alloc notificaiton
|
|
|
|
notification = [[NSUserNotification alloc] init];
|
|
|
|
|
|
|
|
//alloc title
|
|
|
|
title = [NSMutableString string];
|
|
|
|
|
2016-09-13 08:27:45 +01:00
|
|
|
//alloc log msg
|
2017-04-13 10:29:18 +01:00
|
|
|
logMessage = [NSMutableString string];
|
2016-09-13 08:27:45 +01:00
|
|
|
|
2017-03-28 09:00:11 +01:00
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2017-03-28 09:00:11 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"generating notification for %@", event]);
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2017-03-28 09:00:11 +01:00
|
|
|
|
|
|
|
//get process name
|
|
|
|
processName = getProcessName([event[EVENT_PROCESS_ID] intValue]);
|
|
|
|
|
|
|
|
//get process path
|
|
|
|
processPath = getProcessPath([event[EVENT_PROCESS_ID] intValue]);
|
|
|
|
if(nil == processPath)
|
|
|
|
{
|
|
|
|
//set to something
|
|
|
|
processPath = PROCESS_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
|
|
|
//set device and title for audio
|
|
|
|
if(YES == [event[EVENT_DEVICE] isKindOfClass:NSClassFromString(@"AVCaptureHALDevice")])
|
|
|
|
{
|
|
|
|
//add
|
|
|
|
[title appendString:@"Audio Device"];
|
|
|
|
|
|
|
|
//set device
|
|
|
|
deviceType = SOURCE_AUDIO;
|
|
|
|
}
|
|
|
|
//set device and title for video
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//add
|
|
|
|
[title appendString:@"Video Device"];
|
|
|
|
|
|
|
|
//set device
|
|
|
|
deviceType = SOURCE_VIDEO;
|
|
|
|
}
|
|
|
|
|
|
|
|
//ignore whitelisted processes
|
|
|
|
// ->for activation events, can check process path
|
2017-04-08 08:37:23 +01:00
|
|
|
if(YES == [DEVICE_ACTIVE isEqual:event[EVENT_DEVICE_STATUS]])
|
2017-03-28 09:00:11 +01:00
|
|
|
{
|
2017-04-08 08:37:23 +01:00
|
|
|
//check each
|
|
|
|
// ->need match on path and device (camera || mic)
|
|
|
|
for(NSDictionary* item in self.whiteList)
|
|
|
|
{
|
|
|
|
//check path & device
|
|
|
|
if( (YES == [item[EVENT_PROCESS_PATH] isEqualToString:processPath]) &&
|
|
|
|
([item[EVENT_DEVICE] intValue] == deviceType.intValue) )
|
|
|
|
{
|
|
|
|
//dbg msg
|
|
|
|
#ifdef DEBUG
|
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"activation alert for process %@ is whitelisted, so ignoring", item]);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//bail
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
}
|
2017-03-28 09:00:11 +01:00
|
|
|
}
|
|
|
|
//ignore whitelisted processes
|
|
|
|
// ->for deactivation, ignore when no activation alert was shown (cuz process will have likely died, so no pid/path, etc)
|
|
|
|
if(YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]])
|
|
|
|
{
|
2017-04-03 00:12:42 +01:00
|
|
|
//any active alerts?
|
|
|
|
// ->note: we make to check if the type matches
|
|
|
|
for(NSUUID* key in self.activationAlerts.allKeys)
|
2017-03-28 09:00:11 +01:00
|
|
|
{
|
2017-04-03 00:12:42 +01:00
|
|
|
//same type?
|
|
|
|
if(event[EVENT_DEVICE] == self.activationAlerts[key][EVENT_DEVICE])
|
|
|
|
{
|
|
|
|
//got match
|
|
|
|
matchingActivationAlert = YES;
|
|
|
|
|
|
|
|
//no need to check more
|
|
|
|
break;
|
|
|
|
}
|
2017-03-28 09:00:11 +01:00
|
|
|
}
|
|
|
|
|
2017-04-03 00:12:42 +01:00
|
|
|
//no match?
|
|
|
|
// ->bail, as means process was whitelisted so no activation was shown
|
|
|
|
if(YES != matchingActivationAlert)
|
2017-03-28 09:00:11 +01:00
|
|
|
{
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
|
|
|
logMsg(LOG_DEBUG, @"deactivation alert doesn't have an activation alert (whitelisted?), so ignoring");
|
|
|
|
#endif
|
|
|
|
|
2017-03-28 09:00:11 +01:00
|
|
|
//bail
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
//check if event is essentially a duplicate (facetime, etc)
|
|
|
|
if(nil != self.lastEvent)
|
|
|
|
{
|
|
|
|
//less than 10 second ago?
|
|
|
|
if(fabs([self.lastEvent[EVENT_TIMESTAMP] timeIntervalSinceDate:event[EVENT_TIMESTAMP]]) < 10)
|
|
|
|
{
|
|
|
|
//same process/device/action
|
|
|
|
if( (YES == [self.lastEvent[EVENT_PROCESS_ID] isEqual:event[EVENT_PROCESS_ID]]) &&
|
|
|
|
(YES == [self.lastEvent[EVENT_DEVICE] isEqual:event[EVENT_DEVICE]]) &&
|
|
|
|
(YES == [self.lastEvent[EVENT_DEVICE_STATUS] isEqual:event[EVENT_DEVICE_STATUS]]) )
|
|
|
|
{
|
2017-03-28 09:00:11 +01:00
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2017-03-28 09:00:11 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"alert for %@ would be same as previous (%@), so ignoring", event, self.lastEvent]);
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2017-03-28 09:00:11 +01:00
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
//update
|
|
|
|
self.lastEvent = event;
|
|
|
|
|
|
|
|
//bail to ignore
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
}
|
2017-03-28 09:00:11 +01:00
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
}//'same' event check
|
|
|
|
|
|
|
|
//update last event
|
|
|
|
self.lastEvent = event;
|
|
|
|
|
2016-09-20 08:29:40 +01:00
|
|
|
//always (manually) load preferences
|
|
|
|
preferences = [NSDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]];
|
|
|
|
|
2016-12-19 02:38:47 +00:00
|
|
|
//check if user wants to ingnore inactive alerts
|
|
|
|
if( (YES == [preferences[PREF_DISABLE_INACTIVE] boolValue]) &&
|
|
|
|
(YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) )
|
|
|
|
{
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2017-03-28 09:00:11 +01:00
|
|
|
logMsg(LOG_DEBUG, @"user has decided to ingore 'inactive' events, so ingoring A/V going to disable state");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-12-19 02:38:47 +00:00
|
|
|
|
|
|
|
//bail
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//add action
|
|
|
|
// ->device went inactive
|
|
|
|
if(YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]])
|
|
|
|
{
|
|
|
|
//add
|
|
|
|
[title appendString:@" became inactive"];
|
|
|
|
}
|
|
|
|
//add action
|
|
|
|
// ->device went active
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//add
|
|
|
|
[title appendString:@" became active"];
|
|
|
|
}
|
|
|
|
|
2016-09-20 08:29:40 +01:00
|
|
|
//set details
|
|
|
|
// ->name of device
|
|
|
|
details = ((AVCaptureDevice*)event[EVENT_DEVICE]).localizedName;
|
|
|
|
|
2017-04-03 00:12:42 +01:00
|
|
|
//ALERT 1:
|
|
|
|
// ->activate alert with lots of info
|
|
|
|
if( (YES == [DEVICE_ACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) &&
|
|
|
|
(0 != [event[EVENT_PROCESS_ID] intValue]) )
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
{
|
|
|
|
//set other button title
|
2016-12-21 07:38:26 +00:00
|
|
|
notification.otherButtonTitle = @"allow";
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//set action title
|
|
|
|
notification.actionButtonTitle = @"block";
|
|
|
|
|
2017-04-03 00:12:42 +01:00
|
|
|
//remove action button
|
|
|
|
notification.hasActionButton = YES;
|
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
//set pid/name/device into user info
|
|
|
|
// ->allows code to whitelist proc and/or kill proc (later) if user clicks 'block'
|
2017-04-03 00:12:42 +01:00
|
|
|
notification.userInfo = @{EVENT_PROCESS_ID:event[EVENT_PROCESS_ID], EVENT_PROCESS_PATH:processPath, EVENT_PROCESS_NAME:processName, EVENT_DEVICE:deviceType, EVENT_ALERT_TYPE:ALERT_ACTIVATE};
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//set details
|
|
|
|
// ->name of process using it / icon too?
|
2016-09-18 22:10:44 +01:00
|
|
|
[notification setInformativeText:[NSString stringWithFormat:@"process: %@ (%@)", processName, event[EVENT_PROCESS_ID]]];
|
2016-09-13 08:27:45 +01:00
|
|
|
}
|
2017-04-03 00:12:42 +01:00
|
|
|
|
|
|
|
//ALERT 2:
|
|
|
|
// ->activate alert, with no process info
|
|
|
|
else if( (YES == [DEVICE_ACTIVE isEqual:event[EVENT_DEVICE_STATUS]]) &&
|
2017-04-09 03:18:29 +01:00
|
|
|
(0 == [event[EVENT_PROCESS_ID] intValue]) )
|
2017-04-03 00:12:42 +01:00
|
|
|
{
|
|
|
|
//set other button title
|
|
|
|
notification.otherButtonTitle = @"ok";
|
|
|
|
|
|
|
|
//remove action button
|
|
|
|
notification.hasActionButton = NO;
|
|
|
|
|
|
|
|
//set type
|
|
|
|
notification.userInfo = @{EVENT_DEVICE:deviceType, EVENT_ALERT_TYPE:ALERT_ACTIVATE};
|
|
|
|
}
|
|
|
|
|
|
|
|
//ALERT 3:
|
|
|
|
// ->inactive alert, with no process info
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//set other button title
|
|
|
|
notification.otherButtonTitle = @"ok";
|
|
|
|
|
|
|
|
//remove action button
|
|
|
|
notification.hasActionButton = NO;
|
|
|
|
|
|
|
|
//set type
|
|
|
|
notification.userInfo = @{EVENT_DEVICE:deviceType, EVENT_ALERT_TYPE:ALERT_INACTIVE};
|
|
|
|
}
|
|
|
|
|
2016-09-13 08:27:45 +01:00
|
|
|
//log event?
|
2016-09-20 08:29:40 +01:00
|
|
|
if(YES == [preferences[PREF_LOG_ACTIVITY] boolValue])
|
2016-09-13 08:27:45 +01:00
|
|
|
{
|
|
|
|
//no process?
|
|
|
|
// ->just add title / details
|
2017-03-28 09:00:11 +01:00
|
|
|
if( (nil == processName) ||
|
|
|
|
(YES == [processName isEqualToString:PROCESS_UNKNOWN]) )
|
2016-09-13 08:27:45 +01:00
|
|
|
{
|
|
|
|
//add
|
2017-04-13 10:29:18 +01:00
|
|
|
[logMessage appendFormat:@"%@ (%@)", title, details];
|
2016-09-13 08:27:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//process
|
2017-03-28 09:00:11 +01:00
|
|
|
// ->add title / details / process name / process path
|
2016-09-13 08:27:45 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
//add
|
2017-04-13 10:29:18 +01:00
|
|
|
[logMessage appendFormat:@"%@ (%@, process: %@, %@)", title, details, processName, processPath];
|
2016-09-13 08:27:45 +01:00
|
|
|
}
|
|
|
|
|
2017-04-13 10:29:18 +01:00
|
|
|
//log msg
|
|
|
|
logMsg(LOG_DEBUG|LOG_TO_FILE, logMessage);
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//set title
|
|
|
|
[notification setTitle:title];
|
|
|
|
|
|
|
|
//set subtitle
|
2016-09-20 08:29:40 +01:00
|
|
|
[notification setSubtitle:details];
|
2016-09-12 01:21:14 +01:00
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
//set id
|
|
|
|
notification.identifier = [[NSUUID UUID] UUIDString];
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//set notification
|
|
|
|
[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self];
|
|
|
|
|
|
|
|
//deliver notification
|
|
|
|
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
|
|
|
|
|
2017-04-03 00:12:42 +01:00
|
|
|
//save displayed activation alerts
|
2017-03-28 09:00:11 +01:00
|
|
|
if(YES == [DEVICE_ACTIVE isEqual:event[EVENT_DEVICE_STATUS]])
|
|
|
|
{
|
2017-04-03 00:12:42 +01:00
|
|
|
//save
|
|
|
|
self.activationAlerts[notification.identifier] = event;
|
2017-03-28 09:00:11 +01:00
|
|
|
}
|
2017-04-03 00:12:42 +01:00
|
|
|
|
|
|
|
//for 'went inactive' notification automatically close
|
|
|
|
// ->unless there still is an active alert on the screen
|
|
|
|
if(YES == [DEVICE_INACTIVE isEqual:event[EVENT_DEVICE_STATUS]])
|
2017-03-28 09:00:11 +01:00
|
|
|
{
|
2017-04-03 00:12:42 +01:00
|
|
|
//any active alerts still visible?
|
2017-04-09 03:18:29 +01:00
|
|
|
// ->note: we make to check if the type matches and still visible
|
2017-04-03 00:12:42 +01:00
|
|
|
for(NSUUID* key in self.activationAlerts.allKeys)
|
2017-03-28 09:00:11 +01:00
|
|
|
{
|
2017-04-09 03:18:29 +01:00
|
|
|
//same type
|
|
|
|
if( (event[EVENT_DEVICE] == self.activationAlerts[key][EVENT_DEVICE]) &&
|
|
|
|
(YES != [self.activationAlerts[key][EVENT_ALERT_CLOSED] boolValue]) )
|
2017-04-03 00:12:42 +01:00
|
|
|
{
|
2017-04-09 03:18:29 +01:00
|
|
|
//set flag
|
|
|
|
activationAlertVisible = YES;
|
2017-04-03 00:12:42 +01:00
|
|
|
|
|
|
|
//no need to check more
|
|
|
|
break;
|
|
|
|
}
|
2017-03-28 09:00:11 +01:00
|
|
|
}
|
2017-04-03 00:12:42 +01:00
|
|
|
|
2017-04-09 03:18:29 +01:00
|
|
|
//auto close if activation alert isn't still visible
|
|
|
|
if(YES != activationAlertVisible)
|
2017-03-28 09:00:11 +01:00
|
|
|
{
|
2017-04-03 00:12:42 +01:00
|
|
|
//dbg msg
|
|
|
|
#ifdef DEBUG
|
|
|
|
logMsg(LOG_DEBUG, @"automatically closing deactivation alert");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//delay, then close
|
2017-09-25 07:29:24 +01:00
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
2017-04-03 00:12:42 +01:00
|
|
|
|
|
|
|
//close
|
|
|
|
[NSUserNotificationCenter.defaultUserNotificationCenter removeDeliveredNotification:notification];
|
|
|
|
|
|
|
|
});
|
2017-03-28 09:00:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-25 07:45:02 +01:00
|
|
|
}//pool
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//bail
|
|
|
|
bail:
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//always present notifications
|
|
|
|
-(BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification
|
|
|
|
{
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
//automatically invoked when user interacts w/ the notification popup
|
2016-11-14 19:12:19 +00:00
|
|
|
// ->handle rule creation, blocking/killing proc, etc
|
2016-09-12 01:21:14 +01:00
|
|
|
-(void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
|
|
|
|
{
|
2017-09-25 07:45:02 +01:00
|
|
|
//pool
|
|
|
|
@autoreleasepool {
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//xpc connection
|
|
|
|
__block NSXPCConnection* xpcConnection = nil;
|
|
|
|
|
|
|
|
//process id
|
|
|
|
NSNumber* processID = nil;
|
|
|
|
|
2017-04-03 00:12:42 +01:00
|
|
|
//device type
|
|
|
|
NSNumber* deviceType = nil;
|
|
|
|
|
2016-10-05 05:03:41 +01:00
|
|
|
//preferences
|
|
|
|
NSDictionary* preferences = nil;
|
|
|
|
|
|
|
|
//log msg
|
2017-04-13 10:29:18 +01:00
|
|
|
NSMutableString* logMessage = nil;
|
2016-10-05 05:03:41 +01:00
|
|
|
|
2017-09-25 07:29:24 +01:00
|
|
|
//remember popup/window controller
|
|
|
|
RememberWindowController* rememberWindowController = nil;
|
|
|
|
|
2016-10-05 05:03:41 +01:00
|
|
|
//always (manually) load preferences
|
|
|
|
preferences = [NSDictionary dictionaryWithContentsOfFile:[APP_PREFERENCES stringByExpandingTildeInPath]];
|
|
|
|
|
|
|
|
//alloc log msg
|
2017-04-13 10:29:18 +01:00
|
|
|
logMessage = [NSMutableString string];
|
2016-10-05 05:03:41 +01:00
|
|
|
|
2016-11-14 19:12:19 +00:00
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-11-14 19:12:19 +00:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"user responded to notification: %@", notification]);
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-11-14 19:12:19 +00:00
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
//ignore if this notification was already seen
|
|
|
|
// ->need this logic, since have to determine if 'allow' was invoke indirectly
|
|
|
|
if(nil != self.lastNotification)
|
|
|
|
{
|
|
|
|
//same?
|
|
|
|
if(YES == [self.lastNotification isEqualToString:notification.identifier])
|
|
|
|
{
|
|
|
|
//update
|
|
|
|
self.lastNotification = notification.identifier;
|
|
|
|
|
|
|
|
//ignore
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-03 00:12:42 +01:00
|
|
|
//when it's a deactivation alert
|
|
|
|
// ->remove all activation alerts that match the same type
|
|
|
|
if(ALERT_INACTIVE.intValue == [notification.userInfo[EVENT_ALERT_TYPE] intValue])
|
|
|
|
{
|
|
|
|
//remove any active alerts
|
2017-04-09 03:18:29 +01:00
|
|
|
// ->note: we make sure to check if the type matches
|
2017-04-03 00:12:42 +01:00
|
|
|
for(NSUUID* key in self.activationAlerts.allKeys)
|
|
|
|
{
|
|
|
|
//check stored activation alert type
|
|
|
|
// ->audio
|
|
|
|
if(YES == [self.activationAlerts[key][EVENT_DEVICE] isKindOfClass:NSClassFromString(@"AVCaptureHALDevice")])
|
|
|
|
{
|
|
|
|
//set device
|
|
|
|
deviceType = SOURCE_AUDIO;
|
|
|
|
}
|
|
|
|
//check stored activation alert type
|
|
|
|
// ->video
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//set device
|
|
|
|
deviceType = SOURCE_VIDEO;
|
|
|
|
}
|
|
|
|
|
|
|
|
//same type?
|
2017-04-09 03:18:29 +01:00
|
|
|
if([notification.userInfo[EVENT_DEVICE] intValue] == deviceType.intValue)
|
2017-04-03 00:12:42 +01:00
|
|
|
{
|
|
|
|
//remove
|
|
|
|
[self.activationAlerts removeObjectForKey:key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-09 03:18:29 +01:00
|
|
|
//activation alert that user has interacted with
|
|
|
|
// ->this means it's going to close, to we indicate that
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//set flag
|
|
|
|
self.activationAlerts[notification.identifier][EVENT_ALERT_CLOSED] = @YES;
|
|
|
|
}
|
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
//update
|
|
|
|
self.lastNotification = notification.identifier;
|
|
|
|
|
2016-11-14 19:12:19 +00:00
|
|
|
//for alerts without an action
|
|
|
|
// ->don't need to do anything!
|
|
|
|
if(YES != notification.hasActionButton)
|
|
|
|
{
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2017-03-28 09:00:11 +01:00
|
|
|
logMsg(LOG_DEBUG, @"popup without an action, no need to do anything");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-11-14 19:12:19 +00:00
|
|
|
|
|
|
|
//bail
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
|
|
|
|
//log user's response?
|
|
|
|
if(YES == [preferences[PREF_LOG_ACTIVITY] boolValue])
|
2016-10-05 05:03:41 +01:00
|
|
|
{
|
|
|
|
//user clicked 'block'
|
|
|
|
if(notification.activationType == NSUserNotificationActivationTypeActionButtonClicked)
|
|
|
|
{
|
|
|
|
//add
|
2017-04-13 10:29:18 +01:00
|
|
|
[logMessage appendFormat:@"user clicked 'block' for %@", notification.userInfo];
|
2016-10-05 05:03:41 +01:00
|
|
|
}
|
|
|
|
//user clicked 'allow'
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//add
|
2017-04-13 10:29:18 +01:00
|
|
|
[logMessage appendFormat:@"user clicked 'allow' for %@", notification.userInfo];
|
2016-10-05 05:03:41 +01:00
|
|
|
}
|
|
|
|
|
2017-04-13 10:29:18 +01:00
|
|
|
//log
|
|
|
|
logMsg(LOG_DEBUG|LOG_TO_FILE, logMessage);
|
2016-10-05 05:03:41 +01:00
|
|
|
}
|
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
//check if user clicked 'allow' via user info (since OS doesn't directly deliver this)
|
2017-04-03 00:12:42 +01:00
|
|
|
// ->show a popup w/ option to remember ('whitelist') the application
|
|
|
|
// don't do this for 'block' since that kills the app, so obv, that'd be bad to *always* do!
|
|
|
|
if(NSUserNotificationActivationTypeAdditionalActionClicked == [notification.userInfo[@"activationType"] integerValue])
|
2016-11-14 19:12:19 +00:00
|
|
|
{
|
2017-03-28 09:00:11 +01:00
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2017-03-28 09:00:11 +01:00
|
|
|
logMsg(LOG_DEBUG, @"user clicked 'allow'");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2017-03-28 09:00:11 +01:00
|
|
|
|
|
|
|
//can't remember process that we didn't find the path for
|
|
|
|
if(YES == [notification.userInfo[EVENT_PROCESS_PATH] isEqualToString:PROCESS_UNKNOWN])
|
|
|
|
{
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2017-03-28 09:00:11 +01:00
|
|
|
logMsg(LOG_DEBUG, @"don't have a process path, so not displaying whitelisting popup");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2017-03-28 09:00:11 +01:00
|
|
|
|
|
|
|
//bail
|
|
|
|
goto bail;
|
|
|
|
}
|
2017-09-25 07:29:24 +01:00
|
|
|
|
|
|
|
//alloc/init
|
|
|
|
rememberWindowController = [[RememberWindowController alloc] initWithWindowNibName:@"RememberPopup"];
|
2016-12-21 07:38:26 +00:00
|
|
|
|
|
|
|
//center window
|
2017-09-25 07:29:24 +01:00
|
|
|
[[rememberWindowController window] center];
|
2016-12-21 07:38:26 +00:00
|
|
|
|
|
|
|
//show it
|
2017-09-25 07:29:24 +01:00
|
|
|
[rememberWindowController showWindow:self];
|
2016-12-21 07:38:26 +00:00
|
|
|
|
|
|
|
//manually configure
|
|
|
|
// ->invoke here as the outlets will be set
|
2017-09-25 07:29:24 +01:00
|
|
|
[rememberWindowController configure:notification avMonitor:self];
|
2016-12-21 07:38:26 +00:00
|
|
|
|
|
|
|
//make it key window
|
2017-09-25 07:29:24 +01:00
|
|
|
[rememberWindowController.window makeKeyAndOrderFront:self];
|
2016-12-21 07:38:26 +00:00
|
|
|
|
|
|
|
//make window front
|
|
|
|
[NSApp activateIgnoringOtherApps:YES];
|
2017-09-25 07:29:24 +01:00
|
|
|
|
|
|
|
//save reference to window
|
|
|
|
// ->otherwise memory is freed/window not shown :/
|
|
|
|
@synchronized (self)
|
|
|
|
{
|
|
|
|
//save
|
|
|
|
[self.rememberPopups addObject:rememberWindowController];
|
|
|
|
}
|
2016-11-14 19:12:19 +00:00
|
|
|
}
|
2016-09-12 01:21:14 +01:00
|
|
|
|
2016-11-14 19:12:19 +00:00
|
|
|
//when user clicks 'block'
|
|
|
|
// ->kill the process to block it
|
2016-12-21 07:38:26 +00:00
|
|
|
else if(NSUserNotificationActivationTypeActionButtonClicked == notification.activationType)
|
2016-09-12 01:21:14 +01:00
|
|
|
{
|
2016-11-14 19:12:19 +00:00
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-11-14 19:12:19 +00:00
|
|
|
logMsg(LOG_DEBUG, @"user clicked 'block'");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-11-14 19:12:19 +00:00
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//extract process id
|
|
|
|
processID = notification.userInfo[EVENT_PROCESS_ID];
|
|
|
|
if(nil == processID)
|
|
|
|
{
|
|
|
|
//err msg
|
|
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to extract process id from notification, %@", notification.userInfo]);
|
|
|
|
|
|
|
|
//bail
|
|
|
|
goto bail;
|
|
|
|
}
|
2016-11-14 19:12:19 +00:00
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//alloc XPC connection
|
|
|
|
xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"];
|
|
|
|
|
|
|
|
//set remote object interface
|
|
|
|
xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)];
|
|
|
|
|
|
|
|
//resume
|
|
|
|
[xpcConnection resume];
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-12 01:21:14 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"invoking XPC method to kill: %@", processID]);
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//invoke XPC method 'killProcess' to terminate
|
|
|
|
[[xpcConnection remoteObjectProxy] killProcess:processID reply:^(BOOL wasKilled)
|
2016-11-14 19:12:19 +00:00
|
|
|
{
|
|
|
|
//check for err
|
|
|
|
if(YES != wasKilled)
|
|
|
|
{
|
|
|
|
//err msg
|
|
|
|
logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to kill/block: %@", processID]);
|
|
|
|
}
|
|
|
|
|
|
|
|
//close connection
|
|
|
|
[xpcConnection invalidate];
|
|
|
|
|
|
|
|
}];
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
}//user clicked 'block'
|
2017-09-25 07:45:02 +01:00
|
|
|
|
|
|
|
}//pool
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//bail
|
|
|
|
bail:
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
//manually monitor delivered notifications to see if user closes alert
|
|
|
|
// ->can't detect 'allow' otherwise :/ (see: http://stackoverflow.com/questions/21110714/mac-os-x-nsusernotificationcenter-notification-get-dismiss-event-callback)
|
|
|
|
-(void)userNotificationCenter:(NSUserNotificationCenter *)center didDeliverNotification:(NSUserNotification *)notification
|
|
|
|
{
|
|
|
|
//flag
|
|
|
|
__block BOOL notificationStillPresent;
|
|
|
|
|
|
|
|
//user dictionary
|
|
|
|
__block NSMutableDictionary* userInfo = nil;
|
2017-04-03 00:12:42 +01:00
|
|
|
|
|
|
|
//monitor in background to see if alert was dismissed
|
|
|
|
// ->invokes normal 'didActivateNotification' callback when alert is dimsissed
|
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
|
|
|
^{
|
|
|
|
//monitor all delivered notifications until it goes away
|
|
|
|
do {
|
|
|
|
|
|
|
|
//reset
|
|
|
|
notificationStillPresent = NO;
|
|
|
|
|
|
|
|
//check all delivered notifications
|
|
|
|
for (NSUserNotification *nox in [[NSUserNotificationCenter defaultUserNotificationCenter] deliveredNotifications])
|
|
|
|
{
|
|
|
|
//check
|
|
|
|
if(YES == [nox.identifier isEqualToString:notification.identifier])
|
2016-12-21 07:38:26 +00:00
|
|
|
{
|
2017-04-03 00:12:42 +01:00
|
|
|
//found!
|
|
|
|
notificationStillPresent = YES;
|
|
|
|
|
|
|
|
//exit loop
|
|
|
|
break;
|
2016-12-21 07:38:26 +00:00
|
|
|
}
|
2017-04-03 00:12:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//nap if notification is still there
|
|
|
|
if(YES == notificationStillPresent)
|
|
|
|
{
|
|
|
|
//nap
|
|
|
|
[NSThread sleepForTimeInterval:0.25f];
|
|
|
|
}
|
|
|
|
|
|
|
|
//keep monitoring until its gone
|
|
|
|
} while(YES == notificationStillPresent);
|
|
|
|
|
|
|
|
//alert was dismissed
|
|
|
|
// ->invoke 'didActivateNotification' to process if it was an 'allow/block' alert
|
|
|
|
dispatch_async(dispatch_get_main_queue(),
|
|
|
|
^{
|
|
|
|
//add extra info for activation alerts
|
|
|
|
// ->will allow callback to identify we delivered via this mechanism
|
|
|
|
if(YES == notification.hasActionButton)
|
2016-12-21 07:38:26 +00:00
|
|
|
{
|
|
|
|
//grab user info dictionary
|
|
|
|
userInfo = [notification.userInfo mutableCopy];
|
|
|
|
|
|
|
|
//add activation type
|
|
|
|
userInfo[@"activationType"] = [NSNumber numberWithInteger:NSUserNotificationActivationTypeAdditionalActionClicked];
|
|
|
|
|
|
|
|
//update
|
|
|
|
notification.userInfo = userInfo;
|
2017-04-03 00:12:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//deliver
|
|
|
|
[self userNotificationCenter:center didActivateNotification:notification];
|
2016-12-21 07:38:26 +00:00
|
|
|
});
|
2017-04-03 00:12:42 +01:00
|
|
|
});
|
|
|
|
|
2016-12-21 07:38:26 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//monitor for new procs (video only at the moment)
|
2017-09-25 07:45:02 +01:00
|
|
|
// runs until video is no longer in use (set elsewhere)
|
2016-09-12 01:21:14 +01:00
|
|
|
-(void)monitor4Procs
|
|
|
|
{
|
2017-09-25 07:45:02 +01:00
|
|
|
|
|
|
|
//pool
|
|
|
|
@autoreleasepool {
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//xpc connection
|
|
|
|
NSXPCConnection* xpcConnection = nil;
|
|
|
|
|
|
|
|
//wait semaphore
|
|
|
|
dispatch_semaphore_t waitSema = nil;
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-25 04:08:38 +01:00
|
|
|
logMsg(LOG_DEBUG, @"[MONITOR THREAD] video is active, so polling for new procs");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//poll while video is active
|
|
|
|
while(YES == self.videoActive)
|
|
|
|
{
|
2017-09-25 07:45:02 +01:00
|
|
|
//pool
|
|
|
|
@autoreleasepool {
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
//init wait semaphore
|
|
|
|
waitSema = dispatch_semaphore_create(0);
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-25 04:08:38 +01:00
|
|
|
logMsg(LOG_DEBUG, @"[MONITOR THREAD] (re)Asking XPC for (new) video procs");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2017-09-25 07:45:02 +01:00
|
|
|
|
|
|
|
//alloc XPC connection
|
|
|
|
xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.objective-see.OverSightXPC"];
|
|
|
|
|
|
|
|
//set remote object interface
|
|
|
|
xpcConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCProtocol)];
|
|
|
|
|
|
|
|
//set classes
|
|
|
|
// ->arrays/numbers ok to vend
|
|
|
|
[xpcConnection.remoteObjectInterface setClasses: [NSSet setWithObjects: [NSMutableArray class], [NSNumber class], nil]
|
|
|
|
forSelector: @selector(getVideoProcs:reply:) argumentIndex: 0 ofReply: YES];
|
|
|
|
//resume
|
|
|
|
[xpcConnection resume];
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//invoke XPC service to get (new) video procs
|
|
|
|
// ->will generate user notifications for any new processes
|
2017-09-25 07:29:24 +01:00
|
|
|
[[xpcConnection remoteObjectProxy] getVideoProcs:YES reply:^(NSMutableArray* videoProcesses)
|
2016-09-12 01:21:14 +01:00
|
|
|
{
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-25 04:08:38 +01:00
|
|
|
logMsg(LOG_DEBUG, [NSString stringWithFormat:@"[MONITOR THREAD] found %lu new video procs: %@", (unsigned long)videoProcesses.count, videoProcesses]);
|
2017-09-25 07:45:02 +01:00
|
|
|
#endif
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//generate a notification for each process
|
|
|
|
// ->double check video is still active though...
|
|
|
|
for(NSNumber* processID in videoProcesses)
|
|
|
|
{
|
|
|
|
//check video
|
|
|
|
if(YES != self.videoActive)
|
|
|
|
{
|
|
|
|
//exit loop
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
//generate notification
|
2017-03-28 09:00:11 +01:00
|
|
|
[self generateNotification:[@{EVENT_TIMESTAMP:[NSDate date], EVENT_DEVICE:self.camera, EVENT_DEVICE_STATUS:DEVICE_ACTIVE, EVENT_PROCESS_ID:processID} mutableCopy]];
|
2016-09-12 01:21:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//signal sema
|
|
|
|
dispatch_semaphore_signal(waitSema);
|
|
|
|
|
2017-09-25 07:45:02 +01:00
|
|
|
//invalidate
|
|
|
|
[xpcConnection invalidate];
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
}];
|
|
|
|
|
|
|
|
//wait until XPC is done
|
|
|
|
// ->XPC reply block will signal semaphore
|
|
|
|
dispatch_semaphore_wait(waitSema, DISPATCH_TIME_FOREVER);
|
2017-09-25 07:45:02 +01:00
|
|
|
|
|
|
|
}//pool
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
//nap
|
|
|
|
[NSThread sleepForTimeInterval:5.0f];
|
|
|
|
|
|
|
|
}//run until video (camera) is off
|
|
|
|
|
2017-09-25 07:45:02 +01:00
|
|
|
//pool
|
|
|
|
}
|
|
|
|
|
2016-09-12 01:21:14 +01:00
|
|
|
bail:
|
|
|
|
|
|
|
|
//dbg msg
|
2017-04-03 00:12:42 +01:00
|
|
|
#ifdef DEBUG
|
2016-09-25 04:08:38 +01:00
|
|
|
logMsg(LOG_DEBUG, @"[MONITOR THREAD] exiting polling/monitor thread since camera is off");
|
2017-04-03 00:12:42 +01:00
|
|
|
#endif
|
2016-09-12 01:21:14 +01:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|