zyris-screen
Configure screenshots, select a capture backend, share display geometry and handle platform limits.
zyris-screen implements screen_capture v1 with HostScreenCapture. Its companion HostDisplays
supplies monitor geometry to input implementations. It captures still images, not a video session.
Dependencies and host setup
[dependencies]
zyris = { git = "https://github.com/attacca-cc/zyris-protocol", features = ["caps"] }
zyris-screen = { git = "https://github.com/attacca-cc/zyris-protocol" }There is no wayland Cargo feature for this crate: Linux capture backends are selected at runtime.
The Linux build links display libraries even if you ultimately select only one backend.
sudo apt-get install pkg-config libclang-dev libxcb1-dev libxrandr-dev \
libdbus-1-dev libpipewire-0.3-dev libwayland-dev libegl-devThese are build requirements, not a guarantee that a headless process can capture a desktop.
Run with access to the intended desktop session and its permissions/portals. Windows and macOS do
not need these Linux packages; macOS screen-recording permissions still matter at runtime.
The protocol repository's nix develop shell supplies the Linux build environment on NixOS.
Register and configure capture
use zyris::caps::{ImageFormat, ScreenCaptureServer};
use zyris::{Node, NodeKind};
use zyris_screen::HostScreenCapture;
fn screen_node() -> zyris::Result<Node> {
let capture = HostScreenCapture::default()
.with_format(ImageFormat::Jpeg)
.with_max_width(1280)
.with_jpeg_quality(70)
.with_budget(512 * 1024);
Node::builder()
.name("screen")
.kind(NodeKind::Desktop)
.capability(ScreenCaptureServer(capture))
.build()
}Connect using Connecting, with a TLS provider when using wss://.
| Builder method | Default / behavior |
|---|---|
with_backend | Override ScreenBackend::detect(); backend() reads the chosen backend. |
with_format | PNG by default; PNG and JPEG are supported. A per-call format overrides it. |
with_max_width | No initial width limit by default. A per-call width overrides it. |
with_jpeg_quality | 80 by default, clamped to 1–100. |
with_budget | Defaults to zyris::proto::INLINE_BLOB_MAX; oversized encodings are shrunk in fit passes. |
without_budget | Disables automatic byte-budget fitting; it does not remove remote result limits. |
The configured width is a default, not an unbreakable application limit: the caller can provide another width. Enforce a hard size policy in a wrapper if your deployment needs one. Byte fitting also has a minimum size and finite passes; a tiny budget is not a promise that every image will fit.
Call the capability
use std::time::Duration;
use zyris::caps::{ImageFormat, ScreenCapture, ScreenCaptureClient};
use zyris::{Connection, Datum};
async fn capture_primary(conn: &Connection) -> zyris::Result<Datum> {
let screen: ScreenCaptureClient = conn.wait_capability(Duration::from_secs(5)).await?;
let displays = screen.list_displays().await?;
let display = displays.iter().find(|d| d.primary).or_else(|| displays.first())
.ok_or_else(|| zyris::Error::invalid_params("no display is available"))?;
screen.screenshot(Some(display.id.clone()), None, Some(ImageFormat::Jpeg), Some(1280)).await
}screenshot(display, region, format, max_width) returns a Datum::Image, with inline bytes,
media type and description. Pass a display ID or name; ID matching takes priority. region is
an optional Region { x, y, width, height } in display-local captured pixels.
list_displays gives IDs, names, desktop origins, captured width/height, scale factor and primary flag.
Share the backend with input
If you force a capture backend, construct input geometry as HostDisplays(capture.backend())
before moving capture into its server. Independently detecting the default backend is usually
equivalent, but does not guarantee a match after an explicit override.
See the combined desktop example.
Coordinate conversion
Display sizes, display origins, screenshot regions and input positions use captured pixels.
They are not necessarily physical panel pixels—for example, a scaled Retina mode can capture a
larger backing image. Display::scale_factor is informational; do not multiply by it again.
If you locate a point (x, y) in a returned, resized crop, undo that resize first and add the
crop's origin to recover display-local coordinates. Then pass that point to input.move_to for
the same display. Do not add the display's whole-desktop origin; the input implementation does that.
Read the image description for any downscale factor rather than assuming the requested width was used.
Backends and current limitations
| Environment | Capture path / caveat |
|---|---|
| X11 | xcap using XRandR and host capture. |
| wlroots (Sway, Hyprland and similar) | Direct wl_output / zwlr_screencopy when the probe succeeds. |
| Other Wayland sessions | Falls back to xcap; the Screenshot portal must actually be offered. |
| macOS / Windows | xcap host APIs and platform permissions. |
The source documents unresolved GNOME secondary-monitor capture issues and unverified rotated
output geometry. GNOME fractional-scale display enumeration has special handling, but that does
not fix the separate libei input-coordinate limitation. Validate the exact compositor, display
layout and scale you deploy. Enumeration succeeding does not guarantee that capture permissions work.
Reference
Source and platform notes · Published crate · Released rustdoc