I'm trying to add an event trap to enable/disable event from my magic trackpad. I thought this would be straight forward, i.e. register an event trap and when required, discard the event by returning NULL
. The idea is to use the pad for some specific, time consuming data entry, the applications to enter the data into are third party ones so I can't just add code to do want I want there. So i figured I'd monitor the system events and then send the desired input via a bunch of CGEventCreateKeyboardEvent
s.
The problem is returning null does not seem to discard the events, a bit more investigation suggests that this is not restricted to those coming from the trackpad but also my default usb mouse.
My code is below. With what is below i'd expect not to be able to move the mouse, if I change (A) to use kCGEventScrollWheel
or kCGEventLeftMouseDragged
then event is consumed, i.e. scrolling or left btn drag don't occur. Does this mean that not all events can be discarded? Hopefully I'm just missing something obvious here
#define case_print(a) case a: printf("%s - %d\n",#a,a); break;
CGEventRef eventOccurred(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* refcon) {
int subType = CGEventGetIntegerValueField(event, kCGMouseEventSubtype);
if (type == NSEventTypeGesture || subType == NX_SUBTYPE_MOUSE_TOUCH) {
printf("touchpad\n");
switch(type) {
case_print(kCGEventNull)
case_print(kCGEventLeftMouseDown)
case_print(kCGEventLeftMouseUp)
case_print(kCGEventRightMouseDown)
case_print(kCGEventRightMouseUp)
case_print(kCGEventMouseMoved)
case_print(kCGEventLeftMouseDragged)
case_print(kCGEventRightMouseDragged)
case_print(kCGEventScrollWheel)
case_print(kCGEventOtherMouseDown)
case_print(kCGEventOtherMouseUp)
case_print(kCGEventOtherMouseDragged)
case_print(kCGEventTapDisabledByTimeout)
case_print(kCGEventTapDisabledByUserInput)
case_print(NSEventTypeGesture)
case_print(NSEventTypeMagnify)
case_print(NSEventTypeSwipe)
case_print(NSEventTypeRotate)
case_print(NSEventTypeBeginGesture)
case_print(NSEventTypeEndGesture)
default:
printf("default: %d\n",type);
break;
}
event = NULL;
} else {
if (type == kCGEventMouseMoved) { // (A)
printf("discarding mouse event");
event = NULL;
}
}
return event;
}
CFMachPortRef createEventTap() {
CGEventMask eventMask = NSAnyEventMask;
if (!AXAPIEnabled() && !AXIsProcessTrusted()) {
printf("axapi not enabled");
}
return CGEventTapCreate(kCGHIDEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
eventMask,
eventOccurred,
NULL);
}
int main (int argc, const char * argv[]) {
CFMachPortRef tap = createEventTap();
if (tap) {
CFRunLoopSourceRef rl = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), rl, kCFRunLoopCommonModes);
CGEventTapEnable(tap, true);
CFRunLoopRun();
printf("Tap created.\n");
sleep(-1);
} else {
printf("failed!\n");
}
return 0;
}
Note, "axapi not enabled" is not output although i don't think the accessibility option affects anything but the keyboard events.
BTW, I've seen a few similar posts on how to get the events from the touch pad, just nothing applicable to discarding them (other than returning null should work).
I think atm it is not possible to simply discard these events. From the CGEventTypes.h header file:
"The event passed to the callback is retained by the calling code, and is released after the callback returns and the data is passed back to the event system. If a different event is returned by the callback function, then that event will be released by the calling code along with the original event, after the event data has been passed back to the event system."
I've played around with this a little and it seems after the callback returns the window server actively checks again for what you've done to the event. It only allows deletion of key events and mouse up/down events, but just as it ignores deletion of mouse moved events (well, the mouse rendering is processed elsewhere anyways, I guess) it seems to ignore deletion (i.e. your callback returning NULL) for mouse gestures.
Figures, considering tapping for gestures is not specifically explained in the documentation (no type defined at this level).
I tried returning a different event (a key press) and this one is then handled additionally to the original gesture. Releasing the event in your callback doesn't do it, either (of course), just results in an exception.
The only thing I didn't try is directly manipulating the passed CGEvent's internal data to at least make the gesture do nothing (deleting all movement and such), but that's kind of hard because there are no specific methods defined to do that. I'm pretty sure the needed information is somewhere in the various fields accessible via the CGEventSet* methods, though.
I looked down as far as IOLLEvent.h to figure out their data structure, but this was way too ugly for my taste to dig into much further. Let's hope Lion offers a little more regarding the gesture type of events on the CF level.
I can corroborate that on 10.6 neither of the following methods succeed for this purpose.
1-returning NULL
2-returning a new mouse event with the previous cursor position
3-returning the passed event with it's kCGMouseEventDelta's changed to 0
I can not speak of 10.7 but let's just say i would not get my hopes up.
There is one thing however that you could do instead of dropping the movement move it back to the previous location with CGWarpMouseCursorPosition, in effect the cursor will stop moving with only a slight change on the first one.
CGAssociateMouseAndMouseCursorPosition(FALSE);
Will prevent the mouse from functioning while your app is active.
Still exploring if I can extend that to global apps...
http://lists.apple.com/archives/quartz-dev/2007/May/msg00112.html
If your tap is passive, returning NULL will leave the event stream unaffected. From the CGEventTapCallBack reference documentation:
"If the event tap is an passive listener, your callback function may return the event that is passed in, or NULL. In either case, the event stream is not affected."
However, it looks like your tap is active. Thus your returning NULL should delete the event. Have you considered modifying the event to nullify the action?
Also note that the call CGEventTapCreate requires root user permissions to intercept all events. Is your process running as root?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With