From bcee192e500f39f91e1239d05f68927e2e293c8e Mon Sep 17 00:00:00 2001 From: zeefaad Date: Fri, 6 Mar 2026 18:33:47 +0100 Subject: [PATCH] signal json clipboard writing --- rklipd/Cargo.lock | 119 +++++++++++++++++++++++++++++++++++++++++++++ rklipd/Cargo.toml | 5 ++ rklipd/src/lib.rs | 68 ++++++++++++++++++++++---- rklipd/src/main.rs | 37 +++++++++++--- 4 files changed, 211 insertions(+), 18 deletions(-) diff --git a/rklipd/Cargo.lock b/rklipd/Cargo.lock index e5d5ec2..46e0456 100644 --- a/rklipd/Cargo.lock +++ b/rklipd/Cargo.lock @@ -160,6 +160,15 @@ dependencies = [ "core2", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "built" version = "0.8.0" @@ -202,6 +211,19 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "clipboard-master" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324ebf93a69bed78df929206f4475e7287fff609bebdfce199ea9dde4ce4e3d" +dependencies = [ + "objc2", + "objc2-app-kit", + "windows-win", + "x11-clipboard", + "x11rb", +] + [[package]] name = "clipboard-win" version = "5.4.1" @@ -672,8 +694,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags", + "block2", + "libc", "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "bitflags", + "objc2", "objc2-foundation", ] @@ -701,6 +754,41 @@ dependencies = [ "objc2-io-surface", ] +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -729,6 +817,17 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags", + "objc2", + "objc2-foundation", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -982,6 +1081,7 @@ version = "0.1.0" dependencies = [ "arboard", "base64", + "clipboard-master", "image", "serde", "serde_json", @@ -1261,6 +1361,15 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-win" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e23e33622b3b52f948049acbec9bcc34bf6e26d74176b88941f213c75cf2dc" +dependencies = [ + "error-code", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" @@ -1315,6 +1424,16 @@ version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +[[package]] +name = "x11-clipboard" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" +dependencies = [ + "libc", + "x11rb", +] + [[package]] name = "x11rb" version = "0.13.2" diff --git a/rklipd/Cargo.toml b/rklipd/Cargo.toml index a115434..1b5d5fc 100644 --- a/rklipd/Cargo.toml +++ b/rklipd/Cargo.toml @@ -10,3 +10,8 @@ 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" + +[features] +x11 = [] +wayland = [] diff --git a/rklipd/src/lib.rs b/rklipd/src/lib.rs index 58a730f..ccdb701 100644 --- a/rklipd/src/lib.rs +++ b/rklipd/src/lib.rs @@ -3,25 +3,43 @@ 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::sync::mpsc::Sender; use std::{error::Error, time::SystemTime}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct ClipboardEntry { pub content: ClipboardData, #[serde(with = "serde_millis")] pub timestamp: SystemTime, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum ClipboardData { Text(String), #[serde(with = "base64_vec")] Image(Vec), } +// X11 +use clipboard_master::{CallbackResult, ClipboardHandler}; + +pub struct Handler { + pub clipboard_tx: Sender<()>, +} + +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 + mod base64_vec { use base64::{Engine as _, engine::general_purpose::STANDARD}; use serde::{Deserialize, Deserializer, Serializer}; @@ -98,16 +116,46 @@ impl ClipboardEntry { 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())?; + + pub fn append_json(&self, path: &str) -> Result<(), Box> { + let mut entries: Vec = if Path::new(path).exists() { + let data = fs::read_to_string(path)?; + if data.trim().is_empty() { + Vec::new() + } else { + serde_json::from_str(&data)? + } + } else { + Vec::new() + }; + entries.push(self.clone()); + let json = serde_json::to_string_pretty(&entries)?; + fs::write(path, json)?; Ok(()) } - pub fn read_entry_json(path: &str) -> Result> { + pub fn read_json(path: &str) -> Result, Box> { + if !Path::new(path).exists() { + return Ok(Vec::new()); + } let data = fs::read_to_string(path)?; - let entry: ClipboardEntry = serde_json::from_str(&data)?; - Ok(entry) + if data.trim().is_empty() { + return Ok(Vec::new()); + } + let entries: Vec = serde_json::from_str(&data)?; + Ok(entries) } + + // pub fn write_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_json(path: &str) -> Result> { + // let data = fs::read_to_string(path)?; + // let entry: ClipboardEntry = serde_json::from_str(&data)?; + // Ok(entry) + // } } diff --git a/rklipd/src/main.rs b/rklipd/src/main.rs index 4ef86c0..440944b 100644 --- a/rklipd/src/main.rs +++ b/rklipd/src/main.rs @@ -1,17 +1,38 @@ use arboard::Clipboard; -use rklipd::ClipboardEntry; +use clipboard_master::Master; +use rklipd::{ClipboardEntry, Handler}; use std::error::Error; +use std::sync::mpsc::channel; +// X11 fn main() -> Result<(), Box> { let mut clipboard = Clipboard::new()?; - let entry = ClipboardEntry::new(&mut clipboard)?; let path = "clipboard.json"; - match ClipboardEntry::new_json(path) { - Ok(_) => println!("JSON file created {}", path), - Err(e) => println!("{}", e), + ClipboardEntry::new_json(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_json(path) { + eprintln!("JSON writing error: {}", e); + } else { + println!("JSON edited!"); + } + } } - entry.write_entry_json(path)?; - let loaded_entry = ClipboardEntry::read_entry_json(path)?; - println!("{:#?}", loaded_entry); + Ok(()) } +// X11