use std::borrow::Cow;
use std::io::{Write, stdout};
use std::path::Path;
use base64::Engine;
use image::RgbaImage;
use tokio::sync::mpsc::UnboundedSender;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Copied {
System,
Terminal,
}
pub struct Decoded {
pub label: String,
pub image: Result<RgbaImage, String>,
}
pub struct Clipboard {
system: Option<arboard::Clipboard>,
tx: UnboundedSender<Decoded>,
}
impl Clipboard {
pub fn new(tx: UnboundedSender<Decoded>) -> Self {
Self { system: None, tx }
}
fn system(&mut self) -> Result<&mut arboard::Clipboard, arboard::Error> {
if self.system.is_none() {
self.system = Some(arboard::Clipboard::new()?);
}
Ok(self.system.as_mut().expect("just set"))
}
pub fn copy_text(&mut self, text: &str) -> std::io::Result<Copied> {
if let Ok(system) = self.system()
&& system.set_text(text).is_ok()
{
return Ok(Copied::System);
}
let mut out = stdout();
out.write_all(osc52(text).as_bytes())?;
out.flush()?;
Ok(Copied::Terminal)
}
pub fn copy_file(&mut self, path: &Path) -> Result<(), arboard::Error> {
self.system()?.set().file_list(&[path])
}
pub fn decode_image(&self, path: String, label: String) {
let tx = self.tx.clone();
tokio::task::spawn_blocking(move || {
let image = crate::images::open_image(&path)
.map(|i| i.to_rgba8())
.map_err(|e| e.to_string());
let _ = tx.send(Decoded { label, image });
});
}
pub fn copy_image(&mut self, image: RgbaImage) -> Result<(), arboard::Error> {
let (width, height) = image.dimensions();
self.system()?.set_image(arboard::ImageData {
width: width as usize,
height: height as usize,
bytes: Cow::Owned(image.into_raw()),
})
}
}
fn osc52(text: &str) -> String {
let encoded = base64::engine::general_purpose::STANDARD.encode(text);
format!("\x1b]52;c;{encoded}\x07")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_terminal_gets_the_text_in_base64() {
assert_eq!(osc52("hi ✓"), "\x1b]52;c;aGkg4pyT\x07");
}
}