use std::io::Write;
use std::process::{Command, Stdio};
use crate::i18n::trf;
pub fn copy(text: &str) -> Result<&'static str, String> {
for (bin, args) in [
("wl-copy", &[][..]),
("xclip", &["-selection", "clipboard"]),
] {
match pipe_to(bin, args, text) {
Ok(true) => return Ok(bin),
Ok(false) => continue, Err(e) => return Err(e),
}
}
osc52(text).map(|_| "osc52")
}
fn pipe_to(bin: &str, args: &[&str], text: &str) -> Result<bool, String> {
let child = Command::new(bin)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn();
let mut child = match child {
Ok(c) => c,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(e) => return Err(format!("{bin}: {e}")),
};
child
.stdin
.take()
.ok_or_else(|| trf("clip.no_stdin", &[("bin", bin)]))?
.write_all(text.as_bytes())
.map_err(|e| format!("{bin}: {e}"))?;
Ok(true)
}
fn osc52(text: &str) -> Result<(), String> {
let payload = b64(text.as_bytes());
let seq = format!("\x1b]52;c;{payload}\x07");
let out = if std::env::var_os("TMUX").is_some() {
format!("\x1bPtmux;{}\x1b\\", seq.replace('\x1b', "\x1b\x1b"))
} else {
seq
};
let mut stdout = std::io::stdout();
stdout
.write_all(out.as_bytes())
.map_err(|e| e.to_string())?;
stdout.flush().map_err(|e| e.to_string())
}
fn b64(data: &[u8]) -> String {
const T: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
for c in data.chunks(3) {
let b = [c[0], *c.get(1).unwrap_or(&0), *c.get(2).unwrap_or(&0)];
let n = ((b[0] as u32) << 16) | ((b[1] as u32) << 8) | b[2] as u32;
out.push(T[(n >> 18) as usize & 63] as char);
out.push(T[(n >> 12) as usize & 63] as char);
out.push(if c.len() > 1 {
T[(n >> 6) as usize & 63] as char
} else {
'='
});
out.push(if c.len() > 2 {
T[n as usize & 63] as char
} else {
'='
});
}
out
}
#[cfg(test)]
mod tests {
use super::b64;
#[test]
fn base64_matches_rfc4648_vectors() {
assert_eq!(b64(b""), "");
assert_eq!(b64(b"f"), "Zg==");
assert_eq!(b64(b"fo"), "Zm8=");
assert_eq!(b64(b"foo"), "Zm9v");
assert_eq!(b64(b"foob"), "Zm9vYg==");
assert_eq!(b64(b"fooba"), "Zm9vYmE=");
assert_eq!(b64(b"foobar"), "Zm9vYmFy");
}
#[test]
fn base64_handles_non_ascii_payloads() {
assert_eq!(b64("héllo".as_bytes()), "aMOpbGxv");
}
}