fn run_shm_capture<F>(
settings: CaptureSettings,
controls: Arc<Controls>,
encode_tid_tx: Sender<ThreadId>,
on_frame: F,
) -> Result<(), String>Expand description
Run the X11 capture pipeline until stop is set, splitting capture and encode across two
threads that overlap for throughput.
This (the caller’s) thread performs setup and then the capture loop; a spawned encode thread runs
encode_loop and invokes on_frame(stripes) once per encoded frame. The split lets capture and
encode overlap, and because frames are throttled/dropped as RAW frames before encode, the delivered
H.264 stays a valid contiguous reference chain.
- Setup: connect to X (a private connection) and require a 32-bpp BGRA root — the Z-pixmap
byte depth must be 4, which modern servers use for depth 24/32 — then negotiate XShm and
XFixes. XFixes is always negotiated (one round-trip) so the cursor overlay can be toggled on
live even when capture started without it. Initial dimensions and origin are resolved from the
settings and the live root geometry, and a
FramePoolofPOOL_N= 3 shm surfaces is allocated — the working set (one in-capture, one in-slot, one in-encode) that a one-frame-ahead demand-driven capture needs, which also keeps the memory cost (3 * W*H*4, significant at 4K) bounded. - Encode thread: spawned with a raised scheduling priority; it reports its thread id back
through
encode_tid_txso the caller can detect a re-entrant stop issued from inside the delivery callback. - Capture loop (until
stop): fps is re-read each iteration for live pacing. The wait runs to the frame deadline, but a region the X server reports damaged ends it early once the shared pacing budget allows (crate::pace), so a screen that changes mid-period is published then rather than on the next tick. A server without DAMAGE keeps the deadline alone.- Live region change (
region_dirty): re-target the grab origin immediately (an x/y pan needs no surface work); a size change reuses the drain/recreate path below. - Auto-adjust: on a root geometry change, drain in-flight frames then recreate the surfaces
and bump the generation (which the encode thread turns into a reshape or rebuild). The geometry
is re-resolved AFTER the drain, because a slow drain can outlast another geometry change (a
fast flap) and recreating at a stale size would make the next
shm_get_imageexceed the root; a fully-reverted flap keeps its surfaces as-is. A stop that races the drain breaks out to teardown. - Grab: acquire a pooled surface (blocks until the encoder frees one), then bring it up
to the screen: from a shared framebuffer, the tiles drawn since the surface last held
the screen, and the cursor’s and the watermark’s, whose wait ends on the watch rather
than on DAMAGE (
framebuffer); else, or where no whole read comes,shm_get_imageof the region synchronously (reply()waits). A framebuffer a resize retired, or one of a server that went away, is watched again on the next grab. Where the encode thread’s pipeline takes 8-bit 4:2:0 planes (Controls::takes_planes) and no overlay is drawn, the surface’s planes are converted from the shared framebuffer in place of copying its pixels (FramebufferGrab::convert), and the frame carries them. - Overlays: composite the XFixes cursor (drawn at its hotspot-offset origin; live-toggleable) and the watermark onto the BGRA pixels on the CPU.
- Publish: hand the finished frame to the encode thread (blocks until the slot is free; never drops). A stop observed while waiting in acquire/publish exits the loop.
- Live region change (
- Teardown: stop and JOIN the encode thread BEFORE destroying the shm surfaces it may still be reading, preserving the resize-safety guarantee.
Blocking; intended to run on a dedicated thread. The X connection and shm surfaces live on this thread and the encoder lives on the encode thread — nothing X-related crosses the boundary.