🔒 Repository is read-only – file editing is disabled.

PaganDE/tools/x11_mouse.py main

105 linii Raw ← Powrót
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
#!/usr/bin/env python3
"""Narzędzie DEV: wstrzykiwanie zdarzeń myszy przez XTest (X11).

PO CO: pozwala automatycznie przetestować interakcje kompozytora (przeciąganie
okna, doklejanie do krawędzi, resize za rogi) bez ręcznego machania myszką.
Działa na backendzie `winit`, bo ten tworzy zwykłe okno X11.

Użycie (`DISPLAY` musi wskazywać na X z uruchomionym kompozytorem):

    python3 tools/x11_mouse.py move X Y
    python3 tools/x11_mouse.py click X Y
    python3 tools/x11_mouse.py drag X1 Y1 X2 Y2 [kroki]

To NIE jest część PaganDE — zwykły skrypt pomocniczy.
"""

import ctypes
import ctypes.util
import sys
import time

x11 = ctypes.CDLL(ctypes.util.find_library("X11"))
xtst = ctypes.CDLL(ctypes.util.find_library("Xtst"))

x11.XOpenDisplay.restype = ctypes.c_void_p
x11.XOpenDisplay.argtypes = [ctypes.c_char_p]
x11.XDefaultScreen.restype = ctypes.c_int
x11.XDefaultScreen.argtypes = [ctypes.c_void_p]
x11.XFlush.argtypes = [ctypes.c_void_p]

xtst.XTestFakeMotionEvent.argtypes = [
    ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_ulong
]
xtst.XTestFakeButtonEvent.argtypes = [
    ctypes.c_void_p, ctypes.c_uint, ctypes.c_int, ctypes.c_ulong
]

# BTN_LEFT w numeracji X11.
BUTTON_LEFT = 1

_display = None
_screen = 0


def open_display():
    global _display, _screen
    _display = x11.XOpenDisplay(None)
    if not _display:
        raise SystemExit("nie mogę otworzyć DISPLAY (ustaw DISPLAY=:N)")
    _screen = x11.XDefaultScreen(_display)


def move(x, y):
    xtst.XTestFakeMotionEvent(_display, _screen, int(x), int(y), 0)
    x11.XFlush(_display)


def button(pressed):
    xtst.XTestFakeButtonEvent(_display, BUTTON_LEFT, 1 if pressed else 0, 0)
    x11.XFlush(_display)


def click(x, y):
    move(x, y)
    time.sleep(0.05)
    button(True)
    time.sleep(0.08)
    button(False)
    time.sleep(0.05)


def drag(x1, y1, x2, y2, steps=24):
    """Płynne przeciągnięcie: kompozytor dostaje wiele zdarzeń `motion`,
    dzięki czemu widzi wjazd w strefę doklejania, a nie jeden skok."""
    move(x1, y1)
    time.sleep(0.1)
    button(True)
    time.sleep(0.15)
    for i in range(1, steps + 1):
        t = i / steps
        move(x1 + (x2 - x1) * t, y1 + (y2 - y1) * t)
        time.sleep(0.02)
    time.sleep(0.15)
    button(False)
    time.sleep(0.15)


def main(argv):
    open_display()
    if len(argv) < 2:
        raise SystemExit(__doc__)
    cmd = argv[1]
    if cmd == "move":
        move(int(argv[2]), int(argv[3]))
    elif cmd == "click":
        click(int(argv[2]), int(argv[3]))
    elif cmd == "drag":
        steps = int(argv[6]) if len(argv) > 6 else 24
        drag(int(argv[2]), int(argv[3]), int(argv[4]), int(argv[5]), steps)
    else:
        raise SystemExit(f"nieznane polecenie: {cmd}")


if __name__ == "__main__":
    main(sys.argv)