refactored alerting logic

This commit is contained in:
Patrick Wardle 2021-05-11 11:42:21 -04:00
parent bff1cf7f81
commit b9dd3a3bd3
4 changed files with 163 additions and 137 deletions

View File

@ -50,6 +50,9 @@ extern os_log_t logHandle;
self = [super init];
if(nil != self)
{
//init camera state
self.cameraState = -1;
//init video log monitor
self.videoLogMonitor = [[LogMonitor alloc] init];
@ -187,15 +190,11 @@ extern os_log_t logHandle;
event = [[Event alloc] init:client device:Device_Camera state:self.cameraState];
//camera already on?
// show notifcation for new client
// handle event for new client
if(NSControlStateValueOn == self.cameraState)
{
//show notification
if(YES == [self generateNotification:event])
{
//execute action
[self executeUserAction:event];
}
//handle event
[self handleEvent:event];
}
//will handle when "on" camera msg is delivered
@ -229,12 +228,8 @@ extern os_log_t logHandle;
//init event
event = [[Event alloc] init:client device:Device_Camera state:self.cameraState];
//show notification
if(YES == [self generateNotification:event])
{
//execute action
[self executeUserAction:event];
}
//handle event
[self handleEvent:event];
}
//dead client
@ -287,12 +282,8 @@ extern os_log_t logHandle;
//init event
event = [[Event alloc] init:nil device:Device_Camera state:self.cameraState];
//show notification
if(YES == [self generateNotification:event])
{
//execute action
[self executeUserAction:event];
}
//handle event
[self handleEvent:event];
//sync
@synchronized (self) {
@ -441,13 +432,8 @@ extern os_log_t logHandle;
//init event
event = [[Event alloc] init:client device:Device_Camera state:self.cameraState];
//show notification
// ok if client is (still) nil...
if(YES == [self generateNotification:event])
{
//execute action
[self executeUserAction:event];
}
//handle event
[self handleEvent:event];
}
//dead client
@ -517,12 +503,8 @@ extern os_log_t logHandle;
//init event
event = [[Event alloc] init:nil device:Device_Camera state:self.cameraState];
//show notification
if(YES == [self generateNotification:event])
{
//execute action
[self executeUserAction:event];
}
//handle event
[self handleEvent:event];
}
});
}
@ -807,11 +789,8 @@ extern os_log_t logHandle;
//init event
event = [[Event alloc] init:client device:Device_Microphone state:self.microphoneState];
//show notification
[self generateNotification:event];
//execute action
[self executeUserAction:event];
//handle event
[self handleEvent:event];
});
}
@ -1022,12 +1001,8 @@ bail:
}//sync
//show notification
if(YES == [weakSelf generateNotification:event])
{
//execute action
[weakSelf executeUserAction:event];
}
//handle event
[weakSelf handleEvent:event];
}
};
@ -1180,12 +1155,127 @@ bail:
return isRunning;
}
//build and display notification
-(BOOL)generateNotification:(Event*)event
//should an event be shown?
-(NSUInteger)shouldShowNotification:(Event*)event
{
//flag
BOOL wasDelivered = NO;
//result
NSUInteger result = NOTIFICATION_ERROR;
//inactive alerting off?
// ignore if event is an inactive/off
if( (NSControlStateValueOff == event.state) &&
(YES == [NSUserDefaults.standardUserDefaults boolForKey:PREF_DISABLE_INACTIVE]))
{
//set result
result = NOTIFICATION_SKIPPED;
//dbg msg
os_log_debug(logHandle, "disable inactive alerts set, so ignoring inactive/off event");
//bail
goto bail;
}
//(new) mic event?
// need extra logic, since macOS sometimes toggles / delivers 2x event, etc...
if(Device_Microphone == event.device)
{
//from same client?
// ignore if last event *just* occurred
if( (self.lastMicEvent.client.pid == event.client.pid) &&
([[NSDate date] timeIntervalSinceDate:self.lastMicEvent.timestamp] < 0.5f) )
{
//set result
result = NOTIFICATION_SPURIOUS;
//dbg msg
os_log_debug(logHandle, "ignoring mic event, as it happened <0.5s ");
//bail
goto bail;
}
//or, was a 2x off?
if( (nil != self.lastMicEvent) &&
(NSControlStateValueOff == event.state) &&
(NSControlStateValueOff == self.lastMicEvent.state) )
{
//set result
result = NOTIFICATION_SPURIOUS;
//dbg msg
os_log_debug(logHandle, "ignoring mic event, as it was a 2x off");
//bail
goto bail;
}
//update
self.lastMicEvent = event;
}
//client provided?
// check if its allowed
if(nil != event.client)
{
//match is simply: device and path
for(NSDictionary* allowedItem in [NSUserDefaults.standardUserDefaults objectForKey:PREFS_ALLOWED_ITEMS])
{
//match?
if( ([allowedItem[EVENT_DEVICE] intValue] == event.device) &&
(YES == [allowedItem[EVENT_PROCESS_PATH] isEqualToString:event.client.path]) )
{
//set result
result = NOTIFICATION_SKIPPED;
//dbg msg
os_log_debug(logHandle, "%{public}@ is allowed to access %d, so no notification will be shown", event.client.path, event.device);
//done
goto bail;
}
}
}
//set result
result = NOTIFICATION_DELIVER;
bail:
return result;
}
//handle an event
// show alert / exec user action
-(void)handleEvent:(Event*)event
{
//result
NSUInteger result = NOTIFICATION_ERROR;
//should show?
result = [self shouldShowNotification:event];
if(NOTIFICATION_DELIVER == result)
{
//deliver
[self showNotification:event];
}
//should (also) exec user action?
if( (NOTIFICATION_ERROR != result) &&
(NOTIFICATION_SPURIOUS != result) )
{
//exec
[self executeUserAction:event];
}
bail:
return;
}
//build and display notification
-(void)showNotification:(Event*)event
{
//notification content
UNMutableNotificationContent* content = nil;
@ -1198,71 +1288,6 @@ bail:
//title
NSMutableString* title = nil;
//inactive disabled?
// ignore if event is an off
if(YES == [NSUserDefaults.standardUserDefaults boolForKey:PREF_DISABLE_INACTIVE])
{
//dbg msg
os_log_debug(logHandle, "user has set preference to ingore 'inactive' notifications");
//off?
// ignore...
if(NSControlStateValueOff == event.state)
{
//dbg msg
os_log_debug(logHandle, "...so ignoring inactive/off event");
goto bail;
}
}
//(new) mic event?
if(Device_Microphone == event.device)
{
//from same client?
// ignore if last event *just* occurred
// ...macOS sometimes toggles mic on/off when turning off :/
if( (self.lastMicEvent.client.pid == event.client.pid) &&
([[NSDate date] timeIntervalSinceDate:self.lastMicEvent.timestamp] < 0.5f) )
{
//dbg msg
os_log_debug(logHandle, "ignoring mic event, as it happened <0.5s ");
return wasDelivered;
}
//or, was a 2x off?
if( (nil != self.lastMicEvent) &&
(NSControlStateValueOff == event.state) &&
(NSControlStateValueOff == self.lastMicEvent.state) )
{
//dbg msg
os_log_debug(logHandle, "ignoring mic event, as it was a 2x off");
return wasDelivered;
}
//update
self.lastMicEvent = event;
}
//client?
// check if allowed
if(nil != event.client)
{
//match is simply: device and path
for(NSDictionary* allowedItem in [NSUserDefaults.standardUserDefaults objectForKey:PREFS_ALLOWED_ITEMS])
{
//match?
if( (allowedItem[EVENT_DEVICE] == (NSNumber*)@(event.device)) &&
(YES == [allowedItem[EVENT_PROCESS_PATH] isEqualToString:event.client.path]) )
{
//dbg msg
os_log_debug(logHandle, "%{public}@ is allowed to access %d, so no notification will be shown", event.client.path, event.device);
//done
goto bail;
}
}
}
//set (default) category
content.categoryIdentifier = CATEGORY_CLOSE;
@ -1305,13 +1330,10 @@ bail:
os_log_error(logHandle, "ERROR failed to deliver notification (error: %@)", error);
}
}];
//happy
wasDelivered = YES;
bail:
return wasDelivered;
return;
}
//execute user action

