aweinstock314/rust-clipboard

Clipboard is cleared when the process exits on Linux

yaa110 opened this issue ยท 8 comments

Please consider the following code:

fn main() {
    let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
    ctx.set_contents("some string".to_owned()).unwrap();
    // Clipboard is "some string"
} // Clipboard is empty after exit

This is a known tradeoff/design decision around a limitation of x11. This was documented in a comment back when x11-clipboard lived in-tree (

// Under x11, "copying data into the clipboard" is actually
// accomplished by starting a "server" process that owns the
// copied data, and streams copied chunks of it through an
// event loop when another process requests it.
// xclip uses fork(2) to ensure that the clipboard "server"
// outlives the process that generated the data.
// Since there are potential complications of using fork(2)
// from rust (e.g. multiple calls of destructors), threads are
// used for now (until the complications are reviewed in more
// detail). As such, the clipboard "server" provided by
// X11ClipboardContext::set_contents will not outlive the calling
// process.
// X11ClipboardContextSetter is intended to be created {on the thread,
// in the process} that will be serving the clipboard data.
). I'll probably address this by copying that comment back in tree (and possibly adding the feature to create a clipboard context that forks, with a warning about the issues around locks/destructors).

I realize this might be out of scope, but is there a way of detecting when the contents of the clipboard have been accessed, from rust-clipboard? I want to write a tool to receive input from stdin and copy that to the clipboard. I'd be fine with the process hanging until the contents had been pasted out (in fact, that could be ideal), but I'm not sure how i'd know when that had occurred.

I guess this might require something like a variation on set_contents that would block until the contents had been read back out?

Thank you for addressing this in detail. As someone who is learning Rust, would you be able to give brief details on the problems involved with using fork?

This is a real pity as it renders the library essentially useless for my purposes.

Assuming this is a wontfix, I've created the clipboard-ext crate to extend upon this. It provides some additional clipboard providers for X11 that do implement the noted workarounds such as forking or invoking xclip/xsel.

For example, these will keep clipboard contents after exit:

use clipboard_ext::prelude::*;

// Fork and keep contents
clipboard_ext::x11_fork::ClipboardContext::new().unwrap()
    .set_contents("some string".into()).unwrap();

// Invoke xclip/xsel and keep contents
clipboard_ext::x11_bin::ClipboardContext::new().unwrap()
    .set_contents("some string".into()).unwrap();

See the README for more information.

I did a bit of digging to see how much work it would be to enable the paste detection functionality that I mention in #61 (comment), and it appears pretty nontrivial, since this package uses various lower-level libraries for the actual clipboard bits.

@brownjohnf I've done exactly this in clipboard-ext which I mentioned in #61 (comment).

You'd have to use the x11-clipboard crate for this which provides access to the lower level X11 library. This is alright though, because this is the only provider you'd need to implement this for anyway. It provides the load_wait function which blocks until clipboard content is changed.
See my implementation and usage here: https://github.com/timvisee/rust-clipboard-ext/blob/a47cc13807ca19bc2c815b07688a27235ad9b4f7/src/x11_fork.rs#L90-L107

Following up here with my own hack solution, which is to use the nix crate's fork functionality to spawn a temporary child:

match fork() {
    Ok(ForkResult::Child) => {
        let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
        ctx.set_contents(password.value.to_owned()).unwrap();

        std::thread::sleep(std::time::Duration::from_secs(10));

        // optionally clear the clipboard here
    }
    Err(_) => return Err("clipboard fork failed".into()),
    _ => {}
}

This only works on platforms that have a fork, of course.

Edit: This also shouldn't be done in library code. It's safe-ish in the context I intend to use it in, but not necessarily others.