Skip to main content

run_shm_capture

Function run_shm_capture 

Source
fn run_shm_capture<F>(
    settings: CaptureSettings,
    controls: Arc<Controls>,
    encode_tid_tx: Sender<ThreadId>,
    on_frame: F,
) -> Result<(), String>
where F: FnMut(Vec<EncodedStripe>) + Send + 'static,
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.

  1. 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 FramePool of POOL_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.
  2. Encode thread: spawned with a raised scheduling priority; it reports its thread id back through encode_tid_tx so the caller can detect a re-entrant stop issued from inside the delivery callback.
  3. 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_image exceed 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_image of 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.
  4. 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.