summaryrefslogtreecommitdiffstats
path: root/src/utils/clipboard.rs
blob: cb92bce6de41f156861060e6050e0b072498a6ff (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};

#[cfg(not(any(target_os = "android", target_os = "emscripten")))]
static CLIPBOARD: std::sync::LazyLock<std::sync::Mutex<Option<arboard::Clipboard>>> =
    std::sync::LazyLock::new(|| std::sync::Mutex::new(arboard::Clipboard::new().ok()));

/// Attempts to set text to clipboard with OSC52 escape sequence
/// Works in many modern terminals, including over SSH.
fn set_text_osc52(text: &str) -> anyhow::Result<()> {
    let encoded = BASE64.encode(text);
    let seq = format!("\x1b]52;c;{}\x07", encoded);
    // Write to stdout:
    if let Err(e) = std::io::Write::write_all(&mut std::io::stdout(), seq.as_bytes()) {
        return Err(anyhow::anyhow!("Failed to send OSC52 sequence").context(e));
    }
    // Flush stdout:
    if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) {
        return Err(anyhow::anyhow!("Failed to flush OSC52 sequence").context(e));
    }
    Ok(())
}

#[cfg(not(any(target_os = "android", target_os = "emscripten")))]
pub fn set_text(text: &str) -> anyhow::Result<()> {
    // First try arboard:
    let mut clipboard = CLIPBOARD.lock().unwrap();
    if let Some(clipboard) = clipboard.as_mut() {
        if let Ok(()) = clipboard.set_text(text) {
            #[cfg(target_os = "linux")]
            std::thread::sleep(std::time::Duration::from_millis(50));
            return Ok(());
        }
    }
    // If arboard failed, try OSC52:
    match set_text_osc52(text) {
        Ok(()) => Ok(()),
        Err(osc_err) => Err(anyhow::anyhow!("No clipboard available")
            .context(format!("Failed to copy (OSC52 error: {})", osc_err))),
    }
}

#[cfg(any(target_os = "android", target_os = "emscripten"))]
pub fn set_text(_text: &str) -> anyhow::Result<()> {
    Err(anyhow::anyhow!("No clipboard available").context("Failed to copy"))
}