use std::process::Command;
use std::thread::sleep;
use std::time::Duration;
#[derive(Debug, thiserror::Error)]
pub enum ClipboardError {
#[error("could not run {0}: {1}")]
Spawn(String, String),
#[error("{0} exited non-zero: {1}")]
Exit(String, String),
#[error("clipboard was empty after the copy step — selection likely failed")]
Empty,
#[error("clipboard contents were not valid UTF-8")]
NotUtf8,
}
pub fn copy_previous_word() -> Result<String, ClipboardError> {
wtype(&[
"-M", "ctrl", "-M", "shift", "-k", "Left", "-m", "shift", "-m", "ctrl",
])?;
sleep(Duration::from_millis(30));
wtype(&["-M", "ctrl", "-k", "c", "-m", "ctrl"])?;
sleep(Duration::from_millis(80));
let output = Command::new("wl-paste")
.arg("-n") .output()
.map_err(|e| ClipboardError::Spawn("wl-paste".into(), e.to_string()))?;
if !output.status.success() {
return Err(ClipboardError::Exit(
"wl-paste".into(),
String::from_utf8_lossy(&output.stderr).into_owned(),
));
}
let text = std::str::from_utf8(&output.stdout)
.map_err(|_| ClipboardError::NotUtf8)?
.to_string();
if text.is_empty() {
return Err(ClipboardError::Empty);
}
Ok(text)
}
pub fn type_replacement(text: &str) -> Result<(), ClipboardError> {
let output = Command::new("wtype")
.arg("--")
.arg(text)
.output()
.map_err(|e| ClipboardError::Spawn("wtype".into(), e.to_string()))?;
if !output.status.success() {
return Err(ClipboardError::Exit(
"wtype".into(),
String::from_utf8_lossy(&output.stderr).into_owned(),
));
}
Ok(())
}
fn wtype(args: &[&str]) -> Result<(), ClipboardError> {
let output = Command::new("wtype")
.args(args)
.output()
.map_err(|e| ClipboardError::Spawn("wtype".into(), e.to_string()))?;
if !output.status.success() {
return Err(ClipboardError::Exit(
"wtype".into(),
String::from_utf8_lossy(&output.stderr).into_owned(),
));
}
Ok(())
}