ZyrisDocs
Addon crate guides

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.

Debian / Ubuntu build dependencies
sudo apt-get install pkg-config libclang-dev libxcb1-dev libxrandr-dev \
    libdbus-1-dev libpipewire-0.3-dev libwayland-dev libegl-dev

These 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 methodDefault / behavior
with_backendOverride ScreenBackend::detect(); backend() reads the chosen backend.
with_formatPNG by default; PNG and JPEG are supported. A per-call format overrides it.
with_max_widthNo initial width limit by default. A per-call width overrides it.
with_jpeg_quality80 by default, clamped to 1–100.
with_budgetDefaults to zyris::proto::INLINE_BLOB_MAX; oversized encodings are shrunk in fit passes.
without_budgetDisables 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

EnvironmentCapture path / caveat
X11xcap using XRandR and host capture.
wlroots (Sway, Hyprland and similar)Direct wl_output / zwlr_screencopy when the probe succeeds.
Other Wayland sessionsFalls back to xcap; the Screenshot portal must actually be offered.
macOS / Windowsxcap 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

On this page