68 lines
3.2 KiB
Python
68 lines
3.2 KiB
Python
"""Run on box: XTest input to decoded marker. Excludes USB and scanout."""
|
|
import ctypes as C
|
|
import os
|
|
import json
|
|
import statistics
|
|
import subprocess
|
|
import time
|
|
os.environ.update(DISPLAY=':0',XAUTHORITY='/home/armbian/.Xauthority')
|
|
x=C.CDLL('libX11.so.6'); t=C.CDLL('libXtst.so.6')
|
|
x.XOpenDisplay.restype=C.c_void_p
|
|
x.XDefaultRootWindow.argtypes=[C.c_void_p]; x.XDefaultRootWindow.restype=C.c_ulong
|
|
x.XSync.argtypes=[C.c_void_p,C.c_int]
|
|
x.XGetImage.argtypes=[C.c_void_p,C.c_ulong,C.c_int,C.c_int,C.c_uint,C.c_uint,C.c_ulong,C.c_int]; x.XGetImage.restype=C.c_void_p
|
|
x.XGetPixel.argtypes=[C.c_void_p,C.c_int,C.c_int]; x.XGetPixel.restype=C.c_ulong
|
|
x.XDestroyImage.argtypes=[C.c_void_p]
|
|
x.XStringToKeysym.argtypes=[C.c_char_p]; x.XStringToKeysym.restype=C.c_ulong
|
|
x.XKeysymToKeycode.argtypes=[C.c_void_p,C.c_ulong];x.XKeysymToKeycode.restype=C.c_uint
|
|
t.XTestFakeMotionEvent.argtypes=[C.c_void_p,C.c_int,C.c_int,C.c_int,C.c_ulong]
|
|
t.XTestFakeButtonEvent.argtypes=[C.c_void_p,C.c_uint,C.c_int,C.c_ulong]
|
|
t.XTestFakeKeyEvent.argtypes=[C.c_void_p,C.c_uint,C.c_int,C.c_ulong]
|
|
d=x.XOpenDisplay(None);assert d;root=x.XDefaultRootWindow(d)
|
|
pid=int(subprocess.check_output(['pgrep','-x','moonlight']).strip())
|
|
assert int(subprocess.check_output(['xdotool','getwindowfocus','getwindowpid']).strip())==pid
|
|
|
|
def key(name):
|
|
code=x.XKeysymToKeycode(d,x.XStringToKeysym(name.encode()));assert code
|
|
t.XTestFakeKeyEvent(d,code,1,0);t.XTestFakeKeyEvent(d,code,0,0);x.XSync(d,0)
|
|
def move(px,py):
|
|
t.XTestFakeMotionEvent(d,-1,px,py,0);x.XSync(d,0)
|
|
def read():
|
|
im=x.XGetImage(d,root,40,1380,1320,1,0xffffffff,2);assert im
|
|
try:
|
|
def val(offset,n):
|
|
pixels=[x.XGetPixel(im,offset+20+i*40,0)&0xffffff for i in range(n)]
|
|
if any(0x404040<v<0xc0c0c0 for v in pixels):return -1
|
|
return sum((1<<i) for i,v in enumerate(pixels) if v>0xc0c0c0)
|
|
return val(0,16),val(760,8),val(1120,4)
|
|
finally:x.XDestroyImage(im)
|
|
def await_marker(m,s):
|
|
deadline=time.monotonic()+4
|
|
while time.monotonic()<deadline:
|
|
seen,magic,mode=read()
|
|
if (seen,magic,mode)==(s,0x5a,m):return
|
|
time.sleep(.002)
|
|
raise AssertionError(('marker timeout',m,s,read()))
|
|
def sample(m,s,action):
|
|
start=time.monotonic();action();await_marker(m,s);return (time.monotonic()-start)*1000
|
|
def stats(a):
|
|
b=sorted(a);return dict(samples_ms=a,p50=statistics.median(b),p95=b[int(len(b)*.95)],maximum=max(b))
|
|
|
|
result={'pid':pid,'method':'XTest to decoded unique GTK marker; actual buttons/masked entry; submit has no backend; no USB/scanout'}
|
|
for m in [1,2,3]:
|
|
move(300,300);time.sleep(.4);key('F'+str(m));await_marker(m,0)
|
|
values=[]
|
|
for i in range(1,101):
|
|
px=750 if i%2 else 1750
|
|
if m==1: action=lambda:move(px,550)
|
|
elif m==2:
|
|
move(px,550);time.sleep(.12)
|
|
def action():
|
|
t.XTestFakeButtonEvent(d,1,1,0);x.XSync(d,0);time.sleep(.04)
|
|
t.XTestFakeButtonEvent(d,1,0,0);x.XSync(d,0)
|
|
else:action=lambda:key('a')
|
|
values.append(sample(m,i,action));time.sleep(.12)
|
|
result[{1:'hover',2:'click_release',3:'masked_character'}[m]]=stats(values)
|
|
if m==3:result['synthetic_submit_ms']=sample(m,1100,lambda:key('Return'))
|
|
print(json.dumps(result))
|