miniscreenshot
A pluggable, multi-crate Rust workspace for taking screenshots of windowed applications or the entire desktop.
Crate overview
| Crate | Description |
|---|---|
miniscreenshot |
Core — Screenshot type, PNG / PPM / PGM encoding, Capture / CaptureAsync / MultiCapture traits |
miniscreenshot-softbuffer |
softbuffer integration + re-export. Enable the winit feature to re-export winit alongside softbuffer. |
miniscreenshot-wgpu |
wgpu texture readback + re-export |
miniscreenshot-wayland |
Wayland wlr-screencopy-v1 system capture + re-exports |
miniscreenshot-x11 |
X11 (XGetImage / MIT-SHM) system capture + re-exports |
miniscreenshot-portal |
XDG Desktop Portal (ashpd) system capture; works on GNOME, KDE, wlroots, and inside Flatpak/Snap |
miniscreenshot-skia |
skia-safe re-export + surface screenshot helper |
miniscreenshot-vello |
vello re-export + pixel readback support |
miniscreenshot-minifb |
minifb re-export + pixel buffer screenshot helper |
miniscreenshot-desktop |
Umbrella — auto-selects Wayland → X11 → Portal for "just take a screenshot" |
miniscreenshot-mcp |
MCP server — serves a screenshot tool over the Model Context Protocol (streamable HTTP) |
Design goals
- Pluggable — each rendering backend is a separate crate. Applications depend only on what they use.
- No version conflicts — every driver crate re-exports its underlying
library (e.g.,
miniscreenshot_wgpu::wgpu). Depending on a driver crate is sufficient; no separatewgpu/winit/… dependency required. - Low-friction output formats — PNG (default), PPM and PGM are supported out of the box. Format is inferred from the file extension.
- System screenshots — Linux Wayland via
zwlr_screencopy_manager_v1(wlroots-based compositors: Sway, Hyprland, …), X11 viaXGetImagewith an MIT-SHM fast path, and XDG Desktop Portal viaashpd(GNOME, KDE, Flatpak, Snap).
Quick start
Core crate
[]
= "0.3"
use Screenshot;
// Build from raw RGBA8 pixel data
let data = vec!; // 1×1 red pixel
let shot = from_rgba;
// Save — format inferred from extension (.png / .ppm / .pgm)
shot.save.unwrap;
// Or let a "screenshot key" handler name the file for you:
// writes screenshots/screenshot-YYYYMMDD-HHMMSS-mmm.png, returns the path.
let path = shot.save_in_dir_timestamped.unwrap;
// Or encode to bytes explicitly
let png_bytes: = shot.encode_png.unwrap;
let ppm_bytes: = shot.encode_ppm; // lossless, trivial format
let pgm_bytes: = shot.encode_pgm; // grayscale
Desktop umbrella (just take a screenshot)
[]
= "0.3"
use take;
let shot = take.expect;
shot.save.unwrap;
The take() function auto-selects the best backend: Wayland → X11 → Portal.
softbuffer backend
[]
= "0.3"
use ;
// softbuffer stores pixels as u32 XRGB8888 values
let pixels: & = /* buffer.deref() from softbuffer */ &;
let shot = capture;
shot.save.unwrap;
softbuffer + winit pairing
When you want to create a softbuffer::Surface from a winit::Window, enable
the winit feature. This re-exports winit alongside softbuffer at the same
version, avoiding dependency conflicts.
[]
= { = "0.3", = ["winit"] }
use Window;
use softbuffer;
use Rc;
// Rc<Window> implements the raw-handle traits softbuffer needs.
let window: = /* create window */;
let ctx = new.unwrap;
let surface = new.unwrap;
See the softbuffer_winit_scene_screenshot example for a complete demo.
wgpu backend
[]
= "0.3"
use ;
// `texture` must have been created with TextureUsages::COPY_SRC
let shot = capture.unwrap;
shot.save.unwrap;
Capturing a frame you present: the swapchain/surface texture is acquired without
COPY_SRCand cannot be captured directly. Render your scene into an offscreen texture created withRENDER_ATTACHMENT | COPY_SRC,capturethat, then blit it to the surface to present. See thewgpu_scene_screenshotexample. From an async render loop, wrap the (blocking)capturecall intokio::task::spawn_blocking.
Wayland system screenshot
[]
= "0.3"
use WaylandCapture;
let mut cap = connect.expect;
println!;
// Capture first monitor
let shot = cap.capture_output.expect;
shot.save.unwrap;
// Or capture all monitors at once
let shots = cap.capture_all.expect;
Compositor requirements: Requires a Wayland compositor that implements
zwlr_screencopy_manager_v1(wlroots-based — Sway, Hyprland, weston, cage, labwc, …). GNOME-on-Wayland and KWin do not implement this protocol and will returnWaylandCaptureError::NoScreencopyManager. Useminiscreenshot-portalinstead on these compositors.
X11 system screenshot
[]
= "0.3"
use X11Capture;
let mut cap = connect.expect;
println!;
// Capture first screen
let shot = cap.capture_screen.expect;
shot.save.unwrap;
// Or capture all screens at once
let shots = cap.capture_all.expect;
Server requirements: Requires a reachable X11 server (
$DISPLAYset). Uses MIT-SHM when available for a fast-path capture; otherwise falls back to a plainXGetImagetransfer over the wire.
Screenshot portal (GNOME / KDE / Flatpak)
[]
= "0.3"
Blocking usage (default):
use PortalCapture;
let mut cap = connect.expect;
let shot = cap.capture_interactive.expect;
shot.save.unwrap;
Async usage:
[]
= { = "0.3", = false, = ["tokio"] }
use PortalCapture;
async
Portal requirements: Requires a running desktop session with
$XDG_RUNTIME_DIRand a portal implementation (xdg-desktop-portal+ a backend such asxdg-desktop-portal-gnome,-kde,-wlr, or-gtk). GNOME always shows a confirmation dialog; KDE and wlroots may or may not depending on backend policy. Works inside Flatpak and Snap sandboxes. Use this crate on GNOME or KWin instead ofminiscreenshot-wayland.
MCP integration
[]
= "0.3"
Embed it in your running game or editor so a coding agent can inspect the live
frame. Serve any Capture (or CaptureAsync) implementor over streamable HTTP:
use ScreenshotServer;
// `capture` can be any `Capture` — a closure over your wgpu frame, or a
// desktop / wayland / x11 / portal backend.
let capture = take;
let server = new;
server.serve.await?; // serves on http://127.0.0.1:8731/mcp
The server is meant to live inside the long-running process you want to inspect; the agent connects to it over HTTP. (That is why the transport is HTTP rather than stdio: a stdio server would be a fresh subprocess that cannot see your already-running game.)
It exposes a single screenshot tool. An agent can call it with no path to
just see the current frame inline:
or with a path to also save it to disk:
| Argument | Type | Default | Description |
|---|---|---|---|
path |
string |
(none) | Where to save the screenshot. Omit to return the image inline only — nothing is written to disk. |
format |
"png" | "ppm" | "pgm" |
null |
Explicit format override for the saved file (extension is inferred otherwise) |
include_image |
bool |
false |
Also return the image inline as base64 ImageContent. Forced on when path is omitted. |
max_dimension |
int |
1568 |
Cap the inline image's longest side (aspect preserved) to keep the response small. 0 sends it full-resolution. The saved file is always full-resolution. |
Capture your game's frame (wgpu)
To serve your rendered frame rather than the desktop, give the server a
WgpuFrameTarget. It owns clones of your
wgpu device/queue and a swappable "current frame" texture, so it satisfies
Capture + Send + 'static and can live inside the server while your render loop
keeps publishing frames:
use ScreenshotServer;
use WgpuFrameTarget;
// Render into an offscreen texture created with RENDER_ATTACHMENT | COPY_SRC.
let target = new;
// Hand one clone to the server (runs on its own task)…
let server = new;
spawn;
// …and in your render loop, publish each finished frame:
loop
A capture reads whatever was last published; wgpu serializes GPU work on the
queue, so the readback observes a complete frame. See the
wgpu_game_mcp_server example for a runnable headless version.
Custom configuration
use ;
let config = ServerConfig ;
let server = with_config;
server.serve.await?;
Async capture (CaptureAsync)
For async-native backends like the portal with its async feature:
use AsyncScreenshotServer;
use PortalCapture;
let capture = connect_async.await;
let server = new;
server.serve.await?;
Connecting a coding agent
Point your MCP-capable agent at the running server. With Claude Code:
The agent can then call the screenshot tool to see your game's current frame
as you iterate — pass a path when you also want the capture saved to disk.
minifb (prototyping window)
[]
= "0.3"
use capture;
// minifb stores pixels as u32 in 0RGB8888 format
let pixels: & = /* buffer passed to Window::update_with_buffer() */ &;
let shot = capture.unwrap;
shot.save.unwrap;
Full window example:
use minifb;
use capture;
Capture trait
All system-capture driver crates (-wayland, -x11, -portal) implement
the core Capture trait, making backends interchangeable:
use Capture;
Closures as Capture
A blanket implementation allows any FnMut() -> Result<Screenshot, E> to be
used as a Capture. This means free functions like miniscreenshot_wgpu::capture
can be used directly:
use Capture;
// Usage with a wgpu closure:
let mut cap = ;
take_and_save;
MultiCapture
For backends with multiple outputs (monitors), the MultiCapture super-trait
provides source_count(), capture_index(), and capture_all():
use ;
Optional features
# Winit (for softbuffer + winit integration)
= { = "0.3", = ["winit"] }
Portal features
miniscreenshot-portal exposes runtime and API-surface features. Enabling
a runtime (tokio or async-io) automatically enables the async API surface.
# Default: tokio runtime + blocking API + async API
= "0.3"
# Async-only with tokio (no blocking convenience methods)
= { = "0.3", = false, = ["tokio"] }
# Async-only with async-io
= { = "0.3", = false, = ["async-io"] }
The tokio and async-io runtime features are mutually exclusive. The
blocking API-surface feature is independent. The async API surface is
implied by whichever runtime you select, but can also be enabled standalone
if you want to provide your own executor.
Output formats
| Format | Method | Notes |
|---|---|---|
| PNG | encode_png() / save("file.png") |
Lossless, widely supported |
| PPM | encode_ppm() / save("file.ppm") |
Binary P6, trivial to parse |
| PGM | encode_pgm() / save("file.pgm") |
Binary P5 grayscale (BT.601 luma) |
Examples
Each crate ships with a self-contained examples/<crate_short>_scene_screenshot.rs that
renders a scene (or synthesises a buffer) and saves a PNG.
| Crate | Command | Headless? |
|---|---|---|
miniscreenshot (core) |
cargo run -p miniscreenshot --example core_scene_screenshot |
Yes |
miniscreenshot-softbuffer |
cargo run -p miniscreenshot-softbuffer --example softbuffer_scene_screenshot |
Yes |
miniscreenshot-softbuffer (winit) |
cargo run -p miniscreenshot-softbuffer --example softbuffer_winit_scene_screenshot --features winit |
No (needs a display) |
miniscreenshot-wgpu |
cargo run -p miniscreenshot-wgpu --example wgpu_scene_screenshot |
Yes |
miniscreenshot-wayland |
cargo run -p miniscreenshot-wayland --example wayland_scene_screenshot |
No (needs wlroots-based Wayland compositor) |
miniscreenshot-x11 |
cargo run -p miniscreenshot-x11 --example x11_scene_screenshot |
No (needs $DISPLAY / X11 server) |
miniscreenshot-portal |
cargo run -p miniscreenshot-portal --example portal_scene_screenshot |
No (needs desktop session with portal) |
miniscreenshot-portal (async) |
cargo run -p miniscreenshot-portal --example portal_async_scene_screenshot --features async |
No (needs desktop session with portal) |
miniscreenshot-mcp (desktop) |
cargo run -p miniscreenshot-mcp --example desktop_mcp_server |
No (needs desktop session) |
miniscreenshot-mcp (portal async) |
cargo run -p miniscreenshot-mcp --example portal_async_mcp_server |
No (needs desktop session with portal) |
miniscreenshot-mcp (wgpu game) |
cargo run -p miniscreenshot-mcp --example wgpu_game_mcp_server |
No (runs a server; needs a GPU) |
miniscreenshot-skia |
cargo run -p miniscreenshot-skia --example skia_scene_screenshot |
Yes |
miniscreenshot-vello |
cargo run -p miniscreenshot-vello --example vello_scene_screenshot |
Yes |
miniscreenshot-minifb |
cargo run -p miniscreenshot-minifb --example minifb_scene_screenshot |
Yes |
Build all examples at once:
Build and run all headless examples:
License
Licensed under either of Apache License 2.0 or MIT License at your option.