new
This commit is contained in:
1785
rklipd/Cargo.lock
generated
1785
rklipd/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -4,15 +4,3 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
arboard = "3.6.1"
|
||||
image = "0.25.9"
|
||||
serde = {version = "1.0.228", features = ["derive"]}
|
||||
serde_json = "1.0.149"
|
||||
serde_millis = "0.1.1"
|
||||
base64 = "0.22.1"
|
||||
clipboard-master = "4.0.0"
|
||||
rand = "0.10.0"
|
||||
|
||||
[features]
|
||||
x11 = []
|
||||
wayland = []
|
||||
|
||||
@ -1,202 +0,0 @@
|
||||
use arboard::{Clipboard, ImageData};
|
||||
use image::codecs::png::PngEncoder;
|
||||
use image::{EncodableLayout, ExtendedColorType, ImageEncoder, ImageReader, math};
|
||||
use rand::{RngExt, distr::Alphanumeric};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::{self, File};
|
||||
use std::io::Cursor;
|
||||
use std::path::Path;
|
||||
use std::result::Result;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::{error::Error, time::SystemTime};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ClipboardEntry {
|
||||
pub content: ClipboardData,
|
||||
#[serde(with = "serde_millis")]
|
||||
pub timestamp: SystemTime,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum ClipboardData {
|
||||
Text(String),
|
||||
// #[serde(with = "base64_vec")]
|
||||
Image(Vec<u8>),
|
||||
}
|
||||
|
||||
// X11
|
||||
#[cfg(feature = "x11")]
|
||||
use clipboard_master::{CallbackResult, ClipboardHandler};
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub struct Handler {
|
||||
pub clipboard_tx: Sender<()>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
impl ClipboardHandler for Handler {
|
||||
fn on_clipboard_change(&mut self) -> CallbackResult {
|
||||
if let Err(e) = self.clipboard_tx.send(()) {
|
||||
eprintln!("{}", e);
|
||||
}
|
||||
CallbackResult::Next
|
||||
}
|
||||
}
|
||||
// X11 end
|
||||
|
||||
// Wayland
|
||||
// Wayland end
|
||||
|
||||
// mod base64_vec {
|
||||
// use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
// use serde::{Deserialize, Deserializer, Serializer};
|
||||
// pub fn serialize<S: Serializer>(v: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
// let base64_str = STANDARD.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 STANDARD.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>>;
|
||||
fn save_img(bytes: Vec<u8>, dir_path: &str) -> Result<(), 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)
|
||||
}
|
||||
|
||||
fn save_img(bytes: Vec<u8>, image_dir_path: &str) -> Result<(), Box<dyn Error>> {
|
||||
let img = ImageReader::new(Cursor::new(&bytes))
|
||||
.with_guessed_format()?
|
||||
.decode()?;
|
||||
let name: String = rand::rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(16)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
img.save(format!("{}/{}.png", image_dir_path, name))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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 init(dir_path: &str) -> Result<(), Box<dyn Error>> {
|
||||
if !std::fs::exists(dir_path)? {
|
||||
fs::create_dir(dir_path)?;
|
||||
} else {
|
||||
println!("{:?} dir already exists.", { dir_path });
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
pub fn new_json(file_path: &str) -> Result<(), Box<dyn Error>> {
|
||||
if Path::new(file_path).exists() {
|
||||
Err("File already exists.".into())
|
||||
} else {
|
||||
File::create(file_path)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_clipboard_history(
|
||||
&self,
|
||||
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() {
|
||||
Vec::new()
|
||||
} else {
|
||||
serde_json::from_str(&data)?
|
||||
}
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
match &self.content {
|
||||
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(())
|
||||
}
|
||||
|
||||
pub fn read_text_history_json(file_path: &str) -> Result<Vec<ClipboardEntry>, Box<dyn Error>> {
|
||||
if !Path::new(file_path).exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let data = fs::read_to_string(file_path)?;
|
||||
if data.trim().is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let entries: Vec<ClipboardEntry> = serde_json::from_str(&data)?;
|
||||
Ok(entries)
|
||||
}
|
||||
}
|
||||
@ -1,46 +1,3 @@
|
||||
use arboard::Clipboard;
|
||||
use clipboard_master::Master;
|
||||
use rklipd::{ClipboardEntry, Handler};
|
||||
use std::error::Error;
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
// X11
|
||||
// #[cfg(feature = "x11")]
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut clipboard = Clipboard::new()?;
|
||||
let dir_path = "clipboard";
|
||||
let file_path = "clipboard/clipboard.json";
|
||||
let image_dir_path = "clipboard/images/";
|
||||
ClipboardEntry::init(dir_path).unwrap_or(());
|
||||
|
||||
let (tx, rx) = channel();
|
||||
|
||||
let mut master = Master::new(Handler { clipboard_tx: tx })?;
|
||||
// let shutdown = master.shutdown_channel();
|
||||
std::thread::spawn(move || {
|
||||
if let Err(e) = master.run() {
|
||||
eprintln!("Clipboard monitor error : {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
println!("Monitoring clipboard X11...");
|
||||
|
||||
for _ in rx {
|
||||
println!("Clipboard changed!");
|
||||
if let Ok(entry) = ClipboardEntry::new(&mut clipboard) {
|
||||
if let Err(e) = entry.append_clipboard_history(file_path, image_dir_path) {
|
||||
eprintln!("JSON writing error: {}", e);
|
||||
} else {
|
||||
println!("JSON edited!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// X11
|
||||
|
||||
#[cfg(not(feature = "x11"))]
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user