diff --git a/Library/SRShortcutAction.h b/Library/SRShortcutAction.h index 4bc5e26a..3fe6f484 100644 --- a/Library/SRShortcutAction.h +++ b/Library/SRShortcutAction.h @@ -382,15 +382,23 @@ NS_SWIFT_NAME(GlobalShortcutMonitor) security implications as this API requires the app to either run under the root user or been allowed the Accessibility permission. + The monitor automatically enables and disables the tap when needed. + @see SRGlobalShortcutMonitor @see AXIsProcessTrustedWithOptions @see NSAppleEventsUsageDescription */ @interface SRAXGlobalShortcutMonitor : SRShortcutMonitor +/*! + Mach port that corresponds to the event tap used under the hood. + */ @property (readonly) CFMachPortRef eventTap; - (CFMachPortRef)eventTap NS_RETURNS_INNER_POINTER CF_RETURNS_NOT_RETAINED; +@property (readonly) CFRunLoopSourceRef eventTapSource; +- (CFRunLoopSourceRef)eventTapSource NS_RETURNS_INNER_POINTER CF_RETURNS_NOT_RETAINED; + /*! @discussion Initialization may fail if it's impossible to create an event tap. @@ -399,6 +407,8 @@ NS_SWIFT_NAME(GlobalShortcutMonitor) */ - (nullable instancetype)init; +- (nullable instancetype)initWithRunLoop:(nullable NSRunLoop *)aRunLoop NS_DESIGNATED_INITIALIZER; + /*! Perform the action associated with a given event. diff --git a/Library/SRShortcutAction.m b/Library/SRShortcutAction.m index 15eec0fe..5cc26ab9 100644 --- a/Library/SRShortcutAction.m +++ b/Library/SRShortcutAction.m @@ -1110,10 +1110,23 @@ @implementation SRAXGlobalShortcutMonitor CGEventRef _Nullable TapCallback(CGEventTapProxy aProxy, CGEventType aType, CGEventRef anEvent, void * _Nullable aUserInfo) { __auto_type self = (__bridge SRAXGlobalShortcutMonitor *)aUserInfo; - return [self handleEvent:anEvent]; + + if (aType == kCGEventTapDisabledByTimeout || aType == kCGEventTapDisabledByUserInput) + { + os_trace("#Error #Developer The system disabled event tap due to %u", aType); + CGEventTapEnable(self.eventTap, true); + return anEvent; + } + else + return [self handleEvent:anEvent]; } - (instancetype)init +{ + return [self initWithRunLoop:nil]; +} + +- (instancetype)initWithRunLoop:(NSRunLoop *)aRunLoop { static const CGEventMask Mask = (CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | @@ -1135,6 +1148,12 @@ - (instancetype)init if (self) { _eventTap = eventTap; + _eventTapSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0); + + if (aRunLoop) + CFRunLoopAddSource(aRunLoop.getCFRunLoop, _eventTapSource, kCFRunLoopDefaultMode); + else + CFRunLoopAddSource(CFRunLoopGetCurrent(), _eventTapSource, kCFRunLoopDefaultMode); } return self; @@ -1144,33 +1163,47 @@ - (void)dealloc { if (_eventTap) CFRelease(_eventTap); + + CFRelease(_eventTapSource); } #pragma mark Methods - (CGEventRef)handleEvent:(CGEventRef)anEvent { - __auto_type nsEvent = [NSEvent eventWithCGEvent:anEvent]; - __auto_type shortcut = [SRShortcut shortcutWithEvent:nsEvent]; - if (!shortcut) - { - os_trace_error("#Error Not a keyboard event"); - return anEvent; - } + __block __auto_type result = anEvent; - SRKeyEventType eventType = nsEvent.SR_keyEventType; - if (eventType == 0) - return anEvent; + os_activity_initiate("-[SRAXGlobalShortcutMonitor handleEvent:]", OS_ACTIVITY_FLAG_DETACHED, ^{ + __auto_type nsEvent = [NSEvent eventWithCGEvent:anEvent]; + if (!nsEvent) + { + os_trace_error("#Error Unexpected event"); + return; + } - __auto_type actions = [self actionsForShortcut:shortcut keyEvent:eventType]; - __block BOOL isHandled = NO; - [actions enumerateObjectsWithOptions:NSEnumerationReverse - usingBlock:^(SRShortcutAction *obj, NSUInteger idx, BOOL *stop) - { - *stop = isHandled = [obj performActionOnTarget:nil]; - }]; + __auto_type shortcut = [SRShortcut shortcutWithEvent:nsEvent]; + if (!shortcut) + { + os_trace_error("#Error Not a keyboard event"); + return; + } + + SRKeyEventType eventType = nsEvent.SR_keyEventType; + if (eventType == 0) + return; + + __auto_type actions = [self actionsForShortcut:shortcut keyEvent:eventType]; + __block BOOL isHandled = NO; + [actions enumerateObjectsWithOptions:NSEnumerationReverse + usingBlock:^(SRShortcutAction *obj, NSUInteger idx, BOOL *stop) + { + *stop = isHandled = [obj performActionOnTarget:nil]; + }]; + + result = isHandled ? nil : anEvent; + }); - return isHandled ? nil : anEvent; + return result; } #pragma mark SRShortcutMonitor