Skip to main content

codetether_agent/tui/
clipboard.rs

1//! Clipboard image paste support for TUI.
2//!
3//! Reads image data from the system clipboard and converts it to an
4//! `ImageAttachment` suitable for sending with a chat message.
5
6use base64::Engine;
7use std::io::Cursor;
8
9use crate::session::ImageAttachment;
10
11/// Check if we're in an SSH or headless session without clipboard access.
12fn is_ssh_or_headless() -> bool {
13    std::env::var("SSH_CONNECTION").is_ok()
14        || std::env::var("SSH_TTY").is_ok()
15        || (std::env::var("TERM")
16            .ok()
17            .map_or(false, |t| t.starts_with("xterm"))
18            && std::env::var("DISPLAY").is_err()
19            && std::env::var("WAYLAND_DISPLAY").is_err())
20}
21
22/// Extract an image from the system clipboard, returning `None` when
23/// unavailable (SSH/headless, no clipboard, or no image content).
24pub fn get_clipboard_image() -> Option<ImageAttachment> {
25    if is_ssh_or_headless() {
26        return None;
27    }
28
29    let mut clipboard = arboard::Clipboard::new().ok()?;
30    let img_data = clipboard.get_image().ok()?;
31
32    let width = img_data.width;
33    let height = img_data.height;
34    let raw_bytes = img_data.bytes.into_owned();
35
36    let buf: image::ImageBuffer<image::Rgba<u8>, Vec<u8>> =
37        image::ImageBuffer::from_raw(width as u32, height as u32, raw_bytes)?;
38
39    let mut png_bytes = Vec::new();
40    buf.write_to(&mut Cursor::new(&mut png_bytes), image::ImageFormat::Png)
41        .ok()?;
42
43    let b64 = base64::engine::general_purpose::STANDARD.encode(&png_bytes);
44    Some(ImageAttachment {
45        data_url: format!("data:image/png;base64,{b64}"),
46        mime_type: Some("image/png".to_string()),
47    })
48}