How can I listen for a mouse event in Python on Mac?
Asked Answered
A

3

11

I need to listen for global mouse events(not bound to an app) on my Mac in an app written in Python.

I'm using PyObjC, but I can't figure out how to do it. Plain ObjC examples or other Python techniques also appreciated.

My code so far:

from Quartz import *
def MyFunction(proxy, type, event):
    print event

CGEventTapCreate(kCGHIDEventTap, kCGTailAppendEventTap, kCGEventTapOptionListenOnly, kCGEventLeftMouseDown, MyFunction)

== Segmentation fault

I know I need to add it to an event source later on, but I need to get this working first.

[update]

Using PyObjC form Macports solved the segfault, so now I wrote this:

from Quartz import *

def MyFunction(p, t, e, c):
    print e

tap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly, kCGEventLeftMouseDown, MyFunction, None)

runLoopSource = CFMachPortCreateRunLoopSource(None, tap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);
CGEventTapEnable(tap, True);

CFRunLoopRun();

But this just runs forever and does not respond to mouse events, what is wrong?

Aesthetics answered 25/2, 2010 at 20:3 Comment(0)
M
3

The fourth parameter of CGEventTapCreate is CGEventMask eventsOfInterest, and you gave it kCGEventLeftMouseDown which is an enum of type _CGEventType. Instead of the integer constant, you need to flip the appropriate bit in the bitmask. You can do this using CGEventMaskBit

So instead of this:

tap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap,
    kCGEventTapOptionListenOnly, kCGEventLeftMouseDown, MyFunction, None)

We can do this:

tap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap,
    kCGEventTapOptionListenOnly, CGEventMaskBit(kCGEventLeftMouseDown),
    MyFunction, None)

or equivalently:

tap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap,
    kCGEventTapOptionListenOnly, (1 << kCGEventLeftMouseDown),
    MyFunction, None)
Michellemichels answered 19/12, 2010 at 4:17 Comment(0)
S
1

The documentation for CGEventTapCreate (http://developer.apple.com/mac/library/documentation/Carbon/Reference/QuartzEventServicesRef/Reference/reference.html#//apple_ref/c/func/CGEventTapCreate) says that you need to be root to use kCGHIDEventTap. Are you running your script as root? (sudo is one way to do this)

If you are, you should also check whether tap is None; that will help narrow down the problem. There are several error conditions listed in the documentation that can cause CGEventTapCreate to return NULL, which should be reflected as None in Python.

Succussion answered 7/3, 2010 at 3:26 Comment(3)
Sudo or not does not make a difference, both tap and runLoopSource contain something. Is there an alternative to kCGHIDEventTap? I noticed that CFRunLoopRunInMode(kCFRunLoopDefaultMode, 20, False) runs for 20 seconds, but supplying True ends immediately, but still not output from MyFunction.Aesthetics
Could it be something to do with stringifying the CGEventRef you're trying to print? Perhaps you could try printing a constant string in MyFunction just to make sure. Other than that, I don't really know.Succussion
I tried to just print 'hi', but that didn't help either. Could it be that the function is called somewhere where stdout is set differently?Aesthetics
D
-1

First, CGEventTapCreate and CGEventTapCreateForPSN leak some memory when they are called. This is needed to avoid memory management problems. It is therefore advisable not to call these functions, are at least call them a small number of times.

Now, a mouse event works something like this:

evt = CGEventCreateMouseEvent(None, kCGEventLeftMouseDown, (80, 90), kCGMouseButtonLeft)
self.failUnlessIsInstance(evt, CGEventRef)
Dingo answered 25/2, 2010 at 20:46 Comment(2)
I don't want to send events, I want to listen for them. CGEventCreateMouseEvent is for creating events, not event taps.Aesthetics
There is another method for the Tap Event that I thought I was giving you.Dingo

© 2022 - 2024 — McMap. All rights reserved.