use super::Screen;
use image::RgbaImage;
use std::process::{Command, Stdio};
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")
}
pub fn all() -> Result<Vec<Screen>, String> {
Ok(vec![Screen {
id: 0,
x: 0,
y: 0,
width: 0,
height: 0,
scale_factor: 1.0,
}])
}
pub fn capture(_screen: &Screen) -> Result<RgbaImage, String> {
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);
}
}
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())
}
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);
}
}
let full = capture(screen)?;
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 {
Command::new(cmd)
.arg("--version")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok()
}
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())
}