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:

  • Screenshotsscreenshot [-o file] [--region X,Y,W,H] (PNG).
  • Input injectionpointer-move, click, pointer-button, axis, key, type, and touch-*.
  • Window introspectionlist-windows, and wait-window which blocks until a matching window maps (removing races from test scripts).
  • Window/layout controlfocus, move, resize, layout tiled|floating.
  • Outputset-output <w> <h> [--scale s] to resize/rescale the virtual screen at runtime.
  • GPU — start with --gpu to composite on real hardware and let clients do the same; see GPU-rendering clients.
  • Clipboardclipboard-get / clipboard-set for testing copy/paste.
  • Sessionmonitor 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.