From d131fb33b574b8da060d95d169fef35dbafb81b6 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 2 Apr 2026 20:38:22 -0700 Subject: [PATCH 1/3] wayland: Factor out option setting into a method Each clipboard copy method does the same option setup; factor it out, to simplify further extension. --- src/platform/linux/wayland.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/platform/linux/wayland.rs b/src/platform/linux/wayland.rs index eb4dfe5..703628a 100644 --- a/src/platform/linux/wayland.rs +++ b/src/platform/linux/wayland.rs @@ -60,6 +60,13 @@ fn add_clipboard_exclusions(exclude_from_history: bool, sources: &mut Vec Result { + let mut opts = Options::new(); + opts.foreground(matches!(wait, WaitConfig::Forever)); + opts.clipboard(selection.try_into()?); + Ok(opts) +} + fn handle_copy_error(e: copy::Error) -> Error { match e { CopyError::PrimarySelectionUnsupported => Error::ClipboardNotSupported, @@ -122,10 +129,7 @@ impl Clipboard { wait: WaitConfig, exclude_from_history: bool, ) -> Result<(), Error> { - let mut opts = Options::new(); - opts.foreground(matches!(wait, WaitConfig::Forever)); - opts.clipboard(selection.try_into()?); - + let opts = options(wait, selection)?; let mut sources = Vec::with_capacity(if exclude_from_history { 2 } else { 1 }); sources.push(MimeSource { @@ -152,10 +156,7 @@ impl Clipboard { wait: WaitConfig, exclude_from_history: bool, ) -> Result<(), Error> { - let mut opts = Options::new(); - opts.foreground(matches!(wait, WaitConfig::Forever)); - opts.clipboard(selection.try_into()?); - + let opts = options(wait, selection)?; let mut sources = { let cap = [true, alt.is_some(), exclude_from_history] .map(|v| usize::from(v as u8)) @@ -212,10 +213,7 @@ impl Clipboard { wait: WaitConfig, exclude_from_history: bool, ) -> Result<(), Error> { - let mut opts = Options::new(); - opts.foreground(matches!(wait, WaitConfig::Forever)); - opts.clipboard(selection.try_into()?); - + let opts = options(wait, selection)?; let image = encode_as_png(&image)?; let mut sources = Vec::with_capacity(if exclude_from_history { 2 } else { 1 }); @@ -246,12 +244,8 @@ impl Clipboard { wait: WaitConfig, exclude_from_history: bool, ) -> Result<(), Error> { + let opts = options(wait, selection)?; let files = paths_to_uri_list(file_list)?; - - let mut opts = Options::new(); - opts.foreground(matches!(wait, WaitConfig::Forever)); - opts.clipboard(selection.try_into()?); - let mut sources = Vec::with_capacity(if exclude_from_history { 2 } else { 1 }); sources.push(MimeSource { source: Source::Bytes(files.into_bytes().into_boxed_slice()), From eb91f6cd05717f5331dd1815e24197333a5e40b8 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 2 Apr 2026 21:35:32 -0700 Subject: [PATCH 2/3] Add a `wait_once` to support waiting for just one clipboard request This is useful for programs that put something on the clipboard for single use, and want to exit as soon as it's pasted, rather than waiting for some amount of time. --- src/platform/linux/mod.rs | 15 +++++++++++++++ src/platform/linux/wayland.rs | 5 ++++- src/platform/linux/x11.rs | 23 +++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index b9e4095..a584099 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -212,6 +212,9 @@ pub(crate) enum WaitConfig { /// Waits until the given [`Instant`] has reached. Until(Instant), + /// Waits until the clipboard conents have been retrieved once. + Once, + /// Waits forever until a new event is reached. Forever, @@ -336,6 +339,13 @@ pub trait SetExtLinux: private::Sealed { /// that was previously set using it. fn wait_until(self, deadline: Instant) -> Self; + /// Whether to wait for the clipboard's content to be retrieved once after setting it. This + /// waits until the clipboard was retrieved once. + /// + /// Note: this is a superset of [`wait()`][SetExtLinux::wait] and will overwrite any state + /// that was previously set using it. + fn wait_once(self) -> Self; + /// Sets the clipboard the operation will store its data to. /// /// If wayland support is enabled and available, attempting to use the Secondary clipboard will @@ -382,6 +392,11 @@ impl SetExtLinux for crate::Set<'_> { self } + fn wait_once(mut self) -> Self { + self.platform.wait = WaitConfig::Once; + self + } + fn exclude_from_history(mut self) -> Self { self.platform.exclude_from_history = true; self diff --git a/src/platform/linux/wayland.rs b/src/platform/linux/wayland.rs index 703628a..3d73b0a 100644 --- a/src/platform/linux/wayland.rs +++ b/src/platform/linux/wayland.rs @@ -5,7 +5,7 @@ use std::{ }; use wl_clipboard_rs::{ - copy::{self, Error as CopyError, MimeSource, MimeType, Options, Source}, + copy::{self, Error as CopyError, MimeSource, MimeType, Options, ServeRequests, Source}, paste::{self, get_contents, Error as PasteError, Seat}, utils::is_primary_selection_supported, }; @@ -63,6 +63,9 @@ fn add_clipboard_exclusions(exclude_from_history: bool, sources: &mut Vec Result { let mut opts = Options::new(); opts.foreground(matches!(wait, WaitConfig::Forever)); + if matches!(wait, WaitConfig::Once) { + opts.serve_requests(ServeRequests::Only(1)); + } opts.clipboard(selection.try_into()?); Ok(opts) } diff --git a/src/platform/linux/x11.rs b/src/platform/linux/x11.rs index 17f107b..25a8211 100644 --- a/src/platform/linux/x11.rs +++ b/src/platform/linux/x11.rs @@ -185,6 +185,10 @@ struct Selection { /// /// This is associated with `Self::mutex`. data_changed: Condvar, + /// A condvar that is notified when the contents of this clipboard are served. + /// + /// This is associated with `Self::mutex`. + data_served: Condvar, } #[derive(Debug, Clone)] @@ -285,6 +289,11 @@ impl Inner { drop(data_guard); selection.data_changed.wait_until(&mut guard, deadline); } + + WaitConfig::Once => { + drop(data_guard); + selection.data_served.wait(&mut guard); + } } Ok(()) @@ -841,6 +850,9 @@ fn serve_requests(context: Arc) -> Result<(), Box> // reason. let _guard = selection.mutex.lock(); selection.data_changed.notify_all(); + // If a thread is waiting for the data to be served, + // there's no data to be served anymore, so wake it up. + selection.data_served.notify_all(); } } Event::SelectionRequest(event) => { @@ -855,6 +867,17 @@ fn serve_requests(context: Arc) -> Result<(), Box> continue; } + if let Some(selection) = context.kind_of(event.selection) { + let selection = context.selection_of(selection); + + // It is important that this mutex is locked at the time of calling + // `notify_all` to prevent notifications getting lost in case the sleeping + // thread has unlocked its `data_guard` and is just about to sleep. + let _guard = selection.mutex.lock(); + // Notify a thread that's waiting on the data to be served once. + selection.data_served.notify_all(); + } + // if we are in the progress of saving to the clipboard manager // make sure we save that we have finished writing let handover_state = context.handover_state.lock(); From a07ea71b3141a53b0ce4f7376f7c7eb503dca747 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 2 Apr 2026 22:07:36 -0700 Subject: [PATCH 3/3] Add example demonstrating `wait_once` --- examples/wait-once.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 examples/wait-once.rs diff --git a/examples/wait-once.rs b/examples/wait-once.rs new file mode 100644 index 0000000..82fff13 --- /dev/null +++ b/examples/wait-once.rs @@ -0,0 +1,26 @@ +//! Example showcasing the use of `wait_once`. + +use arboard::Clipboard; +#[cfg(all( + unix, + not(any(target_os = "macos", target_os = "android", target_os = "emscripten")) +))] +use arboard::SetExtLinux; + +fn main() -> Result<(), Box> { + env_logger::init(); + + let mut clipboard = Clipboard::new()?; + let mut set = clipboard.set(); + #[cfg(all( + unix, + not(any(target_os = "macos", target_os = "android", target_os = "emscripten")) + ))] + { + set = set.wait_once(); + eprintln!("Waiting for clipboard to be pasted once before exiting..."); + } + set.text("Hello, world!")?; + + Ok(()) +}