clipboard_anywhere/
lib.rs

1use std::{
2    env,
3    process::{Command, Stdio},
4};
5
6use anyhow::Result;
7use arboard::Clipboard;
8use duct::cmd;
9
10/// Copy text to the clipboard. Has special handling for WSL and SSH sessions, otherwise
11/// falls back to the cross-platform `clipboard` crate
12pub fn set_clipboard(text: &str) -> Result<()> {
13    if is_wsl::is_wsl() {
14        set_wsl_clipboard(text)?;
15    } else if env::var("SSH_CLIENT").is_ok() {
16        // we're in an SSH session, so set the clipboard using OSC 52 escape sequence
17        set_clipboard_osc_52(text);
18    } else {
19        // we're probably running on a host/primary OS, so use the default clipboard
20        match Clipboard::new() {
21            Ok(mut ctx) => {
22                if let Err(e) = ctx.set_text(text.to_string()) {
23                    anyhow::bail!("Failed to set clipboard: {e}");
24                }
25            }
26            Err(e) => anyhow::bail!("Failed to create clipboard context: {e}"),
27        }
28    }
29
30    Ok(())
31}
32
33pub fn get_clipboard() -> Result<String> {
34    if is_wsl::is_wsl() {
35        let stdout = cmd!("powershell.exe", "get-clipboard").read()?;
36        Ok(stdout.trim().to_string())
37    } else if env::var("SSH_CLIENT").is_ok() {
38        anyhow::bail!("SSH clipboard not supported");
39    } else {
40        // we're probably running on a host/primary OS, so use the default clipboard
41        match Clipboard::new() {
42            Ok(mut ctx) => match ctx.get_text() {
43                Ok(text) => Ok(text),
44                Err(e) => anyhow::bail!("Failed to get clipboard: {e}"),
45            },
46            Err(e) => anyhow::bail!("Failed to create clipboard context: {e}"),
47        }
48    }
49}
50
51/// Set the clipboard contents using OSC 52 (picked up by most terminals)
52fn set_clipboard_osc_52(text: &str) {
53    print!("\x1B]52;c;{}\x07", base64::encode(text));
54}
55
56/// Set the Windows clipboard using powershell.exe in WSL
57fn set_wsl_clipboard(s: &str) -> anyhow::Result<()> {
58    // In PowerShell, we can escape literal single-quotes
59    // in a single-quoted string by doubling them, e.g.
60    //
61    // 'hello ''world'''
62    //
63    // gets printed as
64    //
65    // hello 'world'
66    let escaped_s = s.replace("'", "''");
67
68    let mut powershell = Command::new("powershell.exe")
69        .arg("-NoProfile")
70        .arg("-Command")
71        .arg(&format!("Set-Clipboard -Value '{}'", escaped_s))
72        .stdout(Stdio::null())
73        .stderr(Stdio::null())
74        .spawn()?;
75
76    // Wait until the powershell process is finished before returning
77    powershell.wait()?;
78
79    Ok(())
80}