use std::path::PathBuf; use tempfile::Builder; #[derive(Debug)] pub enum PasteImageError { ClipboardUnavailable(String), NoImage(String), EncodeFailed(String), IoError(String), } impl std::fmt::Display for PasteImageError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { PasteImageError::ClipboardUnavailable(msg) => write!(f, "clipboard unavailable: {msg}"), PasteImageError::NoImage(msg) => write!(f, "no image on clipboard: {msg}"), PasteImageError::EncodeFailed(msg) => write!(f, "could not encode image: {msg}"), PasteImageError::IoError(msg) => write!(f, "io error: {msg}"), } } } impl std::error::Error for PasteImageError {} #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EncodedImageFormat { Png, } impl EncodedImageFormat { pub fn label(self) -> &'static str { match self { EncodedImageFormat::Png => "PNG", } } } #[derive(Debug, Clone)] pub struct PastedImageInfo { pub width: u32, pub height: u32, pub encoded_format: EncodedImageFormat, // Always PNG for now. } /// Capture image from system clipboard, encode to PNG, and return bytes + info. pub fn paste_image_as_png() -> Result<(Vec, PastedImageInfo), PasteImageError> { tracing::debug!("attempting clipboard image read"); let mut cb = arboard::Clipboard::new() .map_err(|e| PasteImageError::ClipboardUnavailable(e.to_string()))?; let img = cb .get_image() .map_err(|e| PasteImageError::NoImage(e.to_string()))?; let w = img.width as u32; let h = img.height as u32; let mut png: Vec = Vec::new(); let Some(rgba_img) = image::RgbaImage::from_raw(w, h, img.bytes.into_owned()) else { return Err(PasteImageError::EncodeFailed("invalid RGBA buffer".into())); }; let dyn_img = image::DynamicImage::ImageRgba8(rgba_img); tracing::debug!("clipboard image decoded RGBA {w}x{h}"); { let mut cursor = std::io::Cursor::new(&mut png); dyn_img .write_to(&mut cursor, image::ImageFormat::Png) .map_err(|e| PasteImageError::EncodeFailed(e.to_string()))?; } tracing::debug!( "clipboard image encoded to PNG ({len} bytes)", len = png.len() ); Ok(( png, PastedImageInfo { width: w, height: h, encoded_format: EncodedImageFormat::Png, }, )) } /// Convenience: write to a temp file and return its path + info. pub fn paste_image_to_temp_png() -> Result<(PathBuf, PastedImageInfo), PasteImageError> { let (png, info) = paste_image_as_png()?; // Create a unique temporary file with a .png suffix to avoid collisions. let tmp = Builder::new() .prefix("codex-clipboard-") .suffix(".png") .tempfile() .map_err(|e| PasteImageError::IoError(e.to_string()))?; std::fs::write(tmp.path(), &png).map_err(|e| PasteImageError::IoError(e.to_string()))?; // Persist the file (so it remains after the handle is dropped) and return its PathBuf. let (_file, path) = tmp .keep() .map_err(|e| PasteImageError::IoError(e.error.to_string()))?; Ok((path, info)) }