use crate::error::{AumateError, Result};
use arboard::Clipboard;
use image::ImageEncoder;
use std::sync::Mutex;
static CLIPBOARD: Mutex<Option<Clipboard>> = Mutex::new(None);
fn get_or_init_clipboard() -> Result<std::sync::MutexGuard<'static, Option<Clipboard>>> {
let mut guard =
CLIPBOARD.lock().map_err(|e| AumateError::Clipboard(format!("Lock error: {}", e)))?;
if guard.is_none() {
let clipboard = Clipboard::new().map_err(|e| {
AumateError::Clipboard(format!("Failed to initialize clipboard: {}", e))
})?;
*guard = Some(clipboard);
}
Ok(guard)
}
pub fn get_text() -> Result<String> {
let mut guard = get_or_init_clipboard()?;
let clipboard = guard
.as_mut()
.ok_or_else(|| AumateError::Clipboard("Clipboard not initialized".to_string()))?;
clipboard
.get_text()
.map_err(|e| AumateError::Clipboard(format!("Failed to get clipboard text: {}", e)))
}
pub fn set_text(text: &str) -> Result<()> {
let mut guard = get_or_init_clipboard()?;
let clipboard = guard
.as_mut()
.ok_or_else(|| AumateError::Clipboard("Clipboard not initialized".to_string()))?;
clipboard
.set_text(text.to_string())
.map_err(|e| AumateError::Clipboard(format!("Failed to set clipboard text: {}", e)))
}
pub fn get_image() -> Result<Vec<u8>> {
let mut guard = get_or_init_clipboard()?;
let clipboard = guard
.as_mut()
.ok_or_else(|| AumateError::Clipboard("Clipboard not initialized".to_string()))?;
let image_data = clipboard
.get_image()
.map_err(|e| AumateError::Clipboard(format!("Failed to get clipboard image: {}", e)))?;
let rgba_data = image_data.bytes.to_vec();
let width = image_data.width as u32;
let height = image_data.height as u32;
let img = image::RgbaImage::from_raw(width, height, rgba_data).ok_or_else(|| {
AumateError::Clipboard("Failed to create image from clipboard data".to_string())
})?;
let mut png_data = Vec::new();
let encoder = image::codecs::png::PngEncoder::new(&mut png_data);
encoder
.write_image(img.as_raw(), width, height, image::ExtendedColorType::Rgba8)
.map_err(|e| AumateError::Clipboard(format!("Failed to encode image as PNG: {}", e)))?;
Ok(png_data)
}
pub fn set_image(image_buffer: &[u8]) -> Result<()> {
let mut guard = get_or_init_clipboard()?;
let clipboard = guard
.as_mut()
.ok_or_else(|| AumateError::Clipboard("Clipboard not initialized".to_string()))?;
let img = image::load_from_memory(image_buffer)
.map_err(|e| AumateError::Clipboard(format!("Failed to decode image: {}", e)))?;
let rgba = img.to_rgba8();
let (width, height) = rgba.dimensions();
let image_data = arboard::ImageData {
width: width as usize,
height: height as usize,
bytes: rgba.into_raw().into(),
};
clipboard
.set_image(image_data)
.map_err(|e| AumateError::Clipboard(format!("Failed to set clipboard image: {}", e)))
}
pub fn set_image_raw(width: u32, height: u32, rgba_data: Vec<u8>) -> Result<()> {
let mut guard = get_or_init_clipboard()?;
let clipboard = guard
.as_mut()
.ok_or_else(|| AumateError::Clipboard("Clipboard not initialized".to_string()))?;
let image_data = arboard::ImageData {
width: width as usize,
height: height as usize,
bytes: rgba_data.into(),
};
clipboard
.set_image(image_data)
.map_err(|e| AumateError::Clipboard(format!("Failed to set clipboard image: {}", e)))
}
pub fn clear() -> Result<()> {
let mut guard = get_or_init_clipboard()?;
let clipboard = guard
.as_mut()
.ok_or_else(|| AumateError::Clipboard("Clipboard not initialized".to_string()))?;
clipboard
.clear()
.map_err(|e| AumateError::Clipboard(format!("Failed to clear clipboard: {}", e)))
}
#[cfg(test)]
mod tests {
use super::*;
fn has_display() -> bool {
std::env::var("DISPLAY").is_ok() || std::env::var("WAYLAND_DISPLAY").is_ok()
}
#[test]
fn test_clipboard_text() {
if !has_display() {
eprintln!("Skipping clipboard test: no display server available");
return;
}
let test_text = "Hello, clipboard!";
set_text(test_text).unwrap();
let retrieved = get_text().unwrap();
assert_eq!(retrieved, test_text);
}
#[test]
fn test_clear_clipboard() {
if !has_display() {
eprintln!("Skipping clipboard test: no display server available");
return;
}
set_text("test").unwrap();
clear().unwrap();
let _ = get_text();
}
}