91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
139 lines
4.4 KiB
Python
139 lines
4.4 KiB
Python
"""Cross-platform mouse control via pynput."""
|
|
|
|
from typing import List, Optional, Tuple
|
|
|
|
from pynput.mouse import Button as _Button
|
|
from pynput.mouse import Controller as _MouseController
|
|
|
|
_mouse = _MouseController()
|
|
|
|
|
|
def _map_button(button: str) -> _Button:
|
|
"""Map a button name string to a pynput Button."""
|
|
b = (button or "left").lower()
|
|
if b == "right":
|
|
return _Button.right
|
|
if b == "middle":
|
|
return _Button.middle
|
|
return _Button.left
|
|
|
|
|
|
# ── Basic clicks ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
def click(x: int, y: int, button: str = "left") -> None:
|
|
"""Single click at (x, y)."""
|
|
_mouse.position = (x, y)
|
|
_mouse.click(_map_button(button), 1)
|
|
|
|
|
|
def right_click(x: int, y: int) -> None:
|
|
"""Right-click at (x, y)."""
|
|
click(x, y, "right")
|
|
|
|
|
|
def double_click(x: int, y: int) -> None:
|
|
"""Double left-click at (x, y)."""
|
|
_mouse.position = (x, y)
|
|
_mouse.click(_Button.left, 2)
|
|
|
|
|
|
# ── Cursor movement ───────────────────────────────────────────────────────────
|
|
|
|
|
|
def move_to(x: int, y: int) -> None:
|
|
"""Move cursor to (x, y) without clicking."""
|
|
_mouse.position = (x, y)
|
|
|
|
|
|
# ── Mouse button hold / release ───────────────────────────────────────────────
|
|
|
|
|
|
def mouse_down(x: Optional[int] = None, y: Optional[int] = None, button: str = "left") -> None:
|
|
"""Press and hold a mouse button, optionally moving to (x, y) first."""
|
|
if x is not None and y is not None:
|
|
_mouse.position = (x, y)
|
|
_mouse.press(_map_button(button))
|
|
|
|
|
|
def mouse_up(x: Optional[int] = None, y: Optional[int] = None, button: str = "left") -> None:
|
|
"""Release a mouse button, optionally moving to (x, y) first."""
|
|
if x is not None and y is not None:
|
|
_mouse.position = (x, y)
|
|
_mouse.release(_map_button(button))
|
|
|
|
|
|
# ── Drag ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
def drag(
|
|
start_x: int,
|
|
start_y: int,
|
|
end_x: int,
|
|
end_y: int,
|
|
button: str = "left",
|
|
) -> None:
|
|
"""Drag from (start_x, start_y) to (end_x, end_y)."""
|
|
btn = _map_button(button)
|
|
_mouse.position = (start_x, start_y)
|
|
_mouse.press(btn)
|
|
_mouse.position = (end_x, end_y)
|
|
_mouse.release(btn)
|
|
|
|
|
|
def drag_to(x: int, y: int, button: str = "left") -> None:
|
|
"""Drag from the current cursor position to (x, y)."""
|
|
btn = _map_button(button)
|
|
_mouse.press(btn)
|
|
_mouse.position = (x, y)
|
|
_mouse.release(btn)
|
|
|
|
|
|
def drag_path(path: List[Tuple[int, int]], button: str = "left") -> None:
|
|
"""Drag through a sequence of (x, y) points."""
|
|
if not path:
|
|
return
|
|
btn = _map_button(button)
|
|
_mouse.position = path[0]
|
|
_mouse.press(btn)
|
|
for x, y in path[1:]:
|
|
_mouse.position = (x, y)
|
|
_mouse.release(btn)
|
|
|
|
|
|
# ── Scroll ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
def scroll(dx: int, dy: int) -> None:
|
|
"""Scroll by (dx, dy). Positive dy = scroll up."""
|
|
_mouse.scroll(dx, dy)
|
|
|
|
|
|
def scroll_up(clicks: int = 3) -> None:
|
|
"""Scroll up by *clicks* notches."""
|
|
_mouse.scroll(0, abs(clicks))
|
|
|
|
|
|
def scroll_down(clicks: int = 3) -> None:
|
|
"""Scroll down by *clicks* notches."""
|
|
_mouse.scroll(0, -abs(clicks))
|
|
|
|
|
|
def scroll_left(clicks: int = 3) -> None:
|
|
"""Scroll left by *clicks* notches."""
|
|
_mouse.scroll(-abs(clicks), 0)
|
|
|
|
|
|
def scroll_right(clicks: int = 3) -> None:
|
|
"""Scroll right by *clicks* notches."""
|
|
_mouse.scroll(abs(clicks), 0)
|
|
|
|
|
|
# ── Position ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
def position() -> Tuple[int, int]:
|
|
"""Return the current cursor position as (x, y)."""
|
|
x, y = _mouse.position
|
|
return int(x), int(y)
|