improved handling of device off/inactive events

This commit is contained in:
Patrick Wardle 2022-11-11 17:06:25 -10:00
parent dbcecf60c4
commit c4c97f2f2c
3 changed files with 142 additions and 19 deletions

View File

@ -52,6 +52,9 @@
//audio listeners //audio listeners
@property(nonatomic, retain)NSMutableDictionary* audioListeners; @property(nonatomic, retain)NSMutableDictionary* audioListeners;
//camera listeners
@property(nonatomic, retain)NSMutableDictionary* cameraListeners;
//per device events //per device events
@property(nonatomic, retain)NSMutableDictionary* deviceEvents; @property(nonatomic, retain)NSMutableDictionary* deviceEvents;

View File

@ -56,9 +56,12 @@ extern os_log_t logHandle;
//init camera attributions //init camera attributions
self.cameraAttributions = [NSMutableArray array]; self.cameraAttributions = [NSMutableArray array];
//init audio listener //init audio listeners
self.audioListeners = [NSMutableDictionary dictionary]; self.audioListeners = [NSMutableDictionary dictionary];
//init video listeners
self.cameraListeners = [NSMutableDictionary dictionary];
//set up delegate //set up delegate
UNUserNotificationCenter.currentNotificationCenter.delegate = self; UNUserNotificationCenter.currentNotificationCenter.delegate = self;
@ -232,7 +235,7 @@ extern os_log_t logHandle;
continue; continue;
} }
//cameraAttributionsList list? //cameraAttributions list?
if(YES == [line hasPrefix:@"cameraAttributions = "]) if(YES == [line hasPrefix:@"cameraAttributions = "])
{ {
//set flag //set flag
@ -628,6 +631,9 @@ extern os_log_t logHandle;
goto bail; goto bail;
} }
//save
self.audioListeners[device.uniqueID] = listenerBlock;
//dbg msg //dbg msg
os_log_debug(logHandle, "monitoring %{public}@ for audio changes", device.localizedName); os_log_debug(logHandle, "monitoring %{public}@ for audio changes", device.localizedName);
@ -648,6 +654,7 @@ bail:
//status var //status var
OSStatus status = -1; OSStatus status = -1;
//device id
CMIOObjectID deviceID = 0; CMIOObjectID deviceID = 0;
//property struct //property struct
@ -697,13 +704,15 @@ bail:
goto bail; goto bail;
} }
//save
self.cameraListeners[device.uniqueID] = listenerBlock;
//dbg msg //dbg msg
os_log_debug(logHandle, "monitoring %{public}@ for video changes", device.localizedName); os_log_debug(logHandle, "monitoring %{public}@ for video changes", device.localizedName);
//happy //happy
bRegistered = YES; bRegistered = YES;
//bail
bail: bail:
return bRegistered; return bRegistered;
@ -995,7 +1004,7 @@ bail:
//external device? //external device?
// don't show notification // don't show notification
if( (YES != [self.builtInMic.uniqueID isEqualToString:event.device.uniqueID]) && if( (YES != [self.builtInMic.uniqueID isEqualToString:event.device.uniqueID]) &&
(YES != [self.builtInCamera.uniqueID isEqualToString:event.device.uniqueID]) ) (YES != [self.builtInCamera.uniqueID isEqualToString:event.device.uniqueID]) )
{ {
//set result //set result
result = NOTIFICATION_SKIPPED; result = NOTIFICATION_SKIPPED;
@ -1012,13 +1021,10 @@ bail:
// check last device that turned off // check last device that turned off
else else
{ {
//wait
// since logging event might come thru first
[NSThread sleepForTimeInterval:1.0f];
//mic //mic
// check last mic off device // check last mic off device
if( (event.deviceType = Device_Microphone) && if( (Device_Microphone == event.deviceType) &&
(nil != self.lastMicOff) &&
(YES != [self.builtInMic.uniqueID isEqualToString:self.lastMicOff.uniqueID]) ) (YES != [self.builtInMic.uniqueID isEqualToString:self.lastMicOff.uniqueID]) )
{ {
//set result //set result
@ -1033,8 +1039,9 @@ bail:
//camera //camera
// check last camera off device // check last camera off device
if( (event.deviceType = Device_Camera) && if( (Device_Camera == event.deviceType) &&
(YES != [self.builtInMic.uniqueID isEqualToString:self.lastCameraOff.uniqueID]) ) (nil != self.lastCameraOff) &&
(YES != [self.builtInCamera.uniqueID isEqualToString:self.lastCameraOff.uniqueID]) )
{ {
//set result //set result
result = NOTIFICATION_SKIPPED; result = NOTIFICATION_SKIPPED;
@ -1288,12 +1295,125 @@ bail:
//stop monitor //stop monitor
-(void)stop -(void)stop
{ {
//dbg msg
os_log_debug(logHandle, "stopping log monitor");
//stop log monitoring //stop log monitoring
[self.logMonitor stop]; [self.logMonitor stop];
//dbg msg
os_log_debug(logHandle, "stopping audio monitor(s)");
//watch all input audio (mic) devices
for(AVCaptureDevice* audioDevice in [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio])
{
//stop (device) monitor
[self unwatchAudioDevice:audioDevice];
}
//dbg msg
os_log_debug(logHandle, "stopping video monitor(s)");
//watch all input video (cam) devices
for(AVCaptureDevice* videoDevice in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo])
{
//start (device) monitor
[self unwatchVideoDevice:videoDevice];
}
//dbg msg
os_log_debug(logHandle, "all stopped...");
return; return;
} }
//stop audio monitor
-(void)unwatchAudioDevice:(AVCaptureDevice*)device
{
//status
OSStatus status = -1;
//device ID
AudioObjectID deviceID = 0;
//property struct
AudioObjectPropertyAddress propertyStruct = {0};
//get device ID
deviceID = [self getAVObjectID:device];
//init property struct's selector
propertyStruct.mSelector = kAudioDevicePropertyDeviceIsRunningSomewhere;
//init property struct's scope
propertyStruct.mScope = kAudioObjectPropertyScopeGlobal;
//init property struct's element
propertyStruct.mElement = kAudioObjectPropertyElementMaster;
//remove
status = AudioObjectRemovePropertyListenerBlock(deviceID, &propertyStruct, dispatch_get_main_queue(), self.audioListeners[device.uniqueID]);
if(noErr != status)
{
//err msg
os_log_error(logHandle, "ERROR: 'AudioObjectRemovePropertyListenerBlock' failed with %d", status);
//bail
goto bail;
}
//unset listener block
self.audioListeners[device.uniqueID] = nil;
bail:
return;
}
//stop video monitor
-(void)unwatchVideoDevice:(AVCaptureDevice*)device
{
//status
OSStatus status = -1;
//device id
CMIOObjectID deviceID = 0;
//property struct
CMIOObjectPropertyAddress propertyStruct = {0};
//get device ID
deviceID = [self getAVObjectID:device];
//init property struct's selector
propertyStruct.mSelector = kAudioDevicePropertyDeviceIsRunningSomewhere;
//init property struct's scope
propertyStruct.mScope = kAudioObjectPropertyScopeGlobal;
//init property struct's element
propertyStruct.mElement = kAudioObjectPropertyElementMaster;
//remove
status = CMIOObjectRemovePropertyListenerBlock(deviceID, &propertyStruct, dispatch_get_main_queue(), self.cameraListeners[device.uniqueID]);
if(noErr != status)
{
//err msg
os_log_error(logHandle, "ERROR: 'AudioObjectRemovePropertyListenerBlock' failed with %d", status);
//bail
goto bail;
}
//unset listener block
self.cameraListeners[device.uniqueID] = nil;
bail:
return;
}
# pragma mark UNNotificationCenter Delegate Methods # pragma mark UNNotificationCenter Delegate Methods
//handle user response to notification //handle user response to notification

View File

@ -94,7 +94,7 @@
</connections> </connections>
</button> </button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="156" translatesAutoresizingMaskIntoConstraints="NO" id="xbe-1S-lpy"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="156" translatesAutoresizingMaskIntoConstraints="NO" id="xbe-1S-lpy">
<rect key="frame" x="72" y="326" width="114" height="15"/> <rect key="frame" x="72" y="327" width="114" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Start at Login" id="JVI-Or-h0u"> <textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Start at Login" id="JVI-Or-h0u">
<font key="font" size="13" name="Menlo-Bold"/> <font key="font" size="13" name="Menlo-Bold"/>
@ -103,7 +103,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="471" translatesAutoresizingMaskIntoConstraints="NO" id="tFB-E4-zbp"> <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="471" translatesAutoresizingMaskIntoConstraints="NO" id="tFB-E4-zbp">
<rect key="frame" x="72" y="309" width="475" height="15"/> <rect key="frame" x="72" y="310" width="475" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Automatically (re)start each time you login." id="E0T-Ug-P8f"> <textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Automatically (re)start each time you login." id="E0T-Ug-P8f">
<font key="font" size="13" name="Menlo-Regular"/> <font key="font" size="13" name="Menlo-Regular"/>
@ -123,7 +123,7 @@
</connections> </connections>
</button> </button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="156" translatesAutoresizingMaskIntoConstraints="NO" id="Kjh-jc-STu"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="156" translatesAutoresizingMaskIntoConstraints="NO" id="Kjh-jc-STu">
<rect key="frame" x="72" y="264" width="98" height="15"/> <rect key="frame" x="72" y="265" width="98" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="No Icon Mode" id="4EN-j2-enV"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="No Icon Mode" id="4EN-j2-enV">
<font key="font" size="13" name="Menlo-Bold"/> <font key="font" size="13" name="Menlo-Bold"/>
@ -143,7 +143,7 @@
</connections> </connections>
</button> </button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="262" translatesAutoresizingMaskIntoConstraints="NO" id="Enr-tn-mwG"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="262" translatesAutoresizingMaskIntoConstraints="NO" id="Enr-tn-mwG">
<rect key="frame" x="72" y="201" width="177" height="15"/> <rect key="frame" x="72" y="202" width="177" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Ignore External Device" id="a5n-tt-65v"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Ignore External Device" id="a5n-tt-65v">
<font key="font" size="13" name="Menlo-Bold"/> <font key="font" size="13" name="Menlo-Bold"/>
@ -152,7 +152,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="471" translatesAutoresizingMaskIntoConstraints="NO" id="AAL-QY-azK"> <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="471" translatesAutoresizingMaskIntoConstraints="NO" id="AAL-QY-azK">
<rect key="frame" x="72" y="184" width="522" height="15"/> <rect key="frame" x="72" y="185" width="522" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Do not show an alert when external devices trigger an event." id="SYJ-Qv-cRq"> <textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Do not show an alert when external devices trigger an event." id="SYJ-Qv-cRq">
<font key="font" size="13" name="Menlo-Regular"/> <font key="font" size="13" name="Menlo-Regular"/>
@ -172,7 +172,7 @@
</connections> </connections>
</button> </button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="262" translatesAutoresizingMaskIntoConstraints="NO" id="K3U-Od-Xdy"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="262" translatesAutoresizingMaskIntoConstraints="NO" id="K3U-Od-Xdy">
<rect key="frame" x="72" y="149" width="200" height="15"/> <rect key="frame" x="72" y="150" width="200" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disable 'Inactive' Alerts" id="Gsa-fn-iag"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disable 'Inactive' Alerts" id="Gsa-fn-iag">
<font key="font" size="13" name="Menlo-Bold"/> <font key="font" size="13" name="Menlo-Bold"/>
@ -181,7 +181,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="471" translatesAutoresizingMaskIntoConstraints="NO" id="5xo-jp-5qA"> <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="471" translatesAutoresizingMaskIntoConstraints="NO" id="5xo-jp-5qA">
<rect key="frame" x="72" y="132" width="522" height="15"/> <rect key="frame" x="72" y="133" width="522" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Do not show an alert when the camera/microphone is deactivated. " id="1A2-j2-kqG"> <textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Do not show an alert when the camera/microphone is deactivated. " id="1A2-j2-kqG">
<font key="font" size="13" name="Menlo-Regular"/> <font key="font" size="13" name="Menlo-Regular"/>
@ -201,7 +201,7 @@
</connections> </connections>
</button> </button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="471" translatesAutoresizingMaskIntoConstraints="NO" id="xaO-g8-rdS"> <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="471" translatesAutoresizingMaskIntoConstraints="NO" id="xaO-g8-rdS">
<rect key="frame" x="72" y="247" width="475" height="15"/> <rect key="frame" x="72" y="248" width="475" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Run without showing an icon in the status menu bar." id="SG5-YM-BoV"> <textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Run without showing an icon in the status menu bar." id="SG5-YM-BoV">
<font key="font" size="13" name="Menlo-Regular"/> <font key="font" size="13" name="Menlo-Regular"/>