2
0

SKILL.md 6.1 KB


name: mjdev-qemu-test

description: Boot and test the mjdev wayland desktop (the make-iso ISO or the installed deb) in QEMU headlessly — capture screenshots + session logs, diagnose why the session does not start, iterate live WITHOUT rebuilding the ISO, and reflect verified fixes back into the project (make-iso.sh / session / deb packaging). Use when asked to run/test the desktop ISO in QEMU, screenshot the booted desktop, or debug "black screen / session won't start / desktop not running" on the live image or after installDesktop.

mjdev desktop — QEMU test & debug loop

Goal: the deb and the ISO must make the mjdev wayland desktop usable on any machine. Iterate without rebuilding the ISO each time (a full makeIso is ~10 min). Only rebuild for a final check. Reflect every working change back into the project.

Architecture (what runs)

  • mjdevc = the wayland compositor (Kotlin/Native, links libwlroots-0.18 + the wayland stack).
  • mjdev-session (session/mjdev-session) = the session launcher: sets env, exec mjdevc --session --shell-cmd /opt/mjdev-desktop/bin/mjdev-desktop, logs to ~/.cache/mjdev-desktop/session.log.
  • shell = the Compose Desktop app (/opt/mjdev-desktop). It is AWT-based → needs an X display, which mjdevc provides via its built-in XWayland (shim.c:1351, setenv DISPLAY at 1368-1374). This is not xorg; "pure wayland" still needs XWayland for this one legacy app.
  • On tty1 the ISO autologins mjdev → mjdev-session. If it crashes, agetty relogs → crash-loop → black screen.

Known root causes (check these first)

  1. libwlroots-0.18.so: cannot open shared object file — the runtime wayland stack mjdevc links is not installed. The jpackage deb does NOT pull it. FIX: install/depend on libwlroots-0.18 xwayland libegl1 libgles2 libgbm1 libinput10 libseat1 libxkbcommon0 (libwlroots-0.18 pulls libdrm/libwayland/libdisplay-info/libliftoff/libpixman/... as its deps).
  2. Software rendering detected ... WLR_RENDERER_ALLOW_SOFTWARE — in a VM (no GPU) mesa is llvmpipe and wlroots refuses software GL. FIX: export WLR_RENDERER_ALLOW_SOFTWARE=1 in session/mjdev-session (already there; ignored on real GPUs). Needs libgl1-mesa-dri.
  3. java.awt.HeadlessException: No X11 DISPLAY — only when the shell is run with no compositor (e.g. a bare serial shell). Under mjdevc with XWayland+DISPLAY it is fine. Not a real bug by itself.

Map mjdevc's deps to packages on the (Debian trixie) host: ldd compositor/build/session-install/mjdevc | awk '/=>/{print $3}' | while read f; do dpkg -S "$(readlink -f "$f")"; done

Always log the failure

mjdev-session redirects mjdevc+shell to ~/.cache/mjdev-desktop/session.log (and .log.1 = previous crash). Without that the tty1 session dies to a black screen with no trace. Keep this.

Fast capture (screenshot + input) — qmp_drive.py

Boot QEMU headless with a framebuffer + QMP + a USB tablet, then drive it:

ISO=releases/mjdev-desktop-1.0.3.iso
# direct-kernel boot is faster than -boot d; extract once:
xorriso -osirrox on -indev "$ISO" -extract /live/vmlinuz /tmp/iso-test/vmlinuz -extract /live/initrd.img /tmp/iso-test/initrd.img
qemu-system-x86_64 -enable-kvm -cpu host -m 4096 -smp 4 \
  -kernel /tmp/iso-test/vmlinuz -initrd /tmp/iso-test/initrd.img -append "boot=live console=ttyS0,115200" \
  -cdrom "$ISO" -device virtio-vga -usb -device usb-tablet \
  -qmp unix:/tmp/iso-test/qmp.sock,server,nowait -serial file:/tmp/iso-test/vm-serial.log -display none &
python3 .claude/skills/mjdev-qemu-test/qmp_drive.py /tmp/iso-test/qmp.sock   # writes /tmp/iso-test/*.png

qmp_drive.py screendumps boot frames, then moves the pointer to the bottom edge (reveals the bottom bar) and the right edge (reveals the control center) and dumps each — then quits the VM. PPM→PNG is built in (no deps). Read the PNGs with the Read tool. var=10 ≈ black; higher ≈ content.

Grab the session log over serial (when screenshot is black)

The serial getty prompt timing is fragile — wait ~80s, send a wake newline, then login + cat:

{ sleep 80; printf '\n\nmjdev\n'; sleep 5; \
  printf 'cat ~/.cache/mjdev-desktop/session.log.1; echo ---; cat ~/.cache/mjdev-desktop/session.log\n'; sleep 8; } \
| timeout 130 qemu-system-x86_64 -enable-kvm -cpu host -m 4096 -smp 4 \
  -kernel /tmp/iso-test/vmlinuz -initrd /tmp/iso-test/initrd.img -append "boot=live console=ttyS0,115200" \
  -cdrom "$ISO" -nographic -serial mon:stdio 2>&1 | tr -d '\r' | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g'

Iterate WITHOUT rebuilding the ISO

Once an ISO with the runtime deps exists, debug session/compositor changes live:

  • Share the project into the guest: add -virtfs local,path=$PWD,mount_tag=host,security_model=none,readonly=on and in the guest mkdir -p /mnt/host && mount -t 9p -o trans=virtio host /mnt/host.
  • mjdev is in group video → it can get a seatd seat and run the compositor without root. Copy a freshly-built mjdevc/mjdev-session from /mnt/host/... into ~/bin, point MJDEVC at it, run it, capture via QMP. No rebuild.
  • New apt deps need root: a debug ISO can passwd -d root in provisioning so serial root login works.
  • Only apt/dep changes or kernel/initrd changes truly need a rebuild — almost never.

Reflect fixes back into the project (do this every time something works)

  • runtime deps → make-iso.sh provision (apt-get install ...) and the deb Depends (deb packaging) so a clean console install pulls them via apt.
  • session/env fixes → session/mjdev-session.
  • compositor fixes → compositor/native/shim.c / compositor/src/... (do NOT touch the desktop app code).

House rules (from the user)

  • Don't touch the existing desktop app code — only compositor, session, deb packaging are fair game.
  • Pure wayland (no xorg); XWayland-shim for the AWT shell is acceptable.
  • Builds are heavy (mksquashfs xz pegs all cores) — if the user is in a meeting / wants quiet, kill qemu-system, mksquashfs (root, via pkexec), and the gradle daemon.
  • makeIso escalates via pkexec; the script tees to /tmp/mjdev-iso-build.log (pkexec hides stdio).