image dir

This commit is contained in:
2026-03-07 17:45:21 +01:00
parent ab2c25f8dc
commit ff3f8f94ef
2 changed files with 66 additions and 43 deletions

View File

@ -1,6 +1,6 @@
use arboard::{Clipboard, ImageData}; use arboard::{Clipboard, ImageData};
use image::codecs::png::PngEncoder; use image::codecs::png::PngEncoder;
use image::{ExtendedColorType, ImageEncoder, ImageReader}; use image::{EncodableLayout, ExtendedColorType, ImageEncoder, ImageReader, math};
use rand::{RngExt, distr::Alphanumeric}; use rand::{RngExt, distr::Alphanumeric};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fs::{self, File}; use std::fs::{self, File};
@ -20,7 +20,7 @@ pub struct ClipboardEntry {
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ClipboardData { pub enum ClipboardData {
Text(String), Text(String),
#[serde(with = "base64_vec")] // #[serde(with = "base64_vec")]
Image(Vec<u8>), Image(Vec<u8>),
} }
@ -47,28 +47,28 @@ impl ClipboardHandler for Handler {
// Wayland // Wayland
// Wayland end // Wayland end
mod base64_vec { // mod base64_vec {
use base64::{Engine as _, engine::general_purpose::STANDARD}; // use base64::{Engine as _, engine::general_purpose::STANDARD};
use serde::{Deserialize, Deserializer, Serializer}; // use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(v: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error> { // pub fn serialize<S: Serializer>(v: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error> {
let base64_str = STANDARD.encode(v); // let base64_str = STANDARD.encode(v);
serializer.serialize_str(&base64_str) // serializer.serialize_str(&base64_str)
} // }
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> { // pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
let base64_str = String::deserialize(deserializer)?; // let base64_str = String::deserialize(deserializer)?;
match STANDARD.decode(base64_str) { // match STANDARD.decode(base64_str) {
Ok(bytes) => Ok(bytes), // Ok(bytes) => Ok(bytes),
Err(error_base64) => { // Err(error_base64) => {
let error_serde = serde::de::Error::custom(error_base64); // let error_serde = serde::de::Error::custom(error_base64);
Err(error_serde) // Err(error_serde)
} // }
} // }
} // }
} // }
pub trait ImageDataExt { pub trait ImageDataExt {
fn to_png(&self) -> Result<Vec<u8>, Box<dyn Error>>; fn to_png(&self) -> Result<Vec<u8>, Box<dyn Error>>;
fn save_img(bytes: Vec<u8>) -> Result<(), Box<dyn Error>>; fn save_img(bytes: Vec<u8>, dir_path: &str) -> Result<(), Box<dyn Error>>;
} }
impl ImageDataExt for ImageData<'_> { impl ImageDataExt for ImageData<'_> {
@ -84,7 +84,7 @@ impl ImageDataExt for ImageData<'_> {
Ok(buffer) Ok(buffer)
} }
fn save_img(bytes: Vec<u8>) -> Result<(), Box<dyn Error>> { fn save_img(bytes: Vec<u8>, image_dir_path: &str) -> Result<(), Box<dyn Error>> {
let img = ImageReader::new(Cursor::new(&bytes)) let img = ImageReader::new(Cursor::new(&bytes))
.with_guessed_format()? .with_guessed_format()?
.decode()?; .decode()?;
@ -93,7 +93,7 @@ impl ImageDataExt for ImageData<'_> {
.take(16) .take(16)
.map(char::from) .map(char::from)
.collect(); .collect();
img.save(format!("clipboard/{}.png", name))?; img.save(format!("{}/{}.png", image_dir_path, name))?;
Ok(()) Ok(())
} }
} }
@ -129,29 +129,42 @@ impl ClipboardEntry {
}) })
} }
pub fn init(path: &str) -> Result<(), Box<dyn Error>> { pub fn init(dir_path: &str) -> Result<(), Box<dyn Error>> {
if !std::fs::exists(path)? { if !std::fs::exists(dir_path)? {
fs::create_dir(path)?; fs::create_dir(dir_path)?;
} else { } else {
println!("Dir already exists."); println!("{:?} dir already exists.", { dir_path });
} }
let file = Path::new(path).join("clipboard.json");
ClipboardEntry::new_json(file.to_str().unwrap())?; let image_path = format!("{}/images", dir_path);
if !std::fs::exists(&image_path)? {
fs::create_dir(&image_path)?;
} else {
println!("{:?} dir already exists.", { image_path });
}
let file_path = Path::new(dir_path).join("clipboard.json");
ClipboardEntry::new_json(file_path.to_str().unwrap())?;
Ok(()) Ok(())
} }
pub fn new_json(path: &str) -> Result<(), Box<dyn Error>> { pub fn new_json(file_path: &str) -> Result<(), Box<dyn Error>> {
if Path::new(path).exists() { if Path::new(file_path).exists() {
Err("File already exists.".into()) Err("File already exists.".into())
} else { } else {
File::create(path)?; File::create(file_path)?;
Ok(()) Ok(())
} }
} }
pub fn append_json(&self, path: &str) -> Result<(), Box<dyn Error>> { pub fn append_clipboard_history(
let mut entries: Vec<ClipboardEntry> = if Path::new(path).exists() { &self,
let data = fs::read_to_string(path)?; file_path: &str,
image_dir_path: &str,
) -> Result<(), Box<dyn Error>> {
let mut entries: Vec<ClipboardEntry> = if Path::new(file_path).exists() {
let data = fs::read_to_string(file_path)?;
if data.trim().is_empty() { if data.trim().is_empty() {
Vec::new() Vec::new()
} else { } else {
@ -160,17 +173,26 @@ impl ClipboardEntry {
} else { } else {
Vec::new() Vec::new()
}; };
entries.push(self.clone());
let json = serde_json::to_string_pretty(&entries)?; match &self.content {
fs::write(path, json)?; ClipboardData::Text(_) => {
entries.push(self.clone());
let json = serde_json::to_string_pretty(&entries)?;
fs::write(file_path, json)?;
}
ClipboardData::Image(image) => {
<ImageData<'_> as ImageDataExt>::save_img(image.to_vec(), image_dir_path)?;
}
}
Ok(()) Ok(())
} }
pub fn read_json(path: &str) -> Result<Vec<ClipboardEntry>, Box<dyn Error>> { pub fn read_text_history_json(file_path: &str) -> Result<Vec<ClipboardEntry>, Box<dyn Error>> {
if !Path::new(path).exists() { if !Path::new(file_path).exists() {
return Ok(Vec::new()); return Ok(Vec::new());
} }
let data = fs::read_to_string(path)?; let data = fs::read_to_string(file_path)?;
if data.trim().is_empty() { if data.trim().is_empty() {
return Ok(Vec::new()); return Ok(Vec::new());
} }

View File

@ -5,11 +5,12 @@ use std::error::Error;
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
// X11 // X11
#[cfg(feature = "x11")] // #[cfg(feature = "x11")]
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let mut clipboard = Clipboard::new()?; let mut clipboard = Clipboard::new()?;
let dir_path = "clipboard"; let dir_path = "clipboard";
let file_path = "clipboard/clipboard.json"; let file_path = "clipboard/clipboard.json";
let image_dir_path = "clipboard/images/";
ClipboardEntry::init(dir_path).unwrap_or(()); ClipboardEntry::init(dir_path).unwrap_or(());
let (tx, rx) = channel(); let (tx, rx) = channel();
@ -27,7 +28,7 @@ fn main() -> Result<(), Box<dyn Error>> {
for _ in rx { for _ in rx {
println!("Clipboard changed!"); println!("Clipboard changed!");
if let Ok(entry) = ClipboardEntry::new(&mut clipboard) { if let Ok(entry) = ClipboardEntry::new(&mut clipboard) {
if let Err(e) = entry.append_json(file_path) { if let Err(e) = entry.append_clipboard_history(file_path, image_dir_path) {
eprintln!("JSON writing error: {}", e); eprintln!("JSON writing error: {}", e);
} else { } else {
println!("JSON edited!"); println!("JSON edited!");