Home > Back-end >  Keep mouse within area in Swift
Keep mouse within area in Swift

Time:02-23

I'm trying to prevent the mouse cursor from leaving a specific area of the screen. I can't find a native method to do this, so I'm trying to do it manually.

So far I have this:

NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved, .leftMouseDragged], handler: {(event: NSEvent) in
    let x = event.locationInWindow.flipped.x;
    let y = event.locationInWindow.flipped.y;
        
    if (x <= 100) {
        CGWarpMouseCursorPosition(CGPoint(x: 100, y: y))
    }
})

// elsewhere to flip y coordinates
extension NSPoint {
    var flipped: NSPoint {
        let screenFrame = (NSScreen.main?.frame)!
        let screenY = screenFrame.size.height - self.y
        return NSPoint(x: self.x, y: screenY)
    }
}

This stops the cursor from going off the X axis. Great. But it also stops the cursor from sliding along the y axis at X=100.

So I tried to add the delta:

NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved, .leftMouseDragged], handler: {(event: NSEvent) in
    let x = event.locationInWindow.flipped.x;
    let y = event.locationInWindow.flipped.y;
    let deltaY = event.deltaY;
        
    if (x <= 100) {
        CGWarpMouseCursorPosition(CGPoint(x: 100, y: y   deltaY))
    }
})

Now it does slide along the Y axis. But the acceleration is way off, it's too fast. What I don't get is that if I try to do y - deltaY it slides like I expect, but reversed:

NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved, .leftMouseDragged], handler: {(event: NSEvent) in
    let x = event.locationInWindow.flipped.x;
    let y = event.locationInWindow.flipped.y;
    let deltaY = event.deltaY;
        
    if (x <= 100) {
        CGWarpMouseCursorPosition(CGPoint(x: 100, y: y - deltaY))
    }
})

Now the cursor is sliding along the Y axis at X=100 with proper acceleration (like sliding the cursor against the edge of the screen), but it's reversed. Moving the mouse up, moves the cursor down.

How do I get proper smooth sliding of the cursor, in the proper direction, at the edge of my custom area?

Or is there a better way to achieve what I'm trying to do?

CodePudding user response:

I figured it out. I need to subtract the previous deltas.

So now I have this instead:

var oldDeltaX: CGFloat = 0;
var oldDeltaY: CGFloat = 0;

NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved, .leftMouseDragged, .rightMouseDragged], handler: {(event: NSEvent) in
  let deltaX = event.deltaX - oldDeltaX;
  let deltaY = event.deltaY - oldDeltaY;
  let x = event.locationInWindow.flipped.x;
  let y = event.locationInWindow.flipped.y;

  let window = (NSScreen.main?.frame.size)!;
  let width = CGFloat(1920);
  let height = CGFloat(1080);

  let widthCut = (window.width - width) / 2;
  let heightCut = (window.height - height) / 2;

  let xPoint = clamp(x   deltaX, minValue: widthCut, maxValue: window.width - widthCut);
  let yPoint = clamp(y   deltaY, minValue: heightCut, maxValue: window.height - heightCut);
 
  oldDeltaX = xPoint - x;
  oldDeltaY = yPoint - y;
            
  CGWarpMouseCursorPosition(CGPoint(x: xPoint, y: yPoint));
});

public func clamp<T>(_ value: T, minValue: T, maxValue: T) -> T where T : Comparable {
  return min(max(value, minValue), maxValue)
}

extension NSPoint {
  var flipped: NSPoint {
    let screenFrame = (NSScreen.main?.frame)!
    let screenY = screenFrame.size.height - self.y
    return NSPoint(x: self.x, y: screenY)
  }
}

This will restrict the mouse in a 1920x1080 square of the display.

I found Godot's source code to be good resource: https://github.com/godotengine/godot/blob/51a00c2855009ce4cd6475c09209ebd22641f448/platform/osx/display_server_osx.mm#L1087

Is this the best or most perfomant way to do it? I don't know, but it works.

  • Related