From f0030f9a668849c3577f27ed80ed212a463cdb5e Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Sun, 9 Aug 2026 18:58:53 +0300 Subject: [PATCH 1/2] fix(qemu): give every VM a usb tablet for absolute pointing A window or VNC VM had only qemu's default relative PS/2 mouse. A desktop guest cannot align its cursor to the host cursor from relative deltas, so the pointer drifted and clicks in XFCE landed off target. usb-tablet reports absolute coordinates, so the guest cursor tracks the host cursor in the gtk window and in a VNC client. qemu-xhci gives it a USB bus; the default pc machine has none. --- internal/qemu/args.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/qemu/args.go b/internal/qemu/args.go index 489d008..550b48d 100644 --- a/internal/qemu/args.go +++ b/internal/qemu/args.go @@ -113,6 +113,14 @@ func Args(v *config.VM, graphical bool) []string { a = append(a, "-display", "none", "-vnc", "unix:"+v.VNCPath()) } + // Absolute pointer for the framebuffer above. qemu's default is a relative + // PS/2 mouse; a desktop guest cannot align its cursor to the host cursor + // from relative deltas, so the pointer drifts and clicks land off target. + // usb-tablet reports absolute coordinates, so the guest cursor tracks the + // host cursor in the gtk window and in a VNC client alike. qemu-xhci gives + // it a USB bus, which the default pc machine has none of. + a = append(a, "-device", "qemu-xhci,id=xhci", "-device", "usb-tablet") + // Two exports (core-api.md ยง10.2): the user's dir read-only, stoat's // per-VM scratch writable. // From 0e37b86f58802b26c6a3bcea31442c6b57ce2e82 Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Sun, 9 Aug 2026 19:05:19 +0300 Subject: [PATCH 2/2] fix(recipes): switch Alpine XFCE to udev so Xorg gets input Alpine boots mdev, which never feeds Xorg its input devices, so libinput found no mouse or keyboard and the desktop ignored every click. The tablet device existed but no driver bound it. setup-devd udev makes eudev the device manager and adds udev-trigger and udev-settle to sysinit. A cold-plug then hands the current devices to the live session that starts X now, before the next boot's trigger runs. Ubuntu, Debian, and Arch already run udev under systemd, so only the Alpine script needs this. --- internal/qemu/args_test.go | 5 +++++ internal/recipes/bundled/xfce/install-alpine.sh | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/internal/qemu/args_test.go b/internal/qemu/args_test.go index 9140d53..cd298cd 100644 --- a/internal/qemu/args_test.go +++ b/internal/qemu/args_test.go @@ -29,11 +29,16 @@ func TestArgsLive(t *testing.T) { "-virtfs local,path=/home/u/vms,mount_tag=host,security_model=mapped-xattr,readonly=on", "-cdrom /data/isos/alpine.iso", "-drive file=fat:rw:/data/live1/ovl,format=raw,if=virtio", + "-device qemu-xhci,id=xhci", "-device usb-tablet", } { if !strings.Contains(got, want) { t.Errorf("missing %q in:\n%s", want, got) } } + // The tablet must ride the VNC path too, or a VNC client's clicks miss. + if vnc := joined(Args(v, false)); !strings.Contains(vnc, "-device usb-tablet") { + t.Errorf("headless VM lacks usb-tablet, so VNC clicks would miss:\n%s", vnc) + } if strings.Contains(got, "disk.qcow2") { t.Error("live mode must not attach a disk") } diff --git a/internal/recipes/bundled/xfce/install-alpine.sh b/internal/recipes/bundled/xfce/install-alpine.sh index 2eef04c..abfd281 100644 --- a/internal/recipes/bundled/xfce/install-alpine.sh +++ b/internal/recipes/bundled/xfce/install-alpine.sh @@ -12,6 +12,19 @@ setup-apkrepos -c -1 setup-xorg-base apk add xfce4 xfce4-terminal dbus-x11 +# Xorg reads its input devices from udev. Alpine boots mdev by default, which +# never feeds Xorg, so libinput finds no mouse or keyboard and the desktop +# ignores every click. setup-devd switches the device manager to eudev and +# adds udev, udev-trigger, and udev-settle to sysinit for the next boot. +# Ubuntu, Debian, and Arch already run udev under systemd, so only Alpine +# needs this. +setup-devd udev +# Cold-plug the devices that already exist so Xorg finds the tablet in this +# boot, not only after a reboot. The live case below starts X now with a HUP +# to init, before udev-trigger would run at the next sysinit. +udevadm trigger 2>/dev/null || true +udevadm settle 2>/dev/null || true + rc-update add dbus rc-service dbus start || true