# SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie # SPDX-License-Identifier: MIT # A pointer for a compositor with none — a headless sway — made through # zwlr_virtual_pointer_v1 and kept for a whole script, so that a click lands # on a surface the pointer has entered, which one made afresh for each click # never has. After harrier's tests/nixos/drag.py, which only drags. # # pointer.py PROTOCOLS_DIR WIDTH HEIGHT STEP... # # Each step is one of: # # x,y move there # left, right click that button where the pointer is # down, up press or let go of the left button, around moves: a drag # double, triple click the left button two or three times, quickly # wheel:N turn the wheel N notches, down when positive # hwheel:N tilt it sideways N notches, right when positive # sleep:S wait S seconds, for a window to draw, or a test to look # # PROTOCOLS_DIR holds pywayland's bindings for the virtual-pointer protocol, # as `protocols/`. import sys import time sys.path.insert(0, sys.argv[1]) from pywayland.client import Display # noqa: E402 from protocols.wayland import WlSeat # noqa: E402 from protocols.wlr_virtual_pointer_unstable_v1 import ZwlrVirtualPointerManagerV1 # noqa: E402 width, height = int(sys.argv[2]), int(sys.argv[3]) steps = sys.argv[4:] BUTTONS = {"left": 0x110, "right": 0x111, "middle": 0x112} display = Display() display.connect() registry = display.get_registry() found = {} def on_global(reg, name, interface, version): if interface == "wl_seat": found["seat"] = reg.bind(name, WlSeat, 1) elif interface == "zwlr_virtual_pointer_manager_v1": found["manager"] = reg.bind(name, ZwlrVirtualPointerManagerV1, 1) registry.dispatcher["global"] = on_global display.roundtrip() pointer = found["manager"].create_virtual_pointer(found["seat"]) display.roundtrip() # Long enough for the windows to see a pointer appear and bind it. time.sleep(1.0) t = 0 def tick(): global t t += 16 return t for step in steps: if step in BUTTONS: pointer.button(tick(), BUTTONS[step], 1) pointer.frame() display.flush() time.sleep(0.1) pointer.button(tick(), BUTTONS[step], 0) pointer.frame() display.flush() time.sleep(0.3) elif step in ("double", "triple"): for _ in range(2 if step == "double" else 3): for state in (1, 0): pointer.button(tick(), BUTTONS["left"], state) pointer.frame() display.flush() time.sleep(0.05) time.sleep(0.3) elif step in ("down", "up"): pointer.button(tick(), BUTTONS["left"], 1 if step == "down" else 0) pointer.frame() display.flush() time.sleep(0.2) elif step.startswith("wheel:") or step.startswith("hwheel:"): axis = 1 if step.startswith("h") else 0 notches = int(step.split(":")[1]) # A wheel: each notch fifteen units, as libinput reports one. pointer.axis_source(0) pointer.axis_discrete(tick(), axis, 15 * notches, notches) pointer.frame() display.flush() time.sleep(0.3) elif step.startswith("sleep:"): time.sleep(float(step[len("sleep:"):])) else: x, y = (int(v) for v in step.split(",")) # Twice, so that the first enters the surface and the second moves # in it. for _ in range(2): pointer.motion_absolute(tick(), x, y, width, height) pointer.frame() display.flush() time.sleep(0.15) pointer.destroy() display.roundtrip() display.disconnect()