1
votes

I am successfully able to react to keyboard events through my window controller's keyDown: method.
The problem arises while performing a mouse drag:
Keyboard events seem to be delayed and will only fire on mouse up.

To be clear, what I mean is:
• place a log statement in you window controller's keyDown: method
• launch your app, perform some drag operation (on a NSSlider for ex.)
• while maintaining the drag, press any key: nothing logs to the console.
• release drag : logs appear, yay…

The control i am dragging is a custom NSSlider.
I have implemented the dragging mechanism using a 'Mouse-Tracking Loop' Approach. for what I understand, when dragging, NSApplication's main run loop mode is being switched to NSEventTrackingRunLoopMode, thus restricting incoming events.

So, i simply added NSKeyDownMask & NSKeyUpMask in my tracking loop and when encoutered, called self.nextResponder keyDown/up: method accordingly. My problem is solved for this particular custom subclass.

But what about cocoa's native controls ? I can't code that exception...

I had hoped for NSEvent's "addLocalMonitorForEventsMatchingMask:" method but alas, says doc : "will not be called for events that are consumed by nested event-tracking loops such as control tracking, menu tracking, or window dragging".

So, isn't there a straightforward solution to receive keyboard events regardless of the app's runloop mode ?

1
I have a similar problem in that timers don't seem to fire when I use the 'Mouse-Tracking Loop' Approach - no matter what run loop mode I add the timers to. Timers do fire when a top level menu is pulled down... - so any progress on this?Tom Andersen

1 Answers

2
votes

As you found in the documentation for the NSEvent class' addGlobalMonitorForEventsMatchingMask:handler:, this limitation is by design.

However, you can work around it by using the IOKit framework (specifically the IOHID portions) to receive low level device events/interrupts. I just had to do this recently for tracking some specific keypresses during mouse-drags.

The basic gist is to create an IOHID manager with IOHIDManagerCreate(), then add the type(s) of devices to the manager that it should "monitor" with IOHIDManagerSetDeviceMatchingMultiple(), register a callback with the manager via IOHIDManagerRegisterInputValueCallback(), schedule the proper run-loop for the manager with IOHIDManagerScheduleWithRunLoop(), and finally open the manager with IOHIDManagerOpen().

To get these low level events during mouse-drags, perform this setup in a separate thread. When scheduling the run-loop for the manager, use CFRunLoopGetCurrent() to get the run loop for the current thread, and call CFRunLoopRun() after IOHIDManagerOpen().

This guide from Apple can help you get started, along with this Q&A here on Stack Overflow.