Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions examples/wait-once.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error + Send + Sync>> {
env_logger::init();

let mut clipboard = Clipboard::new()?;
let mut set = clipboard.set();

Check failure on line 14 in examples/wait-once.rs

View workflow job for this annotation

GitHub Actions / test (macos-latest)

variable does not need to be mutable
#[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(())
}
15 changes: 15 additions & 0 deletions src/platform/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
33 changes: 15 additions & 18 deletions src/platform/linux/wayland.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -60,6 +60,16 @@ fn add_clipboard_exclusions(exclude_from_history: bool, sources: &mut Vec<MimeSo
}
}

fn options(wait: WaitConfig, selection: LinuxClipboardKind) -> Result<Options, Error> {
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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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))
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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()),
Expand Down
23 changes: 23 additions & 0 deletions src/platform/linux/x11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -841,6 +850,9 @@ fn serve_requests(context: Arc<Inner>) -> Result<(), Box<dyn std::error::Error>>
// 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) => {
Expand All @@ -855,6 +867,17 @@ fn serve_requests(context: Arc<Inner>) -> Result<(), Box<dyn std::error::Error>>
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();
Expand Down
Loading