doe 1.1.90

doe is a powerful Rust crate designed to enhance development workflow by providing an extensive collection of useful macros and utility functions. It not only simplifies common tasks but also offers convenient features for clipboard management,robust cryptographic functions,keyboard input, and mouse interaction.
Documentation
//! Linux Wayland screen capture via external tools.
//!
//! Wayland's security model forbids arbitrary clients from reading the screen
//! buffer directly. Rather than pull in D-Bus / portal bindings, this backend
//! shells out to the compositor-provided screenshot CLI that most Wayland
//! desktops ship:
//!
//! 1. `grim` — wlroots-based compositors (Sway, Hyprland, …), preferred.
//! 2. `gnome-screenshot` — GNOME.
//! 3. `spectacle -b` — KDE Plasma.
//!
//! If none are available the capture functions return a clear error explaining
//! what to install. This keeps the Wayland path free of extra Rust dependencies.

use super::Screen;
use image::RgbaImage;
use std::process::{Command, Stdio};

/// Returns true when the current session looks like Wayland.
pub fn is_wayland() -> bool {
    let session = std::env::var("XDG_SESSION_TYPE").unwrap_or_default();
    let wl_display = std::env::var("WAYLAND_DISPLAY").unwrap_or_default();
    session.eq_ignore_ascii_case("wayland") || wl_display.to_lowercase().contains("wayland")
}

/// Wayland has no portable multi-monitor enumeration without per-compositor
/// protocols. We report a single virtual screen; `capture` then grabs the
/// whole output via the external tool. Dimensions default to 0 and are filled
/// in by the tool at capture time, so callers that pass them through to
/// `capture_area` still work for the common "grab everything" case.
pub fn all() -> Result<Vec<Screen>, String> {
    Ok(vec![Screen {
        id: 0,
        x: 0,
        y: 0,
        width: 0,
        height: 0,
        scale_factor: 1.0,
    }])
}

/// Captures the whole Wayland desktop.
pub fn capture(_screen: &Screen) -> Result<RgbaImage, String> {
    // grim can write PNG to stdout, which avoids touching the filesystem.
    if has("grim") {
        let output = Command::new("grim")
            .arg("-")
            .stdout(Stdio::piped())
            .stderr(Stdio::null())
            .output()
            .map_err(|e| format!("grim invocation failed: {e}"))?;
        if output.status.success() {
            return png_bytes_to_rgba(&output.stdout);
        }
    }
    // Fallbacks that need a file on disk.
    if has("gnome-screenshot") {
        let tmp = crate::utils::temp::TempDir::with_prefix("doe-wayland-shot")
            .map_err(|e| e.to_string())?;
        let file = tmp.path().join("shot.png");
        let status = Command::new("gnome-screenshot")
            .arg("-f")
            .arg(&file)
            .stdout(Stdio::null())
            .stderr(Stdio::null())
            .status()
            .map_err(|e| format!("gnome-screenshot invocation failed: {e}"))?;
        if status.success() && file.exists() {
            return image::open(&file)
                .map_err(|e| format!("gnome-screenshot produced unreadable PNG: {e}"))
                .map(|img| img.into_rgba8());
        }
    }
    if has("spectacle") {
        let tmp = crate::utils::temp::TempDir::with_prefix("doe-wayland-shot")
            .map_err(|e| e.to_string())?;
        let file = tmp.path().join("shot.png");
        let status = Command::new("spectacle")
            .arg("-b")
            .arg("-n")
            .arg("-o")
            .arg(&file)
            .stdout(Stdio::null())
            .stderr(Stdio::null())
            .status()
            .map_err(|e| format!("spectacle invocation failed: {e}"))?;
        if status.success() && file.exists() {
            return image::open(&file)
                .map_err(|e| format!("spectacle produced unreadable PNG: {e}"))
                .map(|img| img.into_rgba8());
        }
    }
    Err("Wayland screen capture requires one of: grim, gnome-screenshot, spectacle. Install one and retry.".to_string())
}

/// Captures a sub-rectangle. grim supports `-g "x,y WxH"` geometry; the GNOME
/// / KDE fallbacks do not, so for them we capture full-screen and crop.
pub fn capture_area(screen: &Screen, x: i32, y: i32, width: u32, height: u32) -> Result<RgbaImage, String> {
    if has("grim") && width > 0 && height > 0 {
        let geometry = format!("{x},{y} {width}x{height}");
        let output = Command::new("grim")
            .arg("-g")
            .arg(&geometry)
            .arg("-")
            .stdout(Stdio::piped())
            .stderr(Stdio::null())
            .output()
            .map_err(|e| format!("grim invocation failed: {e}"))?;
        if output.status.success() {
            return png_bytes_to_rgba(&output.stdout);
        }
    }
    // Fallback: full-screen capture + crop.
    let full = capture(screen)?;
    // Ensure crop stays within image bounds.
    let x = x.max(0) as u32;
    let y = y.max(0) as u32;
    let w = width.min(full.width().saturating_sub(x));
    let h = height.min(full.height().saturating_sub(y));
    if w == 0 || h == 0 {
        return Err("capture_area would produce an empty image".to_string());
    }
    Ok(image::imageops::crop_imm(&full, x, y, w, h).to_image())
}

fn has(cmd: &str) -> bool {
    // `which`/`command -v` differ across shells; probing via `Command::new`
    // avoids spawning a shell and is portable.
    Command::new(cmd)
        .arg("--version")
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status()
        .is_ok()
}

/// Decodes a PNG byte buffer into an RGBA image.
fn png_bytes_to_rgba(bytes: &[u8]) -> Result<RgbaImage, String> {
    image::load_from_memory_with_format(bytes, image::ImageFormat::Png)
        .map_err(|e| format!("failed to decode PNG from screenshot tool: {e}"))
        .map(|img| img.into_rgba8())
}