113 lines
3.3 KiB
Rust
113 lines
3.3 KiB
Rust
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<u8>),
|
|
}
|
|
|
|
mod base64_vec {
|
|
use serde::{Deserialize, Deserializer, Serializer};
|
|
pub fn serialize<S: Serializer>(v: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error> {
|
|
let base64_str = base64::encode(v);
|
|
serializer.serialize_str(&base64_str)
|
|
}
|
|
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, 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<Vec<u8>, Box<dyn Error>>;
|
|
}
|
|
|
|
impl ImageDataExt for ImageData<'_> {
|
|
fn to_png(&self) -> Result<Vec<u8>, Box<dyn Error>> {
|
|
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<ClipboardEntry, Box<dyn Error>> {
|
|
let clipboard_data_opt: Option<ClipboardData> = 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<dyn Error>> {
|
|
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<dyn Error>> {
|
|
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<Self, Box<dyn Error>> {
|
|
let data = fs::read_to_string(path)?;
|
|
let entry: ClipboardEntry = serde_json::from_str(&data)?;
|
|
Ok(entry)
|
|
}
|
|
}
|