Hi all:
I have a macOS application which capture mouse events:
CGEventMask eventMask = CGEventMaskBit(kCGEventMouseMoved) |
CGEventMaskBit(kCGEventLeftMouseUp) |
CGEventMaskBit(kCGEventLeftMouseDown) |
CGEventMaskBit(kCGEventRightMouseUp) |
CGEventMaskBit(kCGEventRightMouseDown) |
CGEventMaskBit(kCGEventOtherMouseUp) |
CGEventMaskBit(kCGEventOtherMouseDown) |
CGEventMaskBit(kCGEventScrollWheel) |
CGEventMaskBit(kCGEventLeftMouseDragged) |
CGEventMaskBit(kCGEventRightMouseDragged) |
CGEventMaskBit(kCGEventOtherMouseDragged);
_eventTap = CGEventTapCreate(kCGHIDEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
eventMask,
&MouseCallback,
nil);
_runLoopRef = CFRunLoopGetMain();
_runLoopSourceRef = CFMachPortCreateRunLoopSource(NULL, _eventTap, 0);
CFRunLoopAddSource(_runLoopRef, _runLoopSourceRef, kCFRunLoopCommonModes);
CGEventTapEnable(_eventTap, true);
CGEventRef MouseCallback(CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void *refcon) {
NSLog(@"Mouse event: %d", type);
return event;
}
This mouse logger need accessibility privilege granted in Privacy & Security. But I found that if accessibility turned off while CGEventTap is running, left & right click are blocked, unless restart macOS.
Although replace kCGEventTapOptionDefault to kCGEventTapOptionListenOnly can fix this issue, but I have other feature which require kCGEventTapOptionDefault.
So I try to detect accessibility is disabled and remove CGEventTap:
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
selector:@selector(didToggleAccessStatus:)
name:@"com.apple.accessibility.api"
object:nil
suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
}
However, the notification won't be sent if user didn't turn off accessibility but removed it from list. Worse, AXIsProcessTrusted() continues to return true.
Is there a way to fix mouse blocked, or detect accessibility removed?
Thanks!
The only reliable way I've found to detect accessibility access is to try creating a new event tap with kCGEventTapOptionDefault
. If the permission is missing, CGEventTapCreate
will return NULL. In my case I was dealing with keyboard events, not mouse events, but I expect it will work the same. Here's the code I use:
bool EventListener::CanFilterEvents()
{
CFMachPortRef thePort = CGEventTapCreate(
kCGSessionEventTap,
kCGTailAppendEventTap,
kCGEventTapOptionDefault, // active filter, not passive listener
CGEventMaskBit(kCGEventKeyDown),
CTapListener::MyTapCallback,
NULL );
bool madeTap = (thePort != NULL);
if (madeTap)
{
CFMachPortInvalidate( thePort );
CFRelease( thePort );
}
return madeTap;
}