修复:完善终端开机及串流交接并接入云管平台
这个提交包含在:
1 父节点
8276da0d45
当前提交
18a9ce995d
25 个文件已更改
+922
-48
没有与搜索条件相匹配的文件
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Opt-in live X11 entry regression check. Follow with verify_return_surface.
|
||||
|
||||
DISPLAY=:0 python3 verify_entry_surface.py 0
|
||||
Starts a real stream from card 0/1/2 and uses Ctrl+Alt+Shift+Q to return.
|
||||
Requires verify_return_surface.py in the same directory.
|
||||
"""
|
||||
import ctypes as C
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from verify_return_surface import x, display, root, attrs, find, child_at, Attributes
|
||||
|
||||
|
||||
class ClassHint(C.Structure):
|
||||
_fields_ = [("name", C.c_void_p), ("class_name", C.c_void_p)]
|
||||
|
||||
|
||||
x.XQueryTree.argtypes = [C.c_void_p, C.c_ulong, C.POINTER(C.c_ulong),
|
||||
C.POINTER(C.c_ulong), C.POINTER(C.POINTER(C.c_ulong)),
|
||||
C.POINTER(C.c_uint)]
|
||||
x.XGetClassHint.argtypes = [C.c_void_p, C.c_ulong, C.POINTER(ClassHint)]
|
||||
x.XFree.argtypes = [C.c_void_p]
|
||||
# A client may destroy a window between QueryTree and GetWindowAttributes.
|
||||
# Ignore that expected BadWindow race; all other errors fail the check.
|
||||
errors = []
|
||||
|
||||
|
||||
class XError(C.Structure):
|
||||
_fields_ = [("type", C.c_int), ("display", C.c_void_p),
|
||||
("resource", C.c_ulong), ("serial", C.c_ulong),
|
||||
("error_code", C.c_ubyte), ("request_code", C.c_ubyte),
|
||||
("minor_code", C.c_ubyte)]
|
||||
|
||||
|
||||
@C.CFUNCTYPE(C.c_int, C.c_void_p, C.POINTER(XError))
|
||||
def on_error(_, error):
|
||||
if error.contents.error_code != 3: # BadWindow
|
||||
errors.append(error.contents.error_code)
|
||||
return 0
|
||||
|
||||
|
||||
x.XSetErrorHandler.argtypes = [C.c_void_p]
|
||||
x.XSetErrorHandler(on_error)
|
||||
|
||||
|
||||
def windows():
|
||||
returned_root, parent, count = C.c_ulong(), C.c_ulong(), C.c_uint()
|
||||
children = C.POINTER(C.c_ulong)()
|
||||
x.XQueryTree(display, root, C.byref(returned_root), C.byref(parent),
|
||||
C.byref(children), C.byref(count))
|
||||
ids = [children[i] for i in range(count.value)]
|
||||
if children:
|
||||
x.XFree(children)
|
||||
result = {}
|
||||
for window in ids:
|
||||
a = Attributes()
|
||||
if not x.XGetWindowAttributes(display, window, C.byref(a)) or a.map_state != 2:
|
||||
continue
|
||||
hint = ClassHint()
|
||||
cls = ""
|
||||
if x.XGetClassHint(display, window, C.byref(hint)):
|
||||
if hint.class_name:
|
||||
cls = C.string_at(hint.class_name).decode("utf-8", "replace")
|
||||
x.XFree(hint.class_name)
|
||||
if hint.name:
|
||||
x.XFree(hint.name)
|
||||
result[window] = (a, cls)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
index = int(sys.argv[1])
|
||||
if index not in (0, 1, 2):
|
||||
raise ValueError("Desktop index must be 0/1/2")
|
||||
geometry = attrs(root)
|
||||
width, height = geometry.width, geometry.height
|
||||
kiosk = find("--onlyvisible", "--name", "^VDI 云终端$")[0]
|
||||
if find("--onlyvisible", "--class", "com.moonlight_stream.Moonlight"):
|
||||
raise RuntimeError("Start this check from the idle desktop list")
|
||||
subprocess.run(["xdotool", "mousemove", str(width - 155), str(209 + 110 * index),
|
||||
"click", "1"], check=True)
|
||||
started = time.monotonic()
|
||||
samples, violations, states, first_geometry = 0, [], [], {}
|
||||
foreground_at = None
|
||||
points = [(20, 10), (width // 2, height // 2), (width - 20, height - 10)]
|
||||
ready = False
|
||||
while time.monotonic() - started < 35:
|
||||
elapsed = time.monotonic() - started
|
||||
mapped = windows()
|
||||
samples += 1
|
||||
tops = [child_at(px, py) for px, py in points]
|
||||
classes = {w: cls for w, (_, cls) in mapped.items()}
|
||||
for window, (a, cls) in mapped.items():
|
||||
if "moonlight" in cls.lower():
|
||||
geometry = [a.x, a.y, a.width, a.height]
|
||||
first_geometry.setdefault(window, {"t": round(elapsed, 4),
|
||||
"class": cls, "geometry": geometry})
|
||||
if cls != "com.moonlight_stream.Moonlight" or geometry != [0, 0, width, height]:
|
||||
violations.append({"t": elapsed, "window": window,
|
||||
"class": cls, "geometry": geometry})
|
||||
if kiosk not in mapped:
|
||||
violations.append({"t": elapsed, "reason": "kiosk backing window unmapped"})
|
||||
if not states or states[-1]["windows"] != tops:
|
||||
states.append({"t": round(elapsed, 4), "windows": tops, "classes": classes})
|
||||
if all(classes.get(w) == "com.moonlight_stream.Moonlight" for w in tops):
|
||||
if foreground_at is None:
|
||||
foreground_at = elapsed
|
||||
if elapsed - foreground_at >= 2:
|
||||
ready = True
|
||||
break
|
||||
else:
|
||||
foreground_at = None
|
||||
time.sleep(0.004)
|
||||
with open("/tmp/entry-live-%s.xwd" % index, "wb") as screenshot:
|
||||
subprocess.run(["xwd", "-root", "-silent"], stdout=screenshot, check=True)
|
||||
subprocess.run(["xdotool", "key", "--clearmodifiers", "ctrl+alt+shift+q"], check=True)
|
||||
time.sleep(1)
|
||||
returned = all(child_at(px, py) == kiosk for px, py in points)
|
||||
result = {"desktop_index": index, "entry_samples": samples,
|
||||
"stream_foreground": ready, "returned_to_kiosk": returned,
|
||||
"first_mapped_moonlight_geometry": first_geometry,
|
||||
"stack_changes": states, "violations": violations,
|
||||
"x11_errors": errors, "ok": ready and returned and not violations and not errors}
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
return 0 if result["ok"] else 1
|
||||
|
||||
|
||||
try:
|
||||
sys.exit(main())
|
||||
finally:
|
||||
x.XCloseDisplay(display)
|
||||
在新工单中引用
屏蔽一个用户