use std::io::Write;
use std::process::{Command, Stdio};
use std::sync::mpsc;
use std::time::Duration;
const COPY_TIMEOUT: Duration = Duration::from_secs(2);
pub fn copy(text: &str) -> Result<(), String> {
let owned = text.to_string();
let (tx, rx) = mpsc::channel::<Result<(), String>>();
std::thread::Builder::new()
.name("huddle-clipboard".into())
.spawn(move || {
let _ = tx.send(copy_blocking(&owned));
})
.map_err(|e| format!("clipboard thread spawn failed: {e}"))?;
match rx.recv_timeout(COPY_TIMEOUT) {
Ok(r) => r,
Err(mpsc::RecvTimeoutError::Timeout) => Err(format!(
"clipboard tool didn't respond within {}s — is the clipboard service running?",
COPY_TIMEOUT.as_secs()
)),
Err(mpsc::RecvTimeoutError::Disconnected) => {
Err("clipboard thread crashed".to_string())
}
}
}
fn copy_blocking(text: &str) -> Result<(), String> {
#[cfg(target_os = "macos")]
{
run_with_stdin("pbcopy", &[], text)
}
#[cfg(target_os = "linux")]
{
match run_with_stdin("wl-copy", &[], text) {
Ok(()) => Ok(()),
Err(_) => run_with_stdin("xclip", &["-selection", "clipboard"], text),
}
}
#[cfg(target_os = "windows")]
{
run_with_stdin("clip", &[], text)
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
{
let _ = text;
Err("clipboard not supported on this platform".to_string())
}
}
fn run_with_stdin(program: &str, args: &[&str], text: &str) -> Result<(), String> {
let mut child = match Command::new(program)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
{
Ok(c) => c,
Err(e) => {
tracing::debug!(error = %e, program, "clipboard tool not found");
return Err(format!("clipboard tool not found ({program})"));
}
};
if let Some(mut stdin) = child.stdin.take() {
if let Err(e) = stdin.write_all(text.as_bytes()) {
tracing::debug!(error = %e, program, "clipboard write failed");
return Err(format!("clipboard write failed: {e}"));
}
}
match child.wait() {
Ok(status) if status.success() => Ok(()),
Ok(status) => Err(format!("clipboard tool exited {status}")),
Err(e) => Err(format!("clipboard wait failed: {e}")),
}
}