Write an EventTap. Documentation can be found here.
In MacOS X every event (e.g. every keyboard key pressed, every mouse key pressed or mouse movement) creates an event that travels the following path:
Driver (Kernel) -> Window Server (privileged) -> User (Login) Session -> Active Application
Everywhere where I wrote an arrow (->
) an EventTap can be placed to either only look at the event (a listen only EventTap) or to either modify or drop the event (an event filtering EventTap). Please note that to catch an event between Driver and WindowServer, your daemon must run with root privileges.
Here is some sample code:
// Compile with:
// gcc -framework ApplicationServices -o MouseWatcher MouseWatcher.c
//
// Start with:
// ./MouseWatcher
//
// Terminate by hitting CTRL+C
#include <ApplicationServices/ApplicationServices.h>
static CGEventRef myEventTapCallback (
CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void * refcon
) {
CGPoint mouseLocation;
// If we would get different kind of events, we can distinguish them
// by the variable "type", but we know we only get mouse moved events
mouseLocation = CGEventGetLocation(event);
printf(
"Mouse is at x/y: %u/%u\n",
(unsigned int)mouseLocation.x,
(unsigned int)mouseLocation.y
);
// Pass on the event, we must not modify it anyway, we are a listener
return event;
}
int main (
int argc,
char ** argv
) {
CGEventMask emask;
CFMachPortRef myEventTap;
CFRunLoopSourceRef eventTapRLSrc;
// We only want one kind of event at the moment: The mouse has moved
emask = CGEventMaskBit(kCGEventMouseMoved);
// Create the Tap
myEventTap = CGEventTapCreate (
kCGSessionEventTap, // Catch all events for current user session
kCGTailAppendEventTap, // Append to end of EventTap list
kCGEventTapOptionListenOnly, // We only listen, we don't modify
emask,
&myEventTapCallback,
NULL // We need no extra data in the callback
);
// Create a RunLoop Source for it
eventTapRLSrc = CFMachPortCreateRunLoopSource(
kCFAllocatorDefault,
myEventTap,
0
);
// Add the source to the current RunLoop
CFRunLoopAddSource(
CFRunLoopGetCurrent(),
eventTapRLSrc,
kCFRunLoopDefaultMode
);
// Keep the RunLoop running forever
CFRunLoopRun();
// Not reached (RunLoop above never stops running)
return 0;
}
The answer from Dave is the nicer Cocoa way of doing pretty much the same thing; basically Cocoa does the same as I do in my sample above behind the scenes, just wrapped into a static method. The code of Dave works only on 10.6, though, the above works in 10.4, 10.5 and 10.6.