View File

@ -25,7 +25,7 @@
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<rect key="contentRect" x="196" y="240" width="600" height="365"/>
<rect key="screenRect" x="0.0" y="0.0" width="3440" height="1415"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="875"/>
<view key="contentView" wantsLayer="YES" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="600" height="365"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -91,8 +91,8 @@
<action selector="togglePreference:" target="-2" id="tpK-Y8-G90"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xbe-1S-lpy">
<rect key="frame" x="72" y="221" width="160" height="19"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="156" translatesAutoresizingMaskIntoConstraints="NO" id="xbe-1S-lpy">
<rect key="frame" x="72" y="224" width="114" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Start at Login" id="JVI-Or-h0u">
<font key="font" size="13" name="Menlo-Bold"/>
@ -120,8 +120,8 @@
<action selector="togglePreference:" target="-2" id="aP4-WS-lmI"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Kjh-jc-STu">
<rect key="frame" x="72" y="159" width="160" height="19"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="156" translatesAutoresizingMaskIntoConstraints="NO" id="Kjh-jc-STu">
<rect key="frame" x="72" y="162" width="98" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<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"/>
@ -140,10 +140,10 @@
<action selector="togglePreference:" target="-2" id="Usk-5k-Wca"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Enr-tn-mwG">
<rect key="frame" x="72" y="97" width="266" height="19"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="262" translatesAutoresizingMaskIntoConstraints="NO" id="Enr-tn-mwG">
<rect key="frame" x="72" y="100" width="200" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disable 'inactive' alerts" id="a5n-tt-65v">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disable 'Inactive' Alerts" id="a5n-tt-65v">
<font key="font" size="13" name="Menlo-Bold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -170,7 +170,7 @@
</connections>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="471" translatesAutoresizingMaskIntoConstraints="NO" id="xaO-g8-rdS">
<rect key="frame" x="72" y="146" width="475" height="15"/>
<rect key="frame" x="72" y="145" width="475" height="15"/>
<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">
<font key="font" size="13" name="Menlo-Regular"/>
@ -196,8 +196,8 @@
<action selector="togglePreference:" target="-2" id="7qn-r4-nxl"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8Tb-Ih-SsC">
<rect key="frame" x="72" y="221" width="121" height="19"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="117" translatesAutoresizingMaskIntoConstraints="NO" id="8Tb-Ih-SsC">
<rect key="frame" x="72" y="224" width="114" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Execute Action" id="ncJ-bF-dAA">
<font key="font" size="13" name="Menlo-Bold"/>
@ -206,7 +206,7 @@
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="471" translatesAutoresizingMaskIntoConstraints="NO" id="GyI-og-F35">
<rect key="frame" x="72" y="196" width="475" height="19"/>
<rect key="frame" x="72" y="202" width="318" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Perform an action (script, binary, etc)." id="bky-MW-yBn">
<font key="font" size="13" name="Menlo-Regular"/>
@ -226,7 +226,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dH0-Xi-Qnd">
<rect key="frame" x="72" y="159" width="114" height="19"/>
<rect key="frame" x="72" y="162" width="114" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Pass Arguments" id="WL4-fV-4Wq">
<font key="font" size="13" name="Menlo-Bold"/>
@ -235,7 +235,7 @@
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="471" translatesAutoresizingMaskIntoConstraints="NO" id="GP0-UX-66J">
<rect key="frame" x="72" y="66" width="475" height="92"/>
<rect key="frame" x="72" y="98" width="427" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" id="7Ah-kZ-gGe">
<font key="font" size="13" name="Menlo-Regular"/>
@ -244,8 +244,8 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="z32-0s-dpb">
<rect key="frame" x="198" y="218" width="354" height="24"/>
<textField verticalHuggingPriority="750" fixedFrame="YES" preferredMaxLayoutWidth="382" translatesAutoresizingMaskIntoConstraints="NO" id="z32-0s-dpb">
<rect key="frame" x="198" y="221" width="382" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="path" drawsBackground="YES" id="Oe0-3Y-Srk">
<font key="font" size="13" name="Menlo-Regular"/>
@ -257,7 +257,7 @@
</connections>
</textField>
</subviews>
<point key="canvasLocation" x="-166" y="739"/>
<point key="canvasLocation" x="-166" y="738.5"/>
</customView>
<customView id="1OV-sl-cSe" userLabel="Update">
<rect key="frame" x="0.0" y="0.0" width="618" height="275"/>
@ -275,7 +275,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="YB6-Vv-lQu">
<rect key="frame" x="72" y="221" width="177" height="19"/>
<rect key="frame" x="72" y="224" width="169" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disable Update Checks" id="z5b-fA-WCk">
<font key="font" size="13" name="Menlo-Bold"/>
@ -304,7 +304,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Oe2-Ye-1s6">
<rect key="frame" x="201" y="101" width="216" height="33"/>
<rect key="frame" x="201" y="101" width="216" height="34"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" id="5gt-xh-Ti5">
<font key="font" size="13" name="Menlo-Bold"/>

View File

@ -196,7 +196,7 @@
<rect key="frame" x="304" y="165" width="500" height="200"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="m0M-KX-7D6" userLabel="loading data">
<rect key="frame" x="18" y="87" width="464" height="25"/>
<rect key="frame" x="89" y="92" width="322" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="Currently, there are no allowed items..." id="Wjb-RO-P6A">
<font key="font" size="13" name="Menlo-Bold"/>

View File

@ -149,7 +149,6 @@
//menu: 'quit'
#define MENU_ITEM_QUIT 1
//app name
#define APP_NAME @"OverSight.app"
@ -230,6 +229,11 @@
#define EVENT_PROCESS_ID @"processID"
#define EVENT_PROCESS_PATH @"processPath"
#define NOTIFICATION_ERROR -1
#define NOTIFICATION_SPURIOUS 0
#define NOTIFICATION_SKIPPED 1
#define NOTIFICATION_DELIVER 2
//av devices
typedef enum {Device_Camera, Device_Microphone} AVDevice;