Expand description
X11/XShm capture loop, stripe dispatch, and per-stripe change detection.
X11 host capture: grab the root window into host memory as BGRA, composite the XFixes hardware
cursor and the watermark on the CPU, and feed each frame to pipeline::X11Pipeline, which owns
damage/stripe/encode. The grab goes through a shared-memory segment (XShm via x11rb) rather than
a plain GetImage for one reason: a full-screen frame is far too large to copy through the X
protocol socket every tick, so XShm has the server write the pixels straight into memory this
process already has mapped.
This is the general path, and it works against any X server. Two zero-copy paths come first
where they can serve the session: [dri3] on any server whose screen lives on the GPU has the
server blit the root into dmabufs the hardware encoder reads in place, and [nvfbc] on an
NVIDIA X server has the driver composite the screen straight into video memory and registers
that buffer with NVENC in place. run_capture tries them in that order and falls back here
otherwise. Here a server that shares its framebuffer (framebuffer, the images’ Xvfb with
the screen in system memory) is read in place, each frame copying only the tiles its surface
misses, with no request a frame, and XShm with XDamage serves every other server.
run_capture splits the work across two threads because grabbing the next frame and encoding
the previous one have no reason to wait on each other: the caller’s thread grabs frames and owns
the x11rb connection and the pool of shm surfaces, while a spawned encode thread owns the
pipeline::X11Pipeline (and thus the encoder) and runs the delivery callback, so the two overlap for
throughput. Frames hand off through a bounded FramePool that carries only a raw pointer and
geometry — never an X object, none of which is safe to share — so nothing X-related ever crosses
the thread boundary and the encoder never has to touch X. Multi-instance safety for the encoders
is handled inside them (e.g. the libx264 open/close lock); each capture owns its own private xcb
connection, so there is no shared X state to serialize here.
Modules§
- computer_
use - X11 backend for the Computer Use actions: XTEST injection and one-shot root screenshots over a private x11rb connection, so the agent can drive an X session with no active capture and no shared state with any streaming capture thread (the X server itself serializes).
- cursor
- Out-of-band X11 cursor delivery, the X11 counterpart of the Wayland compositor’s cursor
callback: one process-wide monitor thread (the X pointer is global, however many captures
run) parks in
wait_for_eventon its own connection for XFixesDisplayCursorNotifyand hands each new cursor to the registeredCursorSink— the same payload the Wayland backend sends, so a consumer needs one handler for both. Keeping the cursor out of the framebuffer means pointer motion over static content never dirties the damage hash or re-encodes video;capture_cursorcompositing stays available as the opt-in alternative. - dri3
- DRI3 zero-copy X11 capture: the X server blits the screen into GPU buffers this process allocated, and the hardware encoder reads them where they lie.
- framebuffer 🔒
- The screen read where the X server keeps it: SELKIES-FRAMEBUFFER, which the Selkies images’ Xvfb offers for a screen held in system memory, in place of XShm’s per-frame copy and XDamage’s round trips.
- nvfbc
- NVIDIA Framebuffer Capture (NvFBC): the zero-copy X11 capture path, where the NVIDIA driver composites the X screen straight into video memory and NVENC encodes it there.
Structs§
- Controls
- Cross-thread controls for a running capture: a bag of atomics (plus two mutex-guarded
payloads) the owning
ScreenCapturepyclass flips from the Python thread and the capture thread reads at the top of each iteration. - Frame
Pool 🔒 - Demand-driven capture -> encode handoff: a bounded single-slot channel over a fixed set of pooled shm surfaces.
- Pool
Inner 🔒 - Mutex-guarded interior of
FramePool: the free-surface index list, the single capture -> encode handoff slot, and the surface the encoder still reads, under one lock so moving a surface between them — acquire, publish, take, recycle, settle — is always a single atomic step. - RawFrame 🔒
- A captured raw BGRA frame held in a pooled shm surface, ready to encode.
- Root
Damage 🔒 - The X server’s report that the root window changed, which lets a capture publish a change as
it lands instead of on the next tick.
Nonewhere the server carries no DAMAGE extension, leaving the frame timer to pace the capture by itself. - ShmSurface 🔒
- A shared-memory image surface: a POSIX shm segment mapped into both this process and the
X server, into which
shm_get_imagewrites one BGRA frame. - Vblank
Claim 🔒 - A running capture’s place in
VblankRate, given up when the capture ends. - Vblank
Rate 🔒 - The rate the X server’s fake vblank runs at while this process captures it: the fastest live
capture’s frame rate, published on the root window as
_FAKE_SCREEN_MILLIHZ(CARDINAL, millihertz, 1000 to 1000000) and, rounded to whole frames for servers that read only that,_FAKE_SCREEN_FPS(1 to 1000), both deleted when the last capture ends. The Selkies Xvfb paces the clients that wait on Present by it, keeping the rate it started at as a floor unless the published one is at most 0.1% slower (59.94 under 60), so a vsynced application presents as fast as it is streamed; any other server holds it as inert data.
Statics§
Functions§
- captured_
damage 🔒 - The share of the captured area at (
x,y),widthxheight, that the root’s damagedrectscover. - clamp_
offset 🔒 - Clamp a capture offset to the live root so a grab always has at least one root pixel in
range: a past-end offset fails every
shm_get_imagewith BadMatch and would burn out the failure budget with no chance of recovery. The upper clamp also keeps protocol-range roots (INT16 coordinates) from truncating a wider value through theas i16cast. - cursor_
image_ 🔒origin - Frame-space top-left of the cursor image, given the XFixes hotspot position.
- encode_
loop 🔒 - Encode thread body: consume captured frames from the pool, keep the [
pipeline::X11Pipeline] in step with the capture size and cross-thread controls, encode, recycle, and deliver. - follow_
frame_ rate - Republish
VblankRateafter a running capture’s frame rate changed. - frame_
rects 🔒 - The root’s damaged
rectsin the coordinates of the captured area at (x,y),widthxheight, clipped to it. - grab_
frame 🔒 - One
shm_get_imagerequest is atomic — the server never interleaves another client inside it — so the grab is a coherent snapshot of whatever the screen held at that instant. Every grabbed frame is published; whether any of it changed is decided downstream by the encoder’s own per-stripe damage detection. - open_
framebuffer 🔒 - The shared framebuffer of
root’s screen (framebuffer), where the server offers one and its pixels over the captureregionread as an XShm grab of it intosurfacedoes, orNone, saying why where the server offers one that is not used. The comparison is tried a few times, since the screen may change between the grab and the read. - overlay_
cursor 🔒 - Composite the XFixes cursor (ARGB
u32per pixel, premultiplied by the XFixes format definition) onto the BGRA frame with its top-left at(img_x, img_y), blending each pixel throughblend_pixel_premultipliedwith per-pixel bounds clipping so an image straddling a frame edge writes only its in-frame portion. - readable 🔒
- Wait for the connection to carry something, for at most
timeout. - require_
32bpp 🔒 - Reject a server whose root visual is not 32 bits per pixel.
- resolve_
dims 🔒 - Resolve the capture dimensions
shm_get_imagewill read, bounded so the region can never run past the live root from the (clamped) capture offset: with auto-adjust (or unset width/height<= 0) the capture tracks the full root minus the offset, otherwise the requested size is clamped to what is available from it. Saturating subtraction plus a finalu16clamp (minimum 2) keep pathological settings from overflowing or collapsing to an unusable surface. Video codecs require even dimensions, so both are then rounded down to a multiple of two. - run_
capture - Capture an X11 display until
stopis set, on the best path this session can run. - run_
shm_ 🔒capture - Run the X11 capture pipeline until
stopis set, splitting capture and encode across two threads that overlap for throughput. - take_
tick 🔒 RootDamage::takefor a ticktriggerwoke: whether the root changed since the last frame, and where. A report the take finds counts whatever woke the tick: one landing after the wait gave up is spent here all the same, and called clean, its change would be cleared with nothing left to report it (a capture that sends only changes would never send it).- try_
rebuild_ 🔒channel - Rebuild the X channel + capture surfaces after a connection-level failure (Xorg restart, VT switch), refreshing the offset/dims state against the live root. The pool is drained first so the encode thread can never be reading a surface as it is destroyed, and its generation is bumped so the encoder rebuilds against the new layout. Every fallible step completes before the swap, and the old channel/surfaces are destroyed best-effort after it (their server is likely gone; the local detaches still run, and the kernel frees each old segment once nothing maps it).
- wait_
for_ 🔒frame - Block until the capture is due a frame and say what asked for it: a change the server reported, brought forward by as much as the pacing budget allows, or the frame timer.
- wait_
for_ 🔒framebuffer wait_for_frameon a shared framebuffer’s watch (framebuffer): a drawing operation the server signals on it ends the wait as a reported change would, with no round trip, and so does a resize retiring it.