diff --git a/Cargo.lock b/Cargo.lock index bd1c47f..ff1c5eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,7 @@ dependencies = [ "parking_lot", "percent-encoding", "windows-sys 0.60.2", + "wl-clipboard-rs", "x11rb", ] @@ -411,7 +412,7 @@ dependencies = [ "anyhow", "arrayvec", "log", - "nom", + "nom 8.0.0", "num-rational", "v_frame", ] @@ -1430,6 +1431,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flare" version = "0.1.0" @@ -2954,6 +2961,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -3109,6 +3122,16 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nom" version = "8.0.0" @@ -3671,6 +3694,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "owned_ttf_parser" version = "0.25.1" @@ -3727,6 +3760,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "phf" version = "0.13.1" @@ -5212,6 +5255,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tree_magic_mini" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f943391d896cdfe8eec03a04d7110332d445be7df856db382dd96a730667562c" +dependencies = [ + "memchr", + "nom 7.1.3", + "once_cell", + "petgraph", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -6510,6 +6565,25 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wl-clipboard-rs" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5ff8d0e60065f549fafd9d6cb626203ea64a798186c80d8e7df4f8af56baeb" +dependencies = [ + "libc", + "log", + "os_pipe", + "rustix 0.38.44", + "tempfile", + "thiserror 2.0.17", + "tree_magic_mini", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] + [[package]] name = "writeable" version = "0.6.2" diff --git a/Cargo.toml b/Cargo.toml index 0b02030..f81ca3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ phf = { version = "0.13.1", features = ["macros"] } rmp-serde = "1.3.0" dirs = "6.0.0" webbrowser = "1.0.6" -arboard = "3.6.1" +arboard = { version = "3.6.1", features = ["wayland-data-control"] } walkdir = "2.5.0" rayon = "1.11.0" which = "8.0.0" diff --git a/src/clipboard_history.rs b/src/clipboard_history.rs new file mode 100644 index 0000000..39a62bd --- /dev/null +++ b/src/clipboard_history.rs @@ -0,0 +1,135 @@ +use std::collections::VecDeque; +use std::fs; +use std::path::PathBuf; +use std::sync::{LazyLock, Mutex, Once}; +use std::thread; +use std::time::Duration; + +const MAX_HISTORY_SIZE: usize = 100; +const POLL_INTERVAL_MS: u64 = 500; +const HISTORY_FILE: &str = "clipboard_history.json"; + +static CLIPBOARD_HISTORY: LazyLock>> = + LazyLock::new(|| Mutex::new(VecDeque::new())); + +static LAST_CONTENT: LazyLock> = LazyLock::new(|| Mutex::new(String::new())); + +static INIT: Once = Once::new(); + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ClipboardEntry { + pub content: String, + pub timestamp: u64, +} + +fn get_history_path() -> PathBuf { + if let Some(data_dir) = dirs::data_dir() { + return data_dir.join("flare").join(HISTORY_FILE); + } + if let Some(home_dir) = dirs::home_dir() { + return home_dir.join(".flare").join(HISTORY_FILE); + } + PathBuf::from(HISTORY_FILE) +} + +fn load_history() -> VecDeque { + let path = get_history_path(); + if let Ok(data) = fs::read_to_string(&path) { + if let Ok(entries) = serde_json::from_str::>(&data) { + return VecDeque::from(entries); + } + } + VecDeque::new() +} + +fn save_history(history: &VecDeque) { + let path = get_history_path(); + if let Some(parent) = path.parent() { + let _ = fs::create_dir_all(parent); + } + + let entries: Vec<_> = history.iter().collect(); + if let Ok(data) = serde_json::to_string(&entries) { + let _ = fs::write(&path, data); + } +} + +fn add_entry(content: String) { + let mut history = CLIPBOARD_HISTORY.lock().unwrap(); + + if let Some(front) = history.front() { + if front.content == content { + return; + } + } + + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + + let entry = ClipboardEntry { content, timestamp }; + + history.push_front(entry); + + while history.len() > MAX_HISTORY_SIZE { + history.pop_back(); + } + + save_history(&history); +} + +fn poll_clipboard() { + loop { + let result = arboard::Clipboard::new().and_then(|mut c| c.get_text()); + + if let Ok(text) = result { + if !text.is_empty() { + let mut last = LAST_CONTENT.lock().unwrap(); + if *last != text { + *last = text.clone(); + drop(last); + add_entry(text); + } + } + } + + thread::sleep(Duration::from_millis(POLL_INTERVAL_MS)); + } +} + +pub fn init() { + INIT.call_once(|| { + let loaded = load_history(); + { + let mut history = CLIPBOARD_HISTORY.lock().unwrap(); + *history = loaded; + } + + if let Some(entry) = CLIPBOARD_HISTORY.lock().unwrap().front() { + let mut last = LAST_CONTENT.lock().unwrap(); + *last = entry.content.clone(); + } + + thread::spawn(poll_clipboard); + }); +} + +pub fn get_history() -> Vec { + let history = CLIPBOARD_HISTORY.lock().unwrap(); + history.iter().cloned().collect() +} + +pub fn clear_history() { + let mut history = CLIPBOARD_HISTORY.lock().unwrap(); + history.clear(); + save_history(&history); +} + +pub fn remove_entry(index: usize) { + let mut history = CLIPBOARD_HISTORY.lock().unwrap(); + if index < history.len() { + history.remove(index); + save_history(&history); + } +} diff --git a/src/main.rs b/src/main.rs index f3f7c2b..2118fb7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod apps; mod clipboard; +mod clipboard_history; mod components; mod deep_link; mod encryption; @@ -243,6 +244,8 @@ fn run_daemon() -> Result<(), Box> { eprintln!("Failed to register deep links: {}", e); } + clipboard_history::init(); + let flare_settings = preferences::FlareSettings::load(); let result = if flare_settings.use_layer_shell { @@ -291,6 +294,8 @@ fn run_daemon() -> Result<(), Box> { } } + clipboard_history::init(); + let result = iced::daemon(boot, update, daemon_view) .subscription(subscription) .title("Flare")