use arboard::{Clipboard, ImageData}; use image::codecs::png::PngEncoder; use image::{ExtendedColorType, ImageEncoder}; use serde::{Deserialize, Serialize}; use std::fs::{self, File}; use std::io::Write; use std::path::Path; use std::result::Result; use std::{error::Error, time::SystemTime}; #[derive(Serialize, Deserialize, Debug)] pub struct ClipboardEntry { pub content: ClipboardData, #[serde(with = "serde_millis")] pub timestamp: SystemTime, } #[derive(Serialize, Deserialize, Debug)] pub enum ClipboardData { Text(String), #[serde(with = "base64_vec")] Image(Vec), } mod base64_vec { use serde::{Deserialize, Deserializer, Serializer}; pub fn serialize(v: &Vec, serializer: S) -> Result { let base64_str = base64::encode(v); serializer.serialize_str(&base64_str) } pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { let base64_str = String::deserialize(deserializer)?; match base64::decode(base64_str) { Ok(bytes) => Ok(bytes), Err(error_base64) => { let error_serde = serde::de::Error::custom(error_base64); Err(error_serde) } } } } pub trait ImageDataExt { fn to_png(&self) -> Result, Box>; } impl ImageDataExt for ImageData<'_> { fn to_png(&self) -> Result, Box> { let mut buffer = Vec::new(); let encoder = PngEncoder::new(&mut buffer); encoder.write_image( &self.bytes, self.width as u32, self.height as u32, ExtendedColorType::Rgba8, )?; Ok(buffer) } } impl ClipboardData { pub fn is_text(&self) -> bool { match self { ClipboardData::Text(_) => true, ClipboardData::Image(_) => false, } } pub fn is_image(&self) -> bool { !self.is_text() } } impl ClipboardEntry { pub fn new(clipboard: &mut Clipboard) -> Result> { let clipboard_data_opt: Option = match clipboard.get_text() { Ok(text) => Some(ClipboardData::Text(text)), Err(_) => match clipboard.get_image() { Ok(image) => Some(ClipboardData::Image(image.to_png()?)), Err(_) => None, }, }; let Some(clipboard_data) = clipboard_data_opt else { return Err("Clipboard empty".into()); }; Ok(ClipboardEntry { content: clipboard_data, timestamp: SystemTime::now(), }) } pub fn new_json(path: &str) -> Result<(), Box> { if Path::new(path).exists() { Err("File already exists.".into()) } else { File::create(path)?; Ok(()) } } pub fn write_entry_json(&self, path: &str) -> Result<(), Box> { let json = serde_json::to_string_pretty(self)?; let mut file = File::create(path)?; file.write_all(json.as_bytes())?; Ok(()) } pub fn read_entry_json(path: &str) -> Result> { let data = fs::read_to_string(path)?; let entry: ClipboardEntry = serde_json::from_str(&data)?; Ok(entry) } }