🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
#!/usr/bin/env python3
"""Narzędzie DEV: podglądanie okien X11 (geometria, nazwa, stan).
PO CO: kompozytor w trybie `winit` jest zwykłym oknem X11. Żeby automatycznie
przetestować renderowanie kursora albo interakcje, trzeba wiedzieć GDZIE to okno
jest na ekranie i przeliczyć współrzędne ekranu → współrzędne kompozytora.
`xwininfo`/`xdotool` bywają niedostępne, a ten skrypt korzysta tylko z libX11.
Użycie:
python3 tools/x11_probe.py list # wypisz widoczne okna + geometrię
python3 tools/x11_probe.py find 1280 800 # znajdź okno o dokładnym rozmiarze
Skrypt szuka wyłącznie okien potomnych korzenia (top-level), co w praktyce
wystarcza: okno `winit` jest właśnie takie.
"""
import ctypes
import ctypes.util
x11 = ctypes.CDLL(ctypes.util.find_library("X11"))
# WAŻNE: dla każdej funkcji ustawiamy `argtypes`/`restype`. Bez tego ctypes
# potraktuje wskaźnik Display i 64-bitowe id okna jak zwykłe inty i obetnie je
# do 32 bitów — dostaniemy segfault albo pustą listę okien.
x11.XOpenDisplay.restype = ctypes.c_void_p
x11.XOpenDisplay.argtypes = [ctypes.c_char_p]
x11.XDefaultRootWindow.restype = ctypes.c_ulong
x11.XDefaultRootWindow.argtypes = [ctypes.c_void_p]
x11.XQueryTree.argtypes = [
ctypes.c_void_p,
ctypes.c_ulong,
ctypes.POINTER(ctypes.c_ulong),
ctypes.POINTER(ctypes.c_ulong),
ctypes.POINTER(ctypes.POINTER(ctypes.c_ulong)),
ctypes.POINTER(ctypes.c_uint),
]
x11.XFetchName.argtypes = [ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(ctypes.c_char_p)]
x11.XFree.argtypes = [ctypes.c_void_p]
# XWindowAttributes — bierzemy tylko pola, których potrzebujemy. Układ musi
# odpowiadać nagłówkowi Xlib.h; dopełniamy buforem, bo interesuje nas początek
# struktury (x, y, width, height, border_width, depth, ...).
class Attr(ctypes.Structure):
_fields_ = [
("x", ctypes.c_int),
("y", ctypes.c_int),
("width", ctypes.c_int),
("height", ctypes.c_int),
("border_width", ctypes.c_int),
("depth", ctypes.c_int),
("visual", ctypes.c_void_p),
("root", ctypes.c_ulong),
("class_", ctypes.c_int),
("bit_gravity", ctypes.c_int),
("win_gravity", ctypes.c_int),
("backing_store", ctypes.c_int),
("backing_planes", ctypes.c_ulong),
("backing_pixel", ctypes.c_ulong),
("save_under", ctypes.c_int),
("colormap", ctypes.c_ulong),
("map_installed", ctypes.c_int),
("map_state", ctypes.c_int),
("all_event_masks", ctypes.c_long),
("your_event_mask", ctypes.c_long),
("do_not_propagate_mask", ctypes.c_long),
("override_redirect", ctypes.c_int),
("screen", ctypes.c_void_p),
]
_display = None
_root = None
def open_display():
global _display, _root
_display = x11.XOpenDisplay(None)
if not _display:
raise SystemExit("nie mogę otworzyć DISPLAY (ustaw DISPLAY=:N)")
_root = x11.XDefaultRootWindow(_display)
def children(win):
"""Zwraca listę okien potomnych w kolejności stosu (od spodu do wierzchu)."""
root_return = ctypes.c_ulong()
parent_return = ctypes.c_ulong()
arr = ctypes.POINTER(ctypes.c_ulong)()
count = ctypes.c_uint()
x11.XQueryTree(
_display,
ctypes.c_ulong(win),
ctypes.byref(root_return),
ctypes.byref(parent_return),
ctypes.byref(arr),
ctypes.byref(count),
)
result = [arr[i] for i in range(count.value)]
if arr:
x11.XFree(arr)
return result
def attributes(win):
x11.XGetWindowAttributes.argtypes = [ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(Attr)]
x11.XGetWindowAttributes.restype = ctypes.c_int
attr = Attr()
ok = x11.XGetWindowAttributes(_display, ctypes.c_ulong(win), ctypes.byref(attr))
return attr if ok else None
def name(win):
ptr = ctypes.c_char_p()
if x11.XFetchName(_display, ctypes.c_ulong(win), ctypes.byref(ptr)) and ptr.value:
title = ptr.value.decode("utf-8", "replace")
x11.XFree(ptr)
return title
return ""
def main(argv):
open_display()
cmd = argv[1] if len(argv) > 1 else "list"
if cmd == "list":
for win in children(_root):
attr = attributes(win)
if not attr or attr.map_state != 2: # 2 = IsViewable
continue
# Interesują nas okna o sensownym rozmiarze (pomijamy 1×1 pomocnicze).
if attr.width < 50 or attr.height < 50:
continue
print(
f"0x{win:x} {attr.width}x{attr.height}+{attr.x}+{attr.y} "
f"title={name(win)!r}"
)
elif cmd == "find":
want_w, want_h = int(argv[2]), int(argv[3])
def walk(win, ox, oy):
attr = attributes(win)
if not attr or attr.map_state != 2:
return None
ax, ay = ox + attr.x, oy + attr.y
if attr.width == want_w and attr.height == want_h:
return (ax, ay)
# Schodzimy w głąb — okno klienta bywa dzieckiem ramki menedżera okien.
for child in children(win):
found = walk(child, ax, ay)
if found:
return found
return None
result = walk(_root, 0, 0)
if result:
print(f"{result[0]} {result[1]}")
return
raise SystemExit(f"nie znaleziono widocznego okna {want_w}x{want_h}")
elif cmd == "tree":
def dump(win, ox, oy, depth):
attr = attributes(win)
if not attr or attr.map_state != 2:
return
ax, ay = ox + attr.x, oy + attr.y
print(
" " * depth
+ f"0x{win:x} {attr.width}x{attr.height}+{ax}+{ay} title={name(win)!r}"
)
for child in children(win):
dump(child, ax, ay, depth + 1)
for top in children(_root):
dump(top, 0, 0, 0)
elif cmd == "pointer":
# Bieżąca pozycja kursora X (bezwzględna). Przydaje się, by sprawdzić,
# czy wstrzyknięcie myszy przez XTest faktycznie zadziałało.
root_ret = ctypes.c_ulong()
child_ret = ctypes.c_ulong()
rx = ctypes.c_int()
ry = ctypes.c_int()
wx = ctypes.c_int()
wy = ctypes.c_int()
mask = ctypes.c_uint()
x11.XQueryPointer.argtypes = [
ctypes.c_void_p,
ctypes.c_ulong,
ctypes.POINTER(ctypes.c_ulong),
ctypes.POINTER(ctypes.c_ulong),
ctypes.POINTER(ctypes.c_int),
ctypes.POINTER(ctypes.c_int),
ctypes.POINTER(ctypes.c_int),
ctypes.POINTER(ctypes.c_int),
ctypes.POINTER(ctypes.c_uint),
]
x11.XQueryPointer(
_display,
ctypes.c_ulong(_root),
ctypes.byref(root_ret),
ctypes.byref(child_ret),
ctypes.byref(rx),
ctypes.byref(ry),
ctypes.byref(wx),
ctypes.byref(wy),
ctypes.byref(mask),
)
print(f"{rx.value} {ry.value} child=0x{child_ret.value:x}")
else:
raise SystemExit(__doc__)
if __name__ == "__main__":
import sys
main(sys.argv)