🔒 Repository is read-only – file editing is disabled.
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)