Update AVMonitor.m

improved 10.15 support
This commit is contained in:
Patrick Wardle 2021-05-10 11:11:18 -04:00
parent f2490a083f
commit a738de0b0c
1 changed files with 200 additions and 105 deletions

View File

@ -280,7 +280,6 @@ extern os_log_t logHandle;
return; return;
} }
//TODO: log info mode ...more info?
//on Intel systems //on Intel systems
// monitor for video events via 'VDCAssistant' // monitor for video events via 'VDCAssistant'
-(void)monitorIntel -(void)monitorIntel
@ -512,7 +511,17 @@ extern os_log_t logHandle;
NSRegularExpression* regex = nil; NSRegularExpression* regex = nil;
//init regex //init regex
//macOS 10.16 (11)
if(@available(macOS 10.16, *))
{
//init
regex = [NSRegularExpression regularExpressionWithPattern:@"pid:(\\d*)," options:0 error:nil]; regex = [NSRegularExpression regularExpressionWithPattern:@"pid:(\\d*)," options:0 error:nil];
}
//macOS 10.15
else
{
regex = [NSRegularExpression regularExpressionWithPattern:@"0x([a-fA-F0-9]){20,}" options:0 error:nil];
}
//find built-in mic //find built-in mic
builtInMic = [self findBuiltInMic]; builtInMic = [self findBuiltInMic];
@ -535,6 +544,9 @@ extern os_log_t logHandle;
//inc //inc
msgCount++; msgCount++;
//macOS 10.16 (11)
if(@available(macOS 10.16, *))
{
//tcc request //tcc request
if(YES == [event.composedMessage containsString:@"function=TCCAccessRequest, service=kTCCServiceMicrophone"]) if(YES == [event.composedMessage containsString:@"function=TCCAccessRequest, service=kTCCServiceMicrophone"])
{ {
@ -583,30 +595,104 @@ extern os_log_t logHandle;
//add client //add client
[self.audioClients addObject:client]; [self.audioClients addObject:client];
} }
}
//auth ok msg //macOS 10.15
else if(YES == [event.composedMessage containsString:@"RECV: synchronous reply"]) else
{
//tcc request
if( (YES == [event.composedMessage containsString:@"TCCAccessRequest"]) &&
(YES == [event.composedMessage containsString:@"kTCCServiceMicrophone"]) )
{ {
//client //client
__block Client* client = nil; Client* client = nil;
//(split) response //token
NSArray* response = nil; NSString* token = nil;
//pid substring
NSString* substring = nil;
//pid
unsigned int pid = 0;
//match
NSTextCheckingResult* match = nil;
//dbg msg //dbg msg
os_log_debug(logHandle, "new client tccd response : %{public}@", event.composedMessage); os_log_debug(logHandle, "new tcc access msg: %{public}@", event.composedMessage);
//response //match/extract pid
response = [event.composedMessage componentsSeparatedByString:@"\n"]; match = [regex firstMatchInString:event.composedMessage options:0 range:NSMakeRange(0, event.composedMessage.length)];
if( (response.count < 2) ||
(YES != [response[1] hasSuffix:@"2"]) || //no match?
(YES != [response[1] containsString:@"auth_value"]) ) if( (nil == match) ||
(NSNotFound == match.range.location) )
{ {
//ignore //ignore
return; return;
} }
//get last client //extract token
token = [event.composedMessage substringWithRange:[match rangeAtIndex:0]];
if(token.length < 46) return;
//extract pid
substring = [token substringWithRange:NSMakeRange(42, 4)];
//convert to int
sscanf(substring.UTF8String, "%x", &pid);
if(0 == pid) return;
//init client
client = [[Client alloc] init];
client.msgCount = msgCount;
client.pid = @(htons(pid));
client.path = getProcessPath(client.pid.intValue);
client.name = getProcessName(client.path);
//dbg msg
os_log_debug(logHandle, "new client: %{public}@", client);
//add client
[self.audioClients addObject:client];
}
}
//tcc auth response
// check that a) auth ok b) msg is right after request
if(YES == [event.composedMessage containsString:@"synchronous reply"])
{
//client
__block Client* client = nil;
//flag
BOOL isAuthorized = NO;
//dbg msg
os_log_debug(logHandle, "new client tccd response : %{public}@", event.composedMessage);
// look for:
//"result" => <bool: 0x1fcca5e70>: true
for(NSString* response in [event.composedMessage componentsSeparatedByString:@"\n"])
{
//no match?
if( (YES != [response hasSuffix:@"true"]) ||
(YES != [response containsString:@"\"result\""]) )
{
continue;
}
//match
isAuthorized = YES;
//done
break;
}
//not auth'd?
if(YES != isAuthorized) return;
//auth, so get last client
// check that it the one in the *last* msg // check that it the one in the *last* msg
client = self.audioClients.lastObject; client = self.audioClients.lastObject;
if(client.msgCount != msgCount-1) if(client.msgCount != msgCount-1)
@ -656,42 +742,20 @@ extern os_log_t logHandle;
//flag //flag
BOOL cameraOn = NO; BOOL cameraOn = NO;
//selector for getting device id
SEL methodSelector = nil;
//device's connection id //device's connection id
unsigned int connectionID = 0; unsigned int connectionID = 0;
//dbg msg //dbg msg
os_log_debug(logHandle, "checking if any camera is active"); os_log_debug(logHandle, "checking if any camera is active");
//init selector
methodSelector = NSSelectorFromString(@"connectionID");
//are any cameras currently on? //are any cameras currently on?
for(AVCaptureDevice* currentCamera in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) for(AVCaptureDevice* currentCamera in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo])
{ {
//dbg msg //dbg msg
os_log_debug(logHandle, "device: %{public}@/%{public}@", currentCamera.manufacturer, currentCamera.localizedName); os_log_debug(logHandle, "device: %{public}@/%{public}@", currentCamera.manufacturer, currentCamera.localizedName);
//sanity check //get id
// make sure is has private 'connectionID' iVar connectionID = [self getAVObjectID:currentCamera];
if(YES != [currentCamera respondsToSelector:methodSelector])
{
//skip
continue;
}
//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)[currentCamera performSelector:methodSelector withObject:nil];
//restore
#pragma clang diagnostic pop
//get state //get state
// is (any) camera on? // is (any) camera on?
@ -719,43 +783,71 @@ bail:
//mic //mic
AudioObjectID builtInMic = 0; AudioObjectID builtInMic = 0;
//selector for getting device id
SEL methodSelector = nil;
//init selector
methodSelector = NSSelectorFromString(@"connectionID");
//look for mic that belongs to apple //look for mic that belongs to apple
for(AVCaptureDevice* currentMic in [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio]) for(AVCaptureDevice* currentMic in [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio])
{ {
//dbg msg //dbg msg
os_log_debug(logHandle, "device: %{public}@/%{public}@", currentMic.manufacturer, currentMic.localizedName); os_log_debug(logHandle, "device: %{public}@/%{public}@", currentMic.manufacturer, currentMic.localizedName);
//sanity check
if(YES != [currentMic respondsToSelector:methodSelector]) continue;
//check if apple //check if apple
// also check input source // also check input source
if( (YES == [currentMic.manufacturer isEqualToString:@"Apple Inc."]) && if( (YES == [currentMic.manufacturer isEqualToString:@"Apple Inc."]) &&
(YES == [[[currentMic activeInputSource] inputSourceID] isEqualToString:@"imic"]) ) (YES == [[[currentMic activeInputSource] inputSourceID] isEqualToString:@"imic"]) )
{ {
//ignore leak warning //grab ID
#pragma clang diagnostic push builtInMic = [self getAVObjectID:currentMic];
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//grab connection ID
builtInMic = (unsigned int)[currentMic performSelector:methodSelector withObject:nil];
//restore
#pragma clang diagnostic pop
//done //done
break; break;
} }
} }
return builtInMic; //not found?
// grab default
if(0 == builtInMic)
{
//get mic / id
builtInMic = [self getAVObjectID:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]];
//dbg msg
os_log_debug(logHandle, "Apple mic not found, defaulting to default (id: %d)", builtInMic);
}
return builtInMic;
}
//get av object's ID
-(UInt32)getAVObjectID:(AVCaptureDevice*)audioObject
{
//object id
AudioObjectID objectID = 0;
//selector for getting device id
SEL methodSelector = nil;
//init selector
methodSelector = NSSelectorFromString(@"connectionID");
//sanity check
if(YES != [audioObject respondsToSelector:methodSelector])
{
//bail
goto bail;
}
//ignore leak warning
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//grab connection ID
objectID = (unsigned int)[audioObject performSelector:methodSelector withObject:nil];
//restore
#pragma clang diagnostic pop
bail:
return objectID;
} }
//is built-in mic on? //is built-in mic on?
@ -865,6 +957,9 @@ bail:
goto bail; goto bail;
} }
//dbg msg
os_log_debug(logHandle, "monitoring %d for audio changes", builtInMic);
//happy //happy
bRegistered = YES; bRegistered = YES;