Skip to main content

Module host

Module host 

Source
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.

Macros§

impl_output_name 🔒

Structs§

CaptureState 🔒
CtrlState 🔒
FrameSink 🔒
The capture thread’s end of one output’s frame queue: the frames, an exact count of those queued and not yet taken (so a wake that the timer has already served is recognized as such), and the calloop wake that follows every frame.
HostCpuFrame
Borrow-by-Arc view of a software frame still sitting in its shm mapping.
HostFrame
A frame the compositor finished blitting, ready for the encoder.
HostKeyboardState 🔒
HostSession
The calloop-side session: input proxies plus one capture handle per output.
LayoutLedger 🔒
Outcome ledger for layout requests: every Apply carries an epoch, and the control thread records whether the newest one it processed was realized by the host. A request is answered once decided has caught up to its epoch; a newer request subsumes an older one (the layout map is cumulative), so the newest verdict stands for every epoch at or below it. Polled, never waited on: the session side reads it from its own loop.
LayoutSlot 🔒
One display’s slice of the union layout, as selkies configured it. Slots are keyed by selkies display id (‘display2’ is id 2 — ids are sparse); active ids map onto host outputs by ascending rank.
OutputGeom 🔒
One host output’s placement, for pairing portal streams with outputs.
OutputHandle 🔒
One host output’s calloop-side handle: control channel, wake pipe, frames.
PortalCtl 🔒
Session-wide portal state: the session thread opens (and, when the cursor mode changes, reopens) the portal session; the outputs and the input methods read the live one.
PortalInner 🔒
PortalLive 🔒
A live portal session: the D-Bus session, and the PipeWire connection its streams live on when frames come through it.
PortalPoint 🔒
Where a union-layout point lands on the portal stream under it (HostSession::portal_target).
SerializedMods 🔒
Want 🔒
What a capture thread is currently aiming at: the encoder’s dimensions, the buffer type its consumer can take, and whether the host paints the cursor in. A size or buffer-type change forces a slot renegotiation; a cursor change reopens the ext session, whose option is fixed at creation.

Enums§

Ctl 🔒
What a control-channel drain decided while a capture wait was in progress.
CtrlMsg 🔒
ExtOutcome 🔒
How the ext capture loop ended: Finished is a normal teardown (the thread is done); Unavailable means it could not get going, so wlr-screencopy should run.
Pump 🔒
SlotBuffer 🔒
ToHost 🔒

Constants§

DETACHED 🔒
The generation of a retained frame the session copied out of its buffer (HostSession::detach_retained): no capture’s, so releasing it reaches no slot.
KEYMAP_FORMAT_XKB_V1 🔒
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.
SCROLL_PX_PER_NOTCH 🔒
Pixels of a finger’s scroll per wheel notch where only notches exist (the uinput wheels): the size Selkies’ page takes for a touchpad’s notch.
SLOTS 🔒
Frames in flight between a capture thread and the encode tick. Two slots: one being blitted by the compositor while the previous one is encoded.

Functions§

alloc_cpu_slot 🔒
An shm-backed capture slot in a memfd pool the CPU consumer reads in place.
alloc_gpu_slot 🔒
A dmabuf-backed capture slot: a GBM buffer object from pixelflux’s render node, exported and wrapped in a host wl_buffer. modifiers narrows the allocation to what the host advertised; empty leaves the driver’s choice, which the host learns through the params anyway. Ok(None) means the host refused the import (wrong device, unsupported modifier): the caller degrades to shm slots instead of dying to the protocol error create_immed would raise.
apply_layout 🔒
Ask the host (wlr-output-management) to give every active output its wanted mode and layout position in one atomic configuration. Retries across a canceled (stale serial). Returns whether the host said it applied the layout: false for a refusal, no answer by LAYOUT_DEADLINE, or no manager at all (KWin offers only its own kde_output_management protocol). Either answer settles the request, and the session owner then re-sizes its capture to the mode the host announces, so capture follows the host instead of gating forever on a size it will never produce.
assign_streams 🔒
Which portal stream backs each output rank: the stream announcing the output’s position, else the one announcing its logical size when that is unambiguous, else the stream at the same index (a backend that sends no geometry lists monitors in its output order).
capture_loop 🔒
capture_loop_ext 🔒
ext-image-copy-capture capture loop: one persistent session per output. The compositor holds each capture until the content actually changes, so a static screen costs nothing — the property copy_with_damage provides on the wlr path. Frames land in the same slot machinery and HostFrame shape as that path.
control_loop 🔒
convert_shm_row 🔒
Convert one source row to a BGRA destination row per (src_bpp, swap_rb) from shm_src_layout: a 4-byte source either copies straight or swaps R/B keeping alpha, a 3-byte source fills opaque alpha and swaps R/B only when the layout calls for it.
drain_ctl 🔒
Drain the control channel from inside a capture wait: releases are applied (generation-checked), and the newest Start/Idle decides the wait’s fate.
fourcc_to_gbm 🔒
fps_milli 🔒
A capture rate as the millihertz a portal stream is asked to hold.
nearest_slot 🔒
The active slot holding (x, y), else the one nearest to it, by rank.
now_ns 🔒
CLOCK_MONOTONIC in nanoseconds, the clock PipeWire stamps frames with.
open_eis 🔒
Ask the portal for an EIS socket and bring an injector up on it, or None when the backend refuses ConnectToEIS (leaving the Notify* path live) or the EIS handshake binds nothing.
open_ext_session 🔒
Open an ext capture session on output, with or without the host painting its cursor into the frames, and wait for its first buffer constraints: the point that decides whether this protocol works here at all. A session’s cursor option is fixed at creation, so a toggle closes the session and opens another through here.
output_order_key 🔒
Natural sort key for an output name: text prefix plus trailing number, so HEADLESS-2 orders before HEADLESS-10. Unnamed outputs keep registry order after every named one.
portal_capture_loop 🔒
The capture thread of a portal-fed output: it holds the PipeWire stream of the output’s portal stream for as long as a Start aims at it, re-offering the stream’s configuration on every change, returning released buffers, and following the portal session across reopenings. Frames themselves are published by the stream’s own thread.
portal_thread 🔒
The portal thread: opens the session once the first Start has decided the cursor mode, reopens it (restoring with the token the backend gave) whenever that mode changes, and ends with the host session.
pump_ctrl 🔒
One bounded dispatch step for the control connection (false = connection died).
pump_until 🔒
Dispatch events until done(state) holds, waiting on the Wayland socket and the wake pipe together so control messages interrupt any capture wait. The predicate is re-checked after every dispatch, so a wait whose condition was satisfied by already-queued events returns without ever blocking.
scroll_notches 🔒
The whole notches in carry + delta pixels, the fraction left in carry.
shm_src_layout 🔒
Source bytes-per-pixel and whether R and B swap to reach BGRA, for one announced wl_shm format. The consumers downstream all read BGRA (byte order B,G,R,A), so a format is mapped by where its red and blue land in memory under the little-endian byte order wayland.xml specifies:
stream_point 🔒
A union-layout point in the logical space of the stream behind slot: the slot captures its output at physical size want, the portal addresses it at logical size logical.
trace 🔒
PIXELFLUX_HOST_TRACE set: log every host frame’s age on arrival and once encoded.
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.