100 lines
3.2 KiB
Python
Executable File
100 lines
3.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Mouse and keyboard on an X display through XTest (python3-xlib), for the test stand (stand.sh in …).
|
|
|
|
xinput.py click X Y [BUTTON] move there and click (1 left, 3 right, 8 back)
|
|
xinput.py dclick X Y double click
|
|
xinput.py move X Y
|
|
xinput.py drag X1 Y1 X2 Y2 press the left button at X1 Y1, move in steps to X2 Y2, release
|
|
xinput.py key NAME [NAME…] keys by keysym name: Escape Return Tab BackSpace Left …; "alt+Left" for a chord
|
|
xinput.py type TEXT types Latin text, digits and punctuation
|
|
xinput.py wait SECONDS
|
|
Several commands in one call are separated by ",": xinput.py click 10 10 , key Return
|
|
"""
|
|
import sys, time
|
|
from Xlib import X, XK, display
|
|
from Xlib.ext import xtest
|
|
|
|
d = display.Display()
|
|
MODS = {"alt": "Alt_L", "ctrl": "Control_L", "shift": "Shift_L"}
|
|
|
|
|
|
def code(name):
|
|
keysym = XK.string_to_keysym(name)
|
|
if keysym == 0 and len(name) == 1:
|
|
keysym = ord(name)
|
|
c = d.keysym_to_keycode(keysym)
|
|
if not c:
|
|
sys.exit(f"no key for {name!r}")
|
|
return c
|
|
|
|
|
|
def press(c, down):
|
|
xtest.fake_input(d, X.KeyPress if down else X.KeyRelease, c)
|
|
d.sync()
|
|
|
|
|
|
def key(chord):
|
|
*mods, name = chord.split("+")
|
|
for m in mods: press(code(MODS[m]), True)
|
|
c = code(name)
|
|
press(c, True); time.sleep(0.03); press(c, False)
|
|
for m in reversed(mods): press(code(MODS[m]), False)
|
|
time.sleep(0.08)
|
|
|
|
|
|
def move(x, y):
|
|
xtest.fake_input(d, X.MotionNotify, x=int(x), y=int(y)); d.sync(); time.sleep(0.05)
|
|
|
|
|
|
def click(x, y, button=1, times=1):
|
|
move(x, y)
|
|
for _ in range(times):
|
|
xtest.fake_input(d, X.ButtonPress, int(button)); d.sync(); time.sleep(0.03)
|
|
xtest.fake_input(d, X.ButtonRelease, int(button)); d.sync(); time.sleep(0.06)
|
|
|
|
|
|
def drag(x1, y1, x2, y2, steps=20):
|
|
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
|
|
move(x1, y1)
|
|
xtest.fake_input(d, X.ButtonPress, 1); d.sync(); time.sleep(0.1)
|
|
for i in range(1, steps + 1):
|
|
xtest.fake_input(d, X.MotionNotify, x=x1 + (x2 - x1) * i // steps, y=y1 + (y2 - y1) * i // steps); d.sync(); time.sleep(0.03)
|
|
time.sleep(0.1)
|
|
xtest.fake_input(d, X.ButtonRelease, 1); d.sync(); time.sleep(0.1)
|
|
|
|
|
|
SHIFTED = {c: True for c in '~!@#$%^&*()_+{}|:"<>?ABCDEFGHIJKLMNOPQRSTUVWXYZ'}
|
|
NAMES = {" ": "space", ".": "period", ":": "colon", "/": "slash", "-": "minus", "_": "underscore", "@": "at"}
|
|
|
|
|
|
def type_text(text):
|
|
for ch in text:
|
|
name = NAMES.get(ch, ch)
|
|
c = code(name)
|
|
shift = ch in SHIFTED
|
|
if shift: press(code("Shift_L"), True)
|
|
press(c, True); press(c, False)
|
|
if shift: press(code("Shift_L"), False)
|
|
time.sleep(0.02)
|
|
|
|
|
|
def run(cmd):
|
|
op, args = cmd[0], cmd[1:]
|
|
if op == "click": click(args[0], args[1], args[2] if len(args) > 2 else 1)
|
|
elif op == "dclick": click(args[0], args[1], times=2)
|
|
elif op == "move": move(*args)
|
|
elif op == "drag": drag(*args)
|
|
elif op == "key": [key(a) for a in args]
|
|
elif op == "type": type_text(" ".join(args))
|
|
elif op == "wait": time.sleep(float(args[0]))
|
|
else: sys.exit(__doc__)
|
|
|
|
|
|
cmds, cur = [], []
|
|
for a in sys.argv[1:]:
|
|
if a == ",": cmds.append(cur); cur = []
|
|
else: cur.append(a)
|
|
if cur: cmds.append(cur)
|
|
if not cmds: sys.exit(__doc__)
|
|
for c in cmds: run(c)
|