Search code examples
objective-cmacoscore-graphics

Generating relative mouse events on macOS using Core Graphics


I'm using the Core Graphics framework to generate mouse events. I want to be able to move the mouse around normally and also move the mouse inside of an FPS game. The issue I'm having is that these two situations seem to require different events.

I can use the following to move the mouse outside of a game.

// Event source was created with this:
// CGEventSourceCreate(kCGEventSourceStateCombinedSessionState)

CGPoint mouse_location(CGEventSourceRef source) {
    CGEventRef event = CGEventCreate(source);
    CGPoint loc = CGEventGetLocation(event);
    CFRelease(event);
}

void mouse_move_relative(CGEventSourceRef source, int x, int y) {
    CGPoint pos = mouse_location(source);
    pos.x += x;
    pos.y += y;
    CGEventRef event = CGEventCreateMouseEvent(source, kCGEventMouseMoved, loc, kCGMouseButtonLeft);
    CGEventSetIntegerValueField(event, kCGMouseEventDeltaX, x);
    CGEventSetIntegerValueField(event, kCGMouseEventDeltaY, y);
    CGEventPost(kCGHIDEventTap, event);
    CFRelease(event);
}

This works outside of a game but inside a game, the mouse seems to hit the edge of the screen and stop. So I tried using deltas without setting the position.

void mouse_move_relative(CGEventSourceRef source, int x, int y) {
    CGEventRef event = CGEventCreate(source);
    CGEventSetType(event, kCGEventMouseMoved);
    CGEventSetIntegerValueField(event, kCGMouseEventButtonNumber, kCGMouseButtonLeft);
    CGEventSetIntegerValueField(event, kCGMouseEventDeltaX, x);
    CGEventSetIntegerValueField(event, kCGMouseEventDeltaY, y);
    CGEventPost(kCGHIDEventTap, event);
    CFRelease(event);
}

This works inside of a game and the mouse doesn't hit the edge of the screen but the mouse doesn't move when outside of the game. If I'm inside a game and open a menu screen, I can't move the mouse anymore. I need two separate code paths or two separate modes for these two situations. I'm trying to avoid having to switch between modes and wondering if this is at all possible.

Can I generate a mouse event that works in both situations?

Maybe there's some way that I tell when the system has gone into relative mouse mode and change the emitted events accordingly? I'm not sure if Core Graphics can do that (or if that even makes sense). Also, I don't fully understand what kCGHIDEventTap and kCGEventSourceStateCombinedSessionState mean. The docs are talking about taps and tables and I don't really get it. Maybe all I need to do is change one of those...?

Note that the above snippets might not be quite right because I'm using a wrapper library in Rust but that's besides the point.


Solution

  • Turned out to be quite simple. All I needed to do was limit the mouse coordinates within the display.

    CGDirectDisplayID display = CGMainDisplayID();
    size_t width = CGDisplayPixelsWide(display);
    size_t height = CGDisplayPixelsHigh(display);
    pos.x = min(max(pos.x, 0), width - 1);
    pos.y = min(max(pos.y, 0), height - 1);