Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Consuming OSX mouse/trackpad events with an event tap

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 CGEventCreateKeyboardEvents.

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).

like image 373
vickirk Avatar asked Dec 23 '10 12:12

vickirk


4 Answers

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.

like image 172
Gero Avatar answered Oct 19 '22 19:10

Gero


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.

like image 36
valexa Avatar answered Oct 19 '22 17:10

valexa


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

like image 37
ck_ Avatar answered Oct 19 '22 18:10

ck_


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?

like image 43
Graham Miln Avatar answered Oct 19 '22 17:10

Graham Miln