vdi-cloud/docs/evidence/native-cursor-20261105/focus.py

97 lines
3.4 KiB
Python

"""Run only with the controlled GTK acceptance window filling the stream."""
import ctypes as C
import json
import os
from pathlib import Path
import signal
import statistics
import struct
import subprocess
import time
os.environ.update(DISPLAY=':0', XAUTHORITY='/home/armbian/.Xauthority')
x = C.CDLL('libX11.so.6')
fix = C.CDLL('libXfixes.so.3')
test = C.CDLL('libXtst.so.6')
class Cursor(C.Structure):
_fields_ = [('x', C.c_short), ('y', C.c_short), ('w', C.c_ushort),
('h', C.c_ushort), ('hx', C.c_ushort), ('hy', C.c_ushort),
('serial', C.c_ulong), ('pixels', C.POINTER(C.c_ulong)),
('atom', C.c_ulong), ('name', C.c_char_p)]
x.XOpenDisplay.argtypes = [C.c_char_p]
x.XOpenDisplay.restype = C.c_void_p
x.XDefaultRootWindow.argtypes = [C.c_void_p]
x.XDefaultRootWindow.restype = C.c_ulong
x.XFree.argtypes = [C.c_void_p]
x.XSync.argtypes = [C.c_void_p, C.c_int]
x.XWarpPointer.argtypes = [C.c_void_p, C.c_ulong, C.c_ulong, C.c_int,
C.c_int, C.c_uint, C.c_uint, C.c_int, 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]
fix.XFixesGetCursorImage.argtypes = [C.c_void_p]
fix.XFixesGetCursorImage.restype = C.POINTER(Cursor)
test.XTestFakeButtonEvent.argtypes = [C.c_void_p, C.c_uint, C.c_int, C.c_ulong]
d = x.XOpenDisplay(None)
assert d
root = x.XDefaultRootWindow(d)
def native():
p = fix.XFixesGetCursorImage(d)
assert p
try:
c = p.contents
return dict(x=c.x, y=c.y, w=c.w, h=c.h, hx=c.hx, hy=c.hy,
pixels=[int(c.pixels[i]) & 0xffffffff for i in range(c.w*c.h)])
finally:
x.XFree(p)
def warp(px, py):
x.XWarpPointer(d, 0, root, 0, 0, 0, 0, px, py)
x.XSync(d, 0)
def decoded_sequence():
im = x.XGetImage(d, root, 40, 1370, 640, 1, 0xffffffff, 2)
assert im
try:
values = [x.XGetPixel(im, i*40+20, 0) & 0xffffff for i in range(16)]
if any(0x202020 <= v <= 0xe0e0e0 for v in values):
return -1
return sum((1 << i) for i, v in enumerate(values) if v > 0xe0e0e0)
finally:
x.XDestroyImage(im)
def flat_video_crop(px, py):
im = x.XGetImage(d, root, px-40, py-40, 100, 100, 0xffffffff, 2)
assert im
try:
pixels = [x.XGetPixel(im, a, b) & 0xffffff for b in range(100) for a in range(100)]
# No baked-in pointer: uniform acceptance-window background at this point.
ref = pixels[0]
bad = sum(any(abs(((v >> s) & 255) - ((ref >> s) & 255)) > 8
for s in (0, 8, 16)) for v in pixels)
return dict(reference=ref, nonuniform_pixels=bad)
finally:
x.XDestroyImage(im)
focus=C.c_ulong();revert=C.c_int()
x.XGetInputFocus.argtypes=[C.c_void_p,C.POINTER(C.c_ulong),C.POINTER(C.c_int)]
x.XSetInputFocus.argtypes=[C.c_void_p,C.c_ulong,C.c_int,C.c_ulong]
x.XGetInputFocus(d,C.byref(focus),C.byref(revert))
before=native()
try:
x.XSetInputFocus(d,0,1,0);x.XSync(d,0);time.sleep(.25);unfocused=native()
finally:
x.XSetInputFocus(d,focus.value,revert.value,0);x.XSync(d,0)
time.sleep(.3);after=native()
print(json.dumps(dict(original_focus=focus.value,unfocused_shape_changed=before['pixels']!=unfocused['pixels'],native_restored=before['pixels']==after['pixels'],position=[after['x'],after['y']])))