atuq: en la imagen el navegador SÍ pinta — la segunda pantalla no era

La sospecha salía del propio serial: el arranque del §6.10.ter veía DOS dispositivos DRM
(`drm: card0 card1 renderD128`) y uno nuevo ve uno solo. Sin `-vga none`, QEMU agrega una VGA
estándar además del virtio-gpu, OVMF pinta su GOP ahí y el kernel levanta simpledrm encima: un
compositor con dos tarjetas puede componer en la que el `screendump` no muestra, y eso se ve igual
que «la ventana no aparece». Por eso el guion lleva `VGA=1`, para pedir ese caso por su nombre.

Medido con las dos configuraciones y el mismo lanzamiento:

    -vga none  → card0        → 703766 px magenta
    VGA=1      → card0 card1  → 703766 px magenta

El mismo número al píxel. La ventana aparece —panel, dock y el navegador con su barra lateral—,
`nsWindow::Create() Toplevel` está en el MOZ_LOG, la superficie se mapea y `mIsFullyOccluded 0`, con
`WaylandBufferSHM` de 1280x696, el mismo camino de buffers que bajo sway.

⇒ la segunda pantalla no es la causa. Queda una sola diferencia con aquella corrida: cómo se lanzó
el navegador. Acá va con perfil nuevo y MOZ_ENABLE_WAYLAND puesto; allá fue un `atuq` pelado con su
perfil por defecto tecleado en el serial. Para eso está `--como-usuario`, que es la corrida que sigue.

