use std::io::{self, Write};
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
pub(crate) fn osc52_sequence(text: &str, in_tmux: bool) -> String {
let encoded = STANDARD.encode(text.as_bytes());
let inner = format!("\x1b]52;c;{encoded}\x07");
if in_tmux {
let escaped = inner.replace('\x1b', "\x1b\x1b");
format!("\x1bPtmux;{escaped}\x1b\\")
} else {
inner
}
}
fn clipboard_candidates(
has_wayland: bool,
has_x11: bool,
is_macos: bool,
) -> Vec<(&'static str, &'static [&'static str])> {
let mut out: Vec<(&'static str, &'static [&'static str])> = Vec::new();
if has_wayland {
out.push(("wl-copy", &[]));
}
if has_x11 {
out.push(("xclip", &["-selection", "clipboard"]));
out.push(("xsel", &["--clipboard", "--input"]));
}
if is_macos {
out.push(("pbcopy", &[]));
}
out.push(("clip.exe", &[]));
out
}
#[cfg(not(test))]
fn try_external_clipboard(text: &str) -> bool {
use std::process::{Command, Stdio};
let has_wayland = std::env::var_os("WAYLAND_DISPLAY").is_some();
let has_x11 = std::env::var_os("DISPLAY").is_some();
let is_macos = cfg!(target_os = "macos");
for (cmd, args) in clipboard_candidates(has_wayland, has_x11, is_macos) {
let Ok(mut child) = Command::new(cmd)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
else {
continue;
};
let Some(mut stdin) = child.stdin.take() else {
continue;
};
if stdin.write_all(text.as_bytes()).is_ok() {
return true;
}
}
false
}
pub(crate) fn copy_to_clipboard(text: &str) {
if text.is_empty() {
return;
}
#[cfg(not(test))]
{
if try_external_clipboard(text) {
return;
}
}
let in_tmux = std::env::var_os("TMUX").is_some();
let seq = osc52_sequence(text, in_tmux);
let _ = io::stdout().write_all(seq.as_bytes());
let _ = io::stdout().flush();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builds_a_plain_osc52_sequence_outside_tmux() {
let seq = osc52_sequence("hi", false);
assert_eq!(seq, format!("\x1b]52;c;{}\x07", STANDARD.encode(b"hi")));
}
#[test]
fn wraps_the_sequence_in_a_tmux_dcs_passthrough_and_doubles_escapes() {
let seq = osc52_sequence("hi", true);
let inner = format!("\x1b]52;c;{}\x07", STANDARD.encode(b"hi"));
let expected = format!("\x1bPtmux;{}\x1b\\", inner.replace('\x1b', "\x1b\x1b"));
assert_eq!(seq, expected);
assert!(seq.starts_with("\x1bPtmux;"));
assert!(seq.ends_with("\x1b\\"));
}
#[test]
fn empty_text_still_produces_a_valid_sequence() {
let seq = osc52_sequence("", false);
assert_eq!(seq, "\x1b]52;c;\x07");
}
#[test]
fn wayland_is_tried_before_x11_tools_when_both_are_present() {
let c = clipboard_candidates(true, true, false);
assert_eq!(c[0].0, "wl-copy");
assert!(c.iter().any(|(name, _)| *name == "xclip"));
assert!(c.iter().any(|(name, _)| *name == "xsel"));
}
#[test]
fn x11_tools_are_offered_without_wayland() {
let c = clipboard_candidates(false, true, false);
assert_eq!(c[0].0, "xclip");
assert!(!c.iter().any(|(name, _)| *name == "wl-copy"));
}
#[test]
fn macos_offers_pbcopy() {
let c = clipboard_candidates(false, false, true);
assert!(c.iter().any(|(name, _)| *name == "pbcopy"));
}
#[test]
fn clip_exe_is_always_offered_as_a_last_resort_for_wsl() {
let c = clipboard_candidates(false, false, false);
assert_eq!(c, vec![("clip.exe", &[][..])]);
}
}