Skip to main content

loopflow/engine/
clipboard.rs

1use std::io::Write;
2use std::process::{Command, Stdio};
3
4/// Read text from the system clipboard.
5///
6/// Returns `None` if the clipboard is empty or no clipboard tool is available.
7pub fn read() -> Option<String> {
8    let output = if cfg!(target_os = "macos") {
9        Command::new("pbpaste").output().ok()?
10    } else {
11        try_command("xclip", &["-selection", "clipboard", "-o"])
12            .or_else(|| try_command("xsel", &["--clipboard", "--output"]))
13            .or_else(|| try_command("wl-paste", &[]))?
14    };
15
16    if output.status.success() {
17        let text = String::from_utf8_lossy(&output.stdout).to_string();
18        if !text.trim().is_empty() {
19            return Some(text);
20        }
21    }
22    None
23}
24
25/// Write text to the system clipboard.
26pub fn write(text: &str) -> Result<(), std::io::Error> {
27    if cfg!(target_os = "macos") {
28        write_via("pbcopy", &[], text)
29    } else {
30        write_via("xclip", &["-selection", "clipboard"], text)
31            .or_else(|_| write_via("xsel", &["--clipboard", "--input"], text))
32            .or_else(|_| write_via("wl-copy", &[], text))
33    }
34}
35
36fn try_command(cmd: &str, args: &[&str]) -> Option<std::process::Output> {
37    Command::new(cmd).args(args).output().ok()
38}
39
40fn write_via(cmd: &str, args: &[&str], text: &str) -> Result<(), std::io::Error> {
41    let mut child = Command::new(cmd).args(args).stdin(Stdio::piped()).spawn()?;
42
43    if let Some(ref mut stdin) = child.stdin {
44        stdin.write_all(text.as_bytes())?;
45    }
46
47    let status = child.wait()?;
48    if !status.success() {
49        return Err(std::io::Error::other(format!("{cmd} exited with {status}")));
50    }
51    Ok(())
52}