Macos Application hangs when using NSDistributedNotificationCenter and CFRunLoopAddSource

Hello all:

I have MacOS application built using Qt. There I have created NSDistributedNotificationCenter to be notified when accessibility settings change (observing "com.apple.accessibility.api" ). Also I have CFRunLoopAddSource to monitor key pressed events.

However when I run the program and I change accessibility application hangs and I cannot run it normally.

Could someone help to see why that is happening?

This is the code:

Here I am creating observer: Creation/deletion is controlled by button click

- (void)createObserver
{
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
  selector:@selector(didToggleAccessStatus:)
  name:@"com.apple.accessibility.api"
  object:nil
  suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
}


And this is how I add key event logger:


// Create an event tap to retrieve keypresses.
CGEventMask eventMask = (CGEventMaskBit(kCGEventKeyDown) | 
CGEventMaskBit(kCGEventFlagsChanged) |
  CGEventMaskBit(kCGEventLeftMouseDown) |
  CGEventMaskBit(kCGEventRightMouseDown) |
  CGEventMaskBit(kCGEventMouseMoved) |
  CGEventMaskBit(kCGEventScrollWheel));
  //| CGEventMaskBit(kCGEventLeftMouseDragged)
  //| CGEventMaskBit(kCGEventRightMouseDragged)
  //| CGEventMaskBit(kCGEventOtherMouseDragged););
CFMachPortRef m_eventTap = nullptr;
CFRunLoopSourceRef m_runLoopSource = nullptr;
 m_eventTap = CGEventTapCreate(
  kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
  eventMask, myCGEventCallback, nullptr);

 if (m_eventTap != Q_NULLPTR) {
  NSLog(@"CGEventTap created");
  // Create a run loop source and add enable the event tap.
  m_runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTap, 0);
  CFRunLoopAddSource(CFRunLoopGetCurrent()/*CFRunLoopGetMain()*/, m_runLoopSource, kCFRunLoopCommonModes);
  CGEventTapEnable(m_eventTap, true);
  //CFRunLoopRun();
 }
 else {
  m_runLoopSource = Q_NULLPTR;
  NSLog(@"Error creating CGEventTap");
 }


Any ideas about what is going on there? And how can I solve that


Thanks in advance and regards

Replies

What does -didToggleAccessStatus: do? Where is your program stuck? You can potentially see that by taking a process sample (using Activity Monitor or the sample command). Or you can just stop the program in the debugger and see where the various threads are stopped.

didToggleAccessStatus so far only prints a message.

I think problem is related to run loops. Application does not really hang because I added log to print when key event and it gets printed, but laptop stop responding to mouse events. I can move mouse only but clicks don't work.

Please could someone explain to me what may happen?

Thanks

OK, so what does myCGEventCallback() do?


Note that if it doesn't return the event that it receives, the event is effectively deleted.

Here is method implementation:


CGEventRef
myCGEventCallback(CGEventTapProxy proxy, CGEventType type,
                  CGEventRef event, void *refcon)
{
    if ((type != kCGEventKeyDown) && (type != kCGEventKeyUp))
            return event;
    qDebug() << "callback called!";
    return event;
}

Any idea why this may happen?

Thanks

No ideas here? I am quite interested in finding out the root of this problem

Not sure if it is still an actual question but it is quite common one. There are few things here to be aware of:

Your app must be trusted for accessibility. When AXIsProcessTrusted() returns false, it is a bad idea to use event taps with kCGEventTapOptionDefault.

You may use kCGEventTapOptionListenOnly option (instead of kCGEventTapOptionDefault). If you're not going to prevent events from propagating further, you can use this option. It should not block any input even if AXIsProcessTrusted() is false.

If you're going to prevent events from propagating, well, don't create tap until AXIsProcessTrusted() is true and make sure you remove the tap once it becomes false. Still there is a caveat. If the user revokes accessibility permission for your app by removing the app from the list (instead of unchecking the check box), AXIsProcessTrusted() continues to return true but the tap will hang.