Expand description
Host-capture mode: capture/inject as a client of an external compositor.
Host-capture mode: pixelflux as a CLIENT of an external Wayland compositor
(e.g. labwc running WLR_BACKENDS=headless), inverting the nested topology.
The host compositor owns the session — one seat, one selection, one screen
model — and pixelflux captures and injects as a privileged-protocol client:
frames via ext_image_copy_capture_v1 where the host offers it (wlroots
0.19+, KWin 6.2+, cosmic) and zwlr_screencopy_v1 (v3 copy_with_damage)
everywhere else — both damage-gated, so idle screens cost nothing
(PIXELFLUX_HOST_CAPTURE=zwlr forces the fallback); input over libei where
the backend grants an EIS socket, else a persistent zwp_virtual_keyboard_v1
device carrying selkies’ own keymap text with zwlr_virtual_pointer_v1, else
kernel uinput devices where /dev/uinput is writable. Zero-copy is preserved by
allocating the capture buffers from pixelflux’s OWN GBM device (render node —
no privileges): the compositor blits straight into the dmabufs the encoder
imports, so no CPU ever touches a frame. A host that cannot import our
dmabufs (other GPU, software renderer) and any run without a GPU degrade to
wl_shm buffers feeding the CPU encode pool.
Every host wl_output maps to one selkies display id, in registry order: a
control thread on the primary connection owns wlr-output-management and
applies the whole layout (per-output custom modes plus row positions) as one
atomic configuration, while each output gets its own capture connection and
thread. Input proxies are called from the calloop thread directly
(wayland-client proxies are thread-safe); pointer coordinates arrive in the
same union-layout space selkies uses, so the virtual-pointer extent is the
layout’s bounding box. All capture-side waits poll a wake pipe next to the
Wayland socket, so a resize or teardown lands immediately even while the
thread is parked in a copy_with_damage wait on a static screen.
A host without those protocols (GNOME, KDE before ext-image-copy-capture) is captured
through xdg-desktop-portal instead: one RemoteDesktop session hands out a PipeWire stream per
monitor and takes the input the seat lacks a virtual device for (portal, pwcapture).
Each rung is chosen per capability, ordered by measured delivery cost: a socket write to the
compositor (libei, then the virtual-input protocols) runs about 4 us, a uinput write reaching
evdev about 12 us, and a portal Notify* D-Bus call about 2 ms, so the kernel serves as the
universal fallback rather than the first choice, and the portal comes last. A uinput pointer
declares a normalized absolute axis, since the layout extent changes while a device’s range
cannot. Frames take the Wayland protocol where the host offers it and the portal where it does
not — so a
KWin that serves ext-image-copy-capture but no virtual pointer still gets its pointer from
the portal. Portal keys go by keysym, resolved from selkies’ own keymap, so the host applies
its layout to the symbol rather than to a keycode from another layout. The compositor owns
the portal buffers: a dmabuf frame is imported where it lies and a memfd frame is read in
place, and the portal stream is asked for at most the capture’s rate so no frame is
produced only to be dropped.
Structs§
- Host
CpuFrame - Borrow-by-Arc view of a software frame still sitting in its shm mapping.
- Host
Frame - A frame the compositor finished blitting, ready for the encoder.
- Host
Session - The calloop-side session: input proxies plus one capture handle per output.
Constants§
- LAYOUT_
DEADLINE - Bound for one layout application on the control thread: a host that has not answered by then is settled without it, and the session follows whatever mode the host announces.
Functions§
- wait_
gpu_ done - Wait, bounded, until the GPU work the compositor queued on a host dmabuf has finished: the CUDA import reads the buffer without honoring its implicit fences, so a frame encoded the instant it was announced could otherwise carry a half-blitted image. Drivers that attach no fence return at once.