Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constrain mouse movement to region in OS X without jitter

I would like to constrain mouse movement to a specific rectangular region of the screen in OS X 10.11. I modified some code from MouseTools (below) to do this but it's jittery when you hit the edge of the screen. How can I get rid of this jitter?

// gcc -Wall constrain.cpp -framework ApplicationServices -o constrain

#include <ApplicationServices/ApplicationServices.h>

int main (int argc, const char * argv[]) {
    if(argc != 5) {
        printf("Usage: constrain left top right bottom\n");
        return 0;
    }
    int leftBound = strtol(argv[1], NULL, 10);
    int topBound = strtol(argv[2], NULL, 10);
    int rightBound = strtol(argv[3], NULL, 10);
    int bottomBound = strtol(argv[4], NULL, 10);
    CGEventTapLocation tapLocation = kCGHIDEventTap;
    CGEventSourceRef sourceRef = CGEventSourceCreate(kCGEventSourceStatePrivate);
    while(true) {
        CGEventRef mouseEvent = CGEventCreate(NULL);
        CGPoint mouseLoc = CGEventGetLocation(mouseEvent);
        int x = mouseLoc.x, y = mouseLoc.y;
        if(x < leftBound || x > rightBound || y < topBound || y > bottomBound) {
            if(x < leftBound) x = leftBound;
            if(x > rightBound) x = rightBound;
            if(y < topBound) y = topBound;
            if(y > bottomBound) y = bottomBound;
            CGEventRef moveMouse = CGEventCreateMouseEvent(sourceRef, kCGEventMouseMoved, CGPointMake(x, y), 0);
            CGEventPost(tapLocation, moveMouse);
            CFRelease(moveMouse);
        }
        CFRelease(mouseEvent);
        usleep(8*1000); // 8ms, ~120fps
    }
    CFRelease(sourceRef);
    return 0;
}

Here are some other things I tried: Receiving, Filtering, and Modifying Mouse Events shows how to create a callback for mouse movements using CGEventTapCreate. In the code it says "We can change aspects of the mouse event. For example, we can use CGEventSetLocation(event, newLocation)." Unfortunately this doesn't actually change the mouse location (complete example here). The only ways I've been able to change the mouse location are by doing a CGEventPost or CGWarpMouseCursorPosition. I also tried using kCGHIDEventTap instead of kCGSessionEventTap, and I checked that I had super user permissions and accessibility enabled for the app. It seems like it should work because there is another example of modifying key presses which works correctly. Instead of using CGEventSetLocation I also tried using CGEventSetIntegerValueField (like in the keyboard example) to set the x and y deltas to 0, but this didn't change anything.

Finally, I also tried using CGEventPost and CGWarpMouseCursorPosition inside the CGEventCallback, the "invisible border" still it is more jittery than the above code.

like image 605
Kyle McDonald Avatar asked Dec 01 '16 07:12

Kyle McDonald


People also ask

How can I make my Mac cursor smoother?

Click on Apple icon in top-menu bar and select System Preferences… in the dropdown menu. On System Preferences screen, click on the Mouse icon > on the next screen, adjust Tracking and Scrolling Speed by moving the slider to right.

How do I customize my Mac mouse?

On your Mac, use the Pointer pane of Accessibility Display preferences to change the size and color of the pointer to make it easier to find on the screen. To change these preferences, choose Apple menu > System Preferences, click Accessibility , click Display, then click Pointer.


1 Answers

I've implemented this for Wine in the Mac driver. It gets a bit complicated.

The approach is to basically disable movement of the mouse cursor by calling CGAssociateMouseAndMouseCursorPosition(false). This right here achieves what you want if the constraining rectangle is the 1x1 rect at the cursor's current location.

On top of that, I then use an event tap to observe the mouse move events. These don't change position but have the deltas of what the user attempted to do. I then use CGWarpMouseCursorPosition() to move the cursor according to those deltas and adjust the events before passing them along.

Note that the location information in the mouse events is not meaningful. It will be stale. Some of the events will have been queued before your last warp and so have an old location. You need to track where the cursor is yourself. It starts wherever it was when you disassociated it from the mouse and changes whenever you warp it. You add the deltas to that (not to the event location), clip that to the constraining rectangle, and use that as the new position for the event and for the warp. For the next event, you'll start from this new position. Etc.

There's an additional unfortunate wrinkle. CGWarpMouseCursorPosition() affects the delta values of subsequent mouse move events. Basically, the distance that you warped the cursor is added to the first mouse move event that's queued after the warp. Unchecked, this will effectively double the user's mouse movements with each event for a runaway feedback loop and the cursor will shoot off to one extreme or the other.

So, you have to keep a record of the warps you perform, when you performed them, and how much they changed the position. Then, as events come through the tap, you compare their time to the warp times. For any warps that are earlier than the current event, you subtract out their position change from the event's deltas and remove them from the list.

like image 136
Ken Thomases Avatar answered Sep 23 '22 03:09

Ken Thomases