From e3e6388ac3bc027407ca4e72da35f9a80656121e Mon Sep 17 00:00:00 2001 From: gcv Date: Thu, 8 May 2025 17:17:24 -0700 Subject: feat: add support for OSC52 clipboard codes (#1280) --- src/utils/clipboard.rs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/utils/clipboard.rs b/src/utils/clipboard.rs index 12e1655..cb92bce 100644 --- a/src/utils/clipboard.rs +++ b/src/utils/clipboard.rs @@ -1,19 +1,42 @@ +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::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(); - match clipboard.as_mut() { - Some(clipboard) => { - clipboard.set_text(text)?; + 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(()); } - None => return Err(anyhow::anyhow!("No clipboard available").context("Failed to copy")), } - 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"))] -- cgit v1.2.3