pub(crate) fn run_wayland_thread(cfg: WaylandThreadConfig)Expand description
The main execution loop of the Wayland backend.
This function is the central nervous system of the backend. It runs on its own thread and owns the entire lifecycle of the headless Wayland compositor:
- Initialization: builds the
calloopevent loop and the Wayland display, raises libwayland’s per-client buffer limit when the newer setter is available (resolved at runtime so the module still loads against older libwayland), and brings up the rendering pipeline — GBM/EGL hardware acceleration on the resolved DRM render node, falling back to software rendering (Pixman) when no node is usable. - State management: constructs and holds the
AppState— the Wayland globals (compositor, seat, SHM, shell, dmabuf, selections, and the rest) plus the output registry: the primary virtualHEADLESS-1output at layout (0, 0), extended at runtime by CreateOutput with additional outputs at their layout offsets, eachOutputNodeowning its damage tracker, render targets, and (at most one) capture pipeline. - Event dispatch:
- Command channel: control messages from the Python thread — per-display start/stop, output lifecycle (create/destroy/list/move-window), input injection routed across the output layout, keymap and clipboard operations, live rate / tunable changes, and the computer-use queries.
- Wayland socket: accepts client connections and drives the compositor protocol.
- StartCapture reconfigure (per display): reprograms that output’s mode / scale / refresh,
resizes its framebuffer and offscreen GBM buffer, and fullscreens the toplevels placed on
it. The encode device is resolved here: an operator’s explicit
encode_node_index(-1 software, >= 0 a device) always wins, and only the unset-2sentinel is filled: from the auto-picked render node, else the first one present. H.264 output masks the dimensions even, because 4:2:0 needs even width and height. - Encode-path choice + render loop: only a same-GPU GLES session encodes zero-copy on this calloop thread, because the dmabuf and its EGL context are calloop-affine; every readback flavor (the CPU path’s H.264 and JPEG, Pixman, or a cross-GPU hardware encoder) builds its encoders on that display’s dedicated encode thread instead. A shared timer (paced at the fastest active capture) renders each capturing output — its windows, popups, and layers made output-local, the cursor only on the pointer’s output — applies the shared paint-over / recovery-IDR policy per display, and delivers each display’s encoded stripes through its own frame callback. The zero-copy encode waits the GL render fence first, so a hardware encoder reading the dmabuf through CUDA/VA never maps a half-rasterized (torn) frame.
- Thread lifecycle: the Python frame callback runs on a dedicated delivery thread so its
GIL never stalls calloop input / control dispatch, and in readback mode the encoders run on
the
wl-encodethread. On a restart or stop the encode thread is torn down before the delivery thread — it feeds the delivery sender and must be gone first — and the retained callbacks are dropped and gated by a process-shutdown flag so nothing fires into a finalizing interpreter.