diff --git a/examples/wait-once.rs b/examples/wait-once.rs new file mode 100644 index 00000000..82fff131 --- /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(()) +} diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index b9e4095a..a5840998 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 eb4dfe5d..3d73b0ae 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, }; @@ -60,6 +60,16 @@ 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) +} + fn handle_copy_error(e: copy::Error) -> Error { match e { CopyError::PrimarySelectionUnsupported => Error::ClipboardNotSupported, @@ -122,10 +132,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 +159,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 +216,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 +247,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()), diff --git a/src/platform/linux/x11.rs b/src/platform/linux/x11.rs index 17f107b2..25a82115 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();