quibble
Headless Wayland compositor for GUI testing
- Clone
git clone https://git.highenergymagic.org/quibble.git- Files
- browse the default branch
- Default branch
- master
- Last commit
- 2026-08-29
README.md
quibble
quibble is a semi-headless Wayland compositor built for automated testing of graphical programs. It hosts Wayland clients without needing a GPU or a display: by default it composites entirely in software into an in-memory framebuffer, so it runs unchanged in CI and other non-graphical environments. It opens no window of its own and processes no interactive input — a test run drives everything remotely.
The compositor exposes a control socket. A companion CLI, quibblectl
(or your own client speaking the same protocol), connects to it to take
screenshots, inject pointer/keyboard/touch input, list and arrange windows,
block until a window appears, read and write the clipboard, and resize the
virtual output. Compositing defaults to pure software (pixman), which also
makes screenshots deterministic across machines; --gpu switches it to OpenGL
ES on a real card for clients that need hardware acceleration.
“Semi-headless” means the graphical extras are opt-in and degrade gracefully. When a display server is available, quibble can open a monitor window that mirrors the framebuffer so a human can watch a test in progress. When a system tray is available, it shows a tray icon whose menu toggles that window and can quit the compositor (a left-click on the icon toggles the monitor too). If neither is present, quibble just runs headless and says so.
Those two are independent: a session bus is enough for the icon, so it can appear where no display server will have it. When a click finds the monitor window impossible, the icon switches to a warning and puts the reason on hover rather than doing nothing.
Both features need quibble itself to be able to reach the surrounding session,
so a launcher should leave WAYLAND_DISPLAY and DISPLAY alone rather than
clearing them to keep the client in. Confining the client is quibble’s job: a
client it launches gets WAYLAND_DISPLAY pointed at quibble’s socket, with
WAYLAND_SOCKET and DISPLAY removed so nothing can reach past it to the real
desktop.
To cover the two dominant compositor families, quibble supports two layout policies, switchable at runtime: tiled (one near-fullscreen window at a time, with popups floating on top) and floating (freely overlapping windows that can be moved and resized individually).
Quick start
# Build both binaries.
cargo build --release
# Start the compositor. It prints the two things clients and tools need:
# WAYLAND_DISPLAY=wayland-2
# QUIBBLE_SOCKET=/run/user/1000/quibble-wayland-2.sock
./target/release/quibble &
# Point a client at it (in another shell, or let quibble launch one for you
# with `quibble -- weston-terminal`).
WAYLAND_DISPLAY=wayland-2 weston-terminal &
# Drive the session with quibblectl. It finds the socket via $QUIBBLE_SOCKET
# or $WAYLAND_DISPLAY, or takes an explicit --socket.
export QUIBBLE_SOCKET=/run/user/1000/quibble-wayland-2.sock
quibblectl wait-window --app-id org.freedesktop.weston.terminal
quibblectl list-windows
quibblectl click 640 400
quibblectl type 'echo hello'
quibblectl screenshot -o shot.png
Remote-control capabilities
quibblectl mirrors the wire protocol one subcommand per request. Highlights:
- Screenshots —
screenshot [-o file] [--region X,Y,W,H](PNG). - Input injection —
pointer-move,click,pointer-button,axis,key,type, andtouch-*. - Window introspection —
list-windows, andwait-windowwhich blocks until a matching window maps (removing races from test scripts). - Window/layout control —
focus,move,resize,layout tiled|floating. - Output —
set-output <w> <h> [--scale s]to resize/rescale the virtual screen at runtime. - GPU — start with
--gputo composite on real hardware and let clients do the same; see GPU-rendering clients. - Clipboard —
clipboard-get/clipboard-setfor testing copy/paste. - Session —
monitor on|off,ping,quit.
Run quibble --help and quibblectl --help for the full reference.
Client compatibility
Beyond the core (xdg-shell, wl_shm, wl_seat, wl_output), quibble advertises the
protocols real toolkits expect, so hosted apps render and behave faithfully:
xdg-decoration (forced server-side, so windows are borderless and screenshots
are chrome-free), viewporter, fractional-scale, primary selection, clipboard
data-control (so tools like wl-clipboard work), presentation-time,
cursor-shape, xdg-activation, relative-pointer, pointer-constraints (pointer
locks are honored during input injection), and wlr-layer-shell (backgrounds,
panels and overlays are composited in the correct order). With --gpu, it also
advertises linux-dmabuf and linux-drm-syncobj (see below). XWayland is not
supported yet.
GPU-rendering clients (OpenGL, Vulkan, wgpu, egui)
quibble has two compositing backends, chosen at startup.
Software (the default). Compositing runs on the CPU with pixman and only
shared-memory buffers are accepted, so a client that wants the GPU has to fall
back to a software rasterizer: install Mesa’s software drivers (the
vulkan-swrast package, i.e. lavapipe) and select one if the client does
not do so itself.
LIBGL_ALWAYS_SOFTWARE=1 ./my-opengl-app
VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json ./my-egui-app
Hardware (--gpu). Compositing moves to OpenGL ES on a real GPU, and
quibble advertises linux-dmabuf and linux-drm-syncobj-v1. Clients render on
the card and hand over dmabufs, which are imported as EGLImages. Screenshots
still come back as ordinary PNGs — the composited framebuffer is read back from
the GPU — so nothing about the control protocol changes.
# Composite on the first render node in /dev/dri...
quibble --gpu
# ...or on a specific GPU.
quibble --render-node /dev/dri/renderD129
It is off by default on purpose. GPU-rendered content is not bit-identical across machines, and reproducible screenshots are the reason the software path exists. Turn it on for the tests that need acceleration, not globally. If a GPU is asked for and cannot be provided, quibble exits rather than falling back to software behind your back.
Compositing on the GPU is what makes the hardware path trustworthy, not merely
faster. The software renderer can import dmabufs too, but only with a linear
modifier, which forces clients down an untiled path — and on the proprietary
NVIDIA driver that path is broken: its Vulkan swapchain advertises
DRM_FORMAT_MOD_LINEAR with a tight stride while filling the buffer with
block-linear (GOB-swizzled) data, so windows composite as horizontal banding.
Reproduced on an RTX 4080 with vkcube, vkcubepp, vkgears and a wgpu
application. Importing as an EGLImage instead lets clients keep their native
tiled modifiers and leaves the driver to interpret its own layout, so the
question never arises. --gpu therefore selects the GL renderer rather than
bolting dmabuf onto the software one, and
a_vulkan_client_composites_coherently in the integration tests guards against
the regression.
Layout of the workspace
quibble-proto/— the JSON control protocol shared by both binaries.quibble/— the compositor daemon.quibblectl/— the command-line remote control.
Building and testing
cargo build # both binaries
cargo test # integration tests drive the real binary over the socket
The integration tests launch the compositor and exercise the protocol
end-to-end. Several drive real Wayland clients (weston-simple-shm, swaybg,
weston-simple-egl, vkcube) and skip when those are not installed; the GPU
tests additionally skip without a usable DRM render node, so the suite stays
green on a headless CI machine.
The pixman renderer needs the system pixman library available at build time.
The GL renderer needs no build-time library — libEGL is loaded with dlopen —
so a machine with no GPU drivers still builds everything and simply runs the
software path.