⚠ Dos trampas del arnés, medidas y escritas en el guion: el terminal hace ECO de lo que se le
escribe, así que una marca de fin literal se lee en el eco y las órdenes se pisan; y `cmd & ; echo`
es error de sintaxis en ash ⇒ el navegador no se lanzó y la corrida siguió dando capturas de un
escritorio vacío, que se leen igual que el fallo que se investiga.
This commit is contained in:
Sergio
2026-09-14 14:35:13 +00:00
parent 9d6c79a749
commit 589efee4f8
2 changed files with 99 additions and 13 deletions
+57 -13
View File
@@ -25,6 +25,10 @@ causa.
"""
import argparse, json, os, shutil, socket, subprocess, sys, time
# Sin esto la salida sale por tandas cuando se redirige a un fichero, y una corrida de 20 minutos se
# lee como si estuviera colgada.
sys.stdout.reconfigure(line_buffering=True)
RAIZ = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
IMG = os.environ.get("IMG", "/mnt/cosecha/takana-cosmic-qemu.img")
OVMF_CODE = os.environ.get("OVMF_CODE", "/usr/share/edk2/x64/OVMF_CODE.4m.fd")
@@ -32,6 +36,18 @@ OVMF_VARS = os.environ.get("OVMF_VARS", "/usr/share/edk2/x64/OVMF_VARS.4m.fd")
MEM = os.environ.get("MEM", "2560")
SMP = os.environ.get("SMP", "2")
# ⚠ `VGA=1` reproduce el arranque del §6.10.ter, y la diferencia NO es cosmética: sin `-vga none`,
# QEMU agrega una VGA estándar ADEMÁS del virtio-gpu, OVMF pinta su GOP ahí y el kernel levanta
# `simpledrm` encima ⇒ el guest ve DOS dispositivos DRM. En el serial se lee tal cual:
#
# == cosmic :: kernel 6.16.12 · drm: card0 card1 renderD128 (corrida del §6.10.ter)
# == cosmic :: kernel 6.16.12 · drm: card0 renderD128 (con -vga none)
#
# Un compositor con dos tarjetas puede componer en la que el `screendump` NO muestra. Por eso el
# default acá es UNA sola (-vga none) y la reproducción del caso original es una opción explícita:
# son dos mediciones distintas y hay que poder pedir cada una por su nombre.
VGA_ARGS = [] if os.environ.get("VGA") == "1" else ["-vga", "none"]
class Serial:
"""El serial de la VM. Todo lo que entra queda en el .log crudo, pase lo que pase."""
@@ -76,12 +92,27 @@ class Serial:
print(f" ⚠ nunca llegó «{marca}» {nombre} en {segundos}s", flush=True)
return self.buf[vistos:].decode("utf-8", "replace")
def cmd(self, orden, segundos=30):
"""Manda una orden y devuelve su salida (delimitada por una marca propia)."""
marca = f"__FIN{int(time.time()*1000) % 100000}__"
self.s.sendall(f"{orden}; echo {marca}\n".encode())
sal = self.esperar(marca, segundos, nombre=f"de «{orden[:40]}»")
return sal
def cmd(self, orden, segundos=30, fondo=False):
"""Manda una orden y devuelve su salida (delimitada por una marca propia).
⚠ DOS TRAMPAS MEDIDAS, las dos hacen que la orden NO CORRA y el guion siga como si nada:
· **el terminal hace ECO de lo que se le escribe**, así que una marca escrita literal en la
línea aparece en el serial ANTES de que el shell ejecute nada: el `esperar` la ve en el eco
y vuelve al instante, y las órdenes se pisan unas a otras. Por eso la marca se ARMA EN EL
GUEST partida en dos (`'FIN''-123'`): el eco muestra las comillas y sólo la salida trae la
marca entera.
· **`cmd & ; echo …` es un error de sintaxis** en ash: pegarle un `;` a un `&` no lanza nada
y el shell contesta «syntax error». Lo de fondo va en su propia línea (`fondo=True`).
"""
n = int(time.time() * 1000) % 100000
marca = f"FIN-{n}"
if fondo:
self.s.sendall(f"{orden}\n".encode()); time.sleep(0.5)
self.s.sendall(f"echo 'FIN''-{n}'\n".encode())
else:
self.s.sendall(f"{orden}; echo 'FIN''-{n}'\n".encode())
return self.esperar(marca, segundos, nombre=f"de «{orden[:40]}»")
def qmp(ruta, ordenes):
@@ -99,6 +130,8 @@ def qmp(ruta, ordenes):
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--solo-cosmic", action="store_true", help="control: no lanza el navegador")
ap.add_argument("--como-usuario", action="store_true",
help="lanza `atuq` a secas —perfil por defecto, sin MOZ_ENABLE_WAYLAND— como se hizo a mano en el §6.10.ter")
ap.add_argument("--espera", type=int, default=180, help="segundos para que el navegador pinte")
ap.add_argument("--out", default=os.path.join(RAIZ, "work/atuq-imagen"))
a = ap.parse_args()
@@ -114,7 +147,7 @@ def main():
qemu = [
"nice", "-n", "10", "qemu-system-x86_64",
"-machine", "q35", "-accel", "tcg", "-cpu", "max", "-m", MEM, "-smp", SMP,
"-vga", "none", "-device", "virtio-gpu-pci", "-display", "none", "-nic", "none",
*VGA_ARGS, "-device", "virtio-gpu-pci", "-display", "none", "-nic", "none",
"-drive", f"if=pflash,format=raw,unit=0,file={OVMF_CODE},readonly=on",
"-drive", f"if=pflash,format=raw,unit=1,file={vars_fd}",
"-drive", f"file={IMG},format=raw,if=virtio",
@@ -156,12 +189,23 @@ def main():
"<h1 style=\"font:40px monospace\">ATUQ PINTA</h1>' > /tmp/pagina.html")
ser.cmd("printf '%s\\n' 'user_pref(\"browser.dom.window.dump.enabled\", true);' > /tmp/perfil/user.js")
ser.cmd("printf '%s\\n' 'user_pref(\"browser.shell.checkDefaultBrowser\", false);' >> /tmp/perfil/user.js")
lanz = ("MOZ_ENABLE_WAYLAND=1 GDK_BACKEND=wayland "
"MOZ_LOG='Widget:5,WidgetWayland:5,WaylandBackend:5,timestamp' "
"MOZ_LOG_FILE=/var/log/cosmic/moz "
"/usr/bin/atuq --no-remote --profile /tmp/perfil file:///tmp/pagina.html "
"> /var/log/cosmic/atuq.log 2>&1 &")
print(ser.cmd(lanz).strip())
# ⚠ LOS DOS LANZAMIENTOS NO SON EL MISMO EXPERIMENTO. El de por defecto fija el
# entorno (perfil nuevo, `MOZ_ENABLE_WAYLAND`) para que la medición no dependa de lo que
# haya quedado en el estado del usuario; `--como-usuario` reproduce lo que se tecleó a
# mano en el §6.10.ter —`atuq` pelado, con SU perfil y sin variables— que es lo único que
# sigue distinto entre aquella corrida y ésta. El `MOZ_LOG` va en los dos: no cambia el
# comportamiento, sólo deja rastro.
if a.como_usuario:
lanz = ("MOZ_LOG='Widget:5,WidgetWayland:5,WaylandBackend:5,timestamp' "
"MOZ_LOG_FILE=/var/log/cosmic/moz "
"/usr/bin/atuq file:///tmp/pagina.html > /var/log/cosmic/atuq.log 2>&1 &")
else:
lanz = ("MOZ_ENABLE_WAYLAND=1 GDK_BACKEND=wayland "
"MOZ_LOG='Widget:5,WidgetWayland:5,WaylandBackend:5,timestamp' "
"MOZ_LOG_FILE=/var/log/cosmic/moz "
"/usr/bin/atuq --no-remote --profile /tmp/perfil file:///tmp/pagina.html "
"> /var/log/cosmic/atuq.log 2>&1 &")
print(ser.cmd(lanz, fondo=True).strip())
print(f"== navegador lanzado; observando {a.espera}s", flush=True)
t0 = time.time(); n = 0
while time.time() - t0 < a.espera: