From 4cc4046716d484eda59967724c6f7e81485ed0f1 Mon Sep 17 00:00:00 2001 From: rickpeters Date: Tue, 29 Sep 2026 17:05:17 +0200 Subject: [PATCH] feat: vmview.py: a window with a headless VM's screen (QMP screendump every 2 s, view only) --- scripts/vmview.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 scripts/vmview.py diff --git a/scripts/vmview.py b/scripts/vmview.py new file mode 100644 index 0000000..2c0d835 --- /dev/null +++ b/scripts/vmview.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# vmview.py [seconds]: a window that shows a headless VM's screen, refreshed +# every 2 seconds (QMP screendump; read-only, no input). For watching an automated run without +# giving the VM a window. Needs python-pillow and tk; on WSL set the WSLg env (wsl-build-host skill). +import io, json, os, socket, sys, tempfile, tkinter as tk +from PIL import Image, ImageTk + +target = sys.argv[1] +sock_path = os.path.join(target, "qmp.sock") if os.path.isdir(target) else target +every = int(float(sys.argv[2]) * 1000) if len(sys.argv) > 2 else 2000 + +def shot(): + s = socket.socket(socket.AF_UNIX); s.settimeout(5); s.connect(sock_path); f = s.makefile() + f.readline(); s.sendall(b'{"execute":"qmp_capabilities"}'); f.readline() + out = os.path.join(tempfile.gettempdir(), f"vmview-{os.getpid()}.ppm") + s.sendall(json.dumps({"execute": "screendump", "arguments": {"filename": out}}).encode()) + reply = json.loads(f.readline()); s.close() + if "error" in reply: raise RuntimeError(reply["error"].get("desc")) + with open(out, "rb") as fh: img = Image.open(io.BytesIO(fh.read())) + os.unlink(out); return img + +root = tk.Tk(); root.title(f"vmview: {target}") +label = tk.Label(root, bg="black", fg="white"); label.pack(fill="both", expand=True) +def tick(): + try: + img = shot(); w = min(img.width, 1280); img = img.resize((w, img.height * w // img.width)) + label.photo = ImageTk.PhotoImage(img); label.config(image=label.photo, text="") + except Exception as e: + label.config(image="", text=f"no screen: {e}", width=60, height=10) + root.after(every, tick) +tick(); root.mainloop()