| Crates.io | grim-rs |
| lib.rs | grim-rs |
| version | 0.1.4 |
| created_at | 2025-09-23 09:02:59.006479+00 |
| updated_at | 2026-01-24 07:14:57.824525+00 |
| description | Rust implementation of grim screenshot utility for Wayland |
| homepage | https://github.com/vremyavnikuda/grim-rs |
| repository | https://github.com/vremyavnikuda/grim-rs |
| max_upload_size | |
| id | 1851170 |
| size | 289,855 |
if you like this project, then the best way to express gratitude is to give it a star ⭐, it doesn't cost you anything, but I understand that I'm moving the project in the right direction.
Rust implementation of grim-rs screenshot utility for Wayland compositors.
⚠️ Breaking Changes in v0.1.3
Version 0.1.3 introduces breaking changes related to struct field encapsulation. See MIGRATION.md for upgrade guide.
wayland-client~/PicturesAdd to your Cargo.toml:
[dependencies]
grim-rs = "0.1.3"
Upgrading from 0.1.2? See MIGRATION.md for breaking changes.
use grim_rs::{Grim, Box};
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
// Capture entire screen (all outputs)
let result = grim.capture_all()?;
grim.save_png(result.data(), result.width(), result.height(), "screenshot.png")?;
// Capture specific region (automatically composites across monitors)
let region = Box::new(100, 100, 800, 600);
let result = grim.capture_region(region)?;
grim.save_png(result.data(), result.width(), result.height(), "region.png")?;
// Capture specific output by name (handles transforms/rotation automatically)
let result = grim.capture_output("DP-1")?;
grim.save_png(result.data(), result.width(), result.height(), "output.png")?;
Ok(())
}
use grim_rs::Grim;
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
// Get list of available outputs with their properties
let outputs = grim.get_outputs()?;
for output in outputs {
println!("Output: {}", output.name());
println!(" Position: ({}, {})", output.geometry().x(), output.geometry().y());
println!(" Size: {}x{}", output.geometry().width(), output.geometry().height());
println!(" Scale: {}", output.scale());
if let Some(desc) = output.description() {
println!(" Description: {}", desc);
}
}
Ok(())
}
use grim_rs::{Grim, Box};
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
// Capture entire screen with scaling (high-quality downscaling)
let result = grim.capture_all_with_scale(0.5)?; // 50% size, uses Lanczos3 filter
grim.save_png(result.data(), result.width(), result.height(), "thumbnail.png")?;
// Capture region with scaling
let region = Box::new(0, 0, 1920, 1080);
let result = grim.capture_region_with_scale(region, 0.8)?; // 80% size, uses Triangle filter
grim.save_png(result.data(), result.width(), result.height(), "scaled.png")?;
// Capture specific output with scaling
let result = grim.capture_output_with_scale("DP-1", 0.5)?;
grim.save_png(result.data(), result.width(), result.height(), "output_scaled.png")?;
Ok(())
}
use grim_rs::{Grim, Box, CaptureParameters};
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
// Capture multiple outputs with different parameters
let parameters = vec![
CaptureParameters::new("DP-1")
.overlay_cursor(true),
CaptureParameters::new("HDMI-A-1")
.region(Box::new(0, 0, 1920, 1080))
.scale(0.5)
];
let results = grim.capture_outputs(parameters)?;
for (output_name, result) in results.into_outputs() {
let filename = format!("{}.png", output_name);
grim.save_png(result.data(), result.width(), result.height(), &filename)?;
}
Ok(())
}
use grim_rs::Grim;
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
let result = grim.capture_all()?;
// Save as PNG with default compression (level 6)
grim.save_png(result.data(), result.width(), result.height(), "screenshot.png")?;
// Save as PNG with custom compression (0-9, where 9 is highest)
grim.save_png_with_compression(result.data(), result.width(), result.height(), "compressed.png", 9)?;
// Save as JPEG with default quality (80)
grim.save_jpeg(result.data(), result.width(), result.height(), "screenshot.jpg")?;
// Save as JPEG with custom quality (0-100, where 100 is highest)
grim.save_jpeg_with_quality(result.data(), result.width(), result.height(), "quality.jpg", 95)?;
// Save as PPM (uncompressed)
grim.save_ppm(result.data(), result.width(), result.height(), "screenshot.ppm")?;
Ok(())
}
use grim_rs::Grim;
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
let result = grim.capture_all()?;
// Convert to PNG bytes
let png_bytes = grim.to_png(result.data(), result.width(), result.height())?;
println!("PNG size: {} bytes", png_bytes.len());
// Convert to PNG bytes with custom compression
let png_bytes = grim.to_png_with_compression(result.data(), result.width(), result.height(), 9)?;
// Convert to JPEG bytes
let jpeg_bytes = grim.to_jpeg(result.data(), result.width(), result.height())?;
println!("JPEG size: {} bytes", jpeg_bytes.len());
// Convert to JPEG bytes with custom quality
let jpeg_bytes = grim.to_jpeg_with_quality(result.data(), result.width(), result.height(), 85)?;
// Convert to PPM bytes
let ppm_bytes = grim.to_ppm(result.data(), result.width(), result.height())?;
println!("PPM size: {} bytes", ppm_bytes.len());
Ok(())
}
use grim_rs::Grim;
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
let result = grim.capture_all()?;
// Write PNG to stdout
grim.write_png_to_stdout(result.data(), result.width(), result.height())?;
// Write PNG to stdout with custom compression
grim.write_png_to_stdout_with_compression(result.data(), result.width(), result.height(), 6)?;
// Write JPEG to stdout
grim.write_jpeg_to_stdout(result.data(), result.width(), result.height())?;
// Write JPEG to stdout with custom quality
grim.write_jpeg_to_stdout_with_quality(result.data(), result.width(), result.height(), 90)?;
// Write PPM to stdout
grim.write_ppm_to_stdout(result.data(), result.width(), result.height())?;
Ok(())
}
use grim_rs::Grim;
fn main() -> grim_rs::Result<()> {
let mut grim = Grim::new()?;
// Read region specification from stdin (format: "x,y widthxheight")
let region = Grim::read_region_from_stdin()?;
let result = grim.capture_region(region)?;
grim.save_png(result.data(), result.width(), result.height(), "region.png")?;
Ok(())
}
The grim-rs binary supports the same functionality as the library API. By default, saves to ~/Pictures (XDG Pictures directory) with timestamped filenames.
Available Options:
-h Show help message and quit
-s <factor> Set the output image's scale factor (e.g., 0.5 for 50%)
-g <geometry> Set the region to capture (format: "x,y widthxheight")
-t png|ppm|jpeg Set the output filetype (default: png)
-q <quality> Set the JPEG compression quality (0-100, default: 80)
-l <level> Set the PNG compression level (0-9, default: 6)
-o <output> Set the output name to capture (e.g., "DP-1", "HDMI-A-1")
-c Include cursor in the screenshot
Usage Examples:
# Build the binary first
cargo build --release
# Capture entire screen (saves to ~/Pictures/<timestamp>.png)
cargo run --bin grim-rs
# Capture with specific filename
cargo run --bin grim-rs -- screenshot.png
# Capture specific region
cargo run --bin grim-rs -- -g "100,100 800x600" region.png
# Capture with scaling (50% size, creates thumbnail)
cargo run --bin grim-rs -- -s 0.5 thumbnail.png
# Capture specific output by name
cargo run --bin grim-rs -- -o DP-1 monitor.png
# Capture with cursor included
cargo run --bin grim-rs -- -c -o DP-1 with_cursor.png
# Save as JPEG with custom quality
cargo run --bin grim-rs -- -t jpeg -q 90 screenshot.jpg
# Save as PNG with maximum compression
cargo run --bin grim-rs -- -l 9 compressed.png
# Save as PPM (uncompressed)
cargo run --bin grim-rs -- -t ppm screenshot.ppm
# Combine options: region + scaling + cursor
cargo run --bin grim-rs -- -g "0,0 1920x1080" -s 0.8 -c scaled_region.png
# Capture to stdout and pipe to another program
cargo run --bin grim-rs -- - > screenshot.png
# Save to custom directory via environment variable
GRIM_DEFAULT_DIR=/tmp cargo run --bin grim-rs
# Read region from stdin
echo "100,100 800x600" | cargo run --bin grim-rs -- -g -
Using the installed binary:
After installation with cargo install grim-rs, you can use it directly:
# Capture entire screen
grim-rs
# All the same options work without 'cargo run'
grim-rs -g "100,100 800x600" -s 0.5 thumbnail.png
grim-rs -o DP-1 -c monitor.png
grim-rs - | wl-copy # Pipe to clipboard
Note: The binary is named grim-rs to avoid conflicts with the original C implementation of grim.
wl_shm - Shared memory bufferszwlr_screencopy_manager_v1 - Screenshot capture (wlroots extension)wl_output - Output informationGrim::new() - Create new Grim instance and connect to Wayland compositorget_outputs() - Get list of available outputs with their properties (name, geometry, scale)capture_all() - Capture entire screen (all outputs)capture_all_with_scale(scale: f64) - Capture entire screen with scalingcapture_output(output_name: &str) - Capture specific output by namecapture_output_with_scale(output_name: &str, scale: f64) - Capture output with scalingcapture_region(region: Box) - Capture specific rectangular regioncapture_region_with_scale(region: Box, scale: f64) - Capture region with scalingcapture_outputs(parameters: Vec<CaptureParameters>) - Capture multiple outputs with different parameterscapture_outputs_with_scale(parameters: Vec<CaptureParameters>, default_scale: f64) - Capture multiple outputs with scalingsave_png(&data, width, height, path) - Save as PNG with default compression (level 6)save_png_with_compression(&data, width, height, path, compression: u8) - Save as PNG with custom compression (0-9)save_jpeg(&data, width, height, path) - Save as JPEG with default quality (80) [requires jpeg feature]save_jpeg_with_quality(&data, width, height, path, quality: u8) - Save as JPEG with custom quality (0-100) [requires jpeg feature]save_ppm(&data, width, height, path) - Save as PPM (uncompressed)to_png(&data, width, height) - Convert to PNG bytes with default compressionto_png_with_compression(&data, width, height, compression: u8) - Convert to PNG bytes with custom compressionto_jpeg(&data, width, height) - Convert to JPEG bytes with default quality [requires jpeg feature]to_jpeg_with_quality(&data, width, height, quality: u8) - Convert to JPEG bytes with custom quality [requires jpeg feature]to_ppm(&data, width, height) - Convert to PPM byteswrite_png_to_stdout(&data, width, height) - Write PNG to stdout with default compressionwrite_png_to_stdout_with_compression(&data, width, height, compression: u8) - Write PNG to stdout with custom compressionwrite_jpeg_to_stdout(&data, width, height) - Write JPEG to stdout with default quality [requires jpeg feature]write_jpeg_to_stdout_with_quality(&data, width, height, quality: u8) - Write JPEG to stdout with custom quality [requires jpeg feature]write_ppm_to_stdout(&data, width, height) - Write PPM to stdoutGrim::read_region_from_stdin() - Read region specification from stdin (format: "x,y widthxheight")CaptureResultContains captured image data:
data: Vec<u8> - Raw RGBA image data (4 bytes per pixel)width: u32 - Image width in pixelsheight: u32 - Image height in pixelsCaptureParametersParameters for capturing specific outputs:
output_name: String - Name of the output to captureregion: Option<Box> - Optional region within the outputoverlay_cursor: bool - Whether to include cursor in capturescale: Option<f64> - Optional scale factor for the outputMultiOutputCaptureResultResult of capturing multiple outputs:
outputs: HashMap<String, CaptureResult> - Map of output names to their capture resultsOutputInformation about a display output:
name: String - Output name (e.g., "eDP-1", "HDMI-A-1")geometry: Box - Output position and sizescale: i32 - Scale factor (1 for normal DPI, 2 for HiDPI)description: Option<String> - Monitor model and manufacturer informationBoxRectangular region:
x: i32 - X coordinatey: i32 - Y coordinatewidth: i32 - Widthheight: i32 - Heightjpeg - Enable JPEG support (enabled by default)
save_jpeg*, to_jpeg*, and write_jpeg_to_stdout* methodsTo disable JPEG support:
[dependencies]
grim-rs = { version = "0.1.0", default-features = false }
Comprehensive API documentation is available at docs.rs or can be generated locally:
cargo doc --open
| Feature | Original grim | grim-rs |
|---|---|---|
| Language | C | Rust |
| Dependencies | libpng, pixman, wayland, libjpeg | Pure Rust crates |
| Output formats | PNG, JPEG, PPM | PNG, JPEG, PPM |
| Installation | System package | Rust crate |
| Integration | External process | Library + Binary |
| Memory safety | Manual | Guaranteed by Rust |
| Output transforms | ✅ | ✅ |
| Y-invert handling | ✅ | ✅ |
| Multi-monitor compositing | ✅ | ✅ |
| Image scaling | Nearest-neighbor | 4-tier adaptive (Triangle/CatmullRom/Lanczos3) |
| XDG Pictures support | ✅ | ✅ |
| Output descriptions | ✅ | ✅ |
| Color accuracy | ✅ | ✅ |
| Real capture | ✅ | ✅ |
┌─────────────────┐
│ Application │
├─────────────────┤
│ grim-rs │
├─────────────────┤
│ wayland-client │
├─────────────────┤
│ Wayland │
│ Compositor │
└─────────────────┘
Wayland Screencopy → Buffer → Output Transform → Y-invert → Scaling → Format Conversion → Save
↓ ↓ ↓
(rotation/flip) (vertical) (Bilinear/Lanczos3)
Adaptive 4-tier algorithm selection ensures optimal quality/performance balance:
Upscaling (scale > 1.0): Triangle filter
Mild downscaling (0.75 ≤ scale ≤ 1.0): Triangle filter
Moderate downscaling (0.5 ≤ scale < 0.75): CatmullRom filter
Heavy downscaling (scale < 0.5): Lanczos3 convolution
GRIM_DEFAULT_DIR - Override default screenshot directory (highest priority)XDG_PICTURES_DIR - XDG Pictures directory (from env or ~/.config/user-dirs.dirs)Priority order: GRIM_DEFAULT_DIR → XDG_PICTURES_DIR → current directory
zwlr_screencopy_manager_v1zwlr_screencopy_manager_v1 protocol supportcd grim-rs
cargo build --release
# Run tests
cargo test
# Run examples
cargo run --example simple all screenshot.png
cargo run --example multi_output
MIT License - see LICENSE file for details.