Files
SleppyOwl/tools/linux/xinput.py

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)