use anyhow::{Context, Result};
use arboard::Clipboard;
use serde::{Deserialize, Serialize};
use std::io::Write;
use std::process::{Command, Stdio};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ClipboardMethod {
#[default]
Auto,
Xclip,
WlCopy,
Arboard,
}
fn is_flatpak() -> bool {
std::path::Path::new("/.flatpak-info").exists()
}
fn session_type() -> &'static str {
static SESSION_TYPE: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
SESSION_TYPE.get_or_init(|| {
std::env::var("XDG_SESSION_TYPE")
.map(|s| match s.to_lowercase().as_str() {
"x11" => "x11",
"wayland" => "wayland",
_ => "unknown",
})
.unwrap_or("unknown")
})
}
fn copy_via_wl_copy(text: &str) -> Result<()> {
crate::verbose!("Using wl-copy for clipboard");
let mut child = Command::new("wl-copy")
.stdin(Stdio::piped())
.spawn()
.context("Failed to spawn wl-copy")?;
if let Some(mut stdin) = child.stdin.take() {
stdin
.write_all(text.as_bytes())
.context("Failed to write to wl-copy")?;
}
let status = child.wait().context("Failed to wait for wl-copy")?;
if !status.success() {
anyhow::bail!("wl-copy exited with non-zero status");
}
crate::verbose!("wl-copy succeeded");
Ok(())
}
fn copy_via_xclip(text: &str) -> Result<()> {
crate::verbose!("Using xclip for clipboard");
let mut child = Command::new("xclip")
.args(["-selection", "clipboard"])
.stdin(Stdio::piped())
.spawn()
.context("Failed to spawn xclip. Install it with: sudo apt install xclip")?;
if let Some(mut stdin) = child.stdin.take() {
stdin
.write_all(text.as_bytes())
.context("Failed to write to xclip")?;
}
let status = child.wait().context("Failed to wait for xclip")?;
if !status.success() {
anyhow::bail!("xclip exited with non-zero status");
}
crate::verbose!("xclip succeeded");
Ok(())
}
fn copy_via_arboard(text: &str) -> Result<()> {
crate::verbose!("Using arboard for clipboard");
let mut clipboard = Clipboard::new().context("Failed to access clipboard")?;
clipboard
.set_text(text)
.context("Failed to copy text to clipboard")?;
crate::verbose!("arboard succeeded");
Ok(())
}
pub fn copy_to_clipboard(text: &str, method: ClipboardMethod) -> Result<()> {
crate::verbose!("Copying {} chars to clipboard", text.len());
crate::verbose!(
"Method: {:?}, Session: {}, Flatpak: {}",
method,
session_type(),
is_flatpak()
);
match method {
ClipboardMethod::Auto => {
if is_flatpak() {
return copy_via_wl_copy(text);
}
if session_type() == "x11" {
return copy_via_xclip(text);
}
copy_via_arboard(text)
}
ClipboardMethod::Xclip => copy_via_xclip(text),
ClipboardMethod::WlCopy => copy_via_wl_copy(text),
ClipboardMethod::Arboard => copy_via_arboard(text),
}
}