diff --git a/src/main.rs b/src/main.rs index 19551a5..be77b8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -627,6 +627,21 @@ fn run_loop( result } +/// Write text to the system clipboard via an OSC52 terminal escape sequence. +/// +/// Flow: base64-encode `text` -> wrap in `\x1b]52;c;\x07` -> write and +/// flush to `stdout`. +/// +/// Why: OSC52 asks the terminal emulator itself to set the clipboard, so no +/// OS-level clipboard library (X11/Wayland/win32) is needed. Terminals that +/// don't support it silently ignore the sequence. +fn write_osc52(stdout: &mut impl Write, text: &str) -> io::Result<()> { + use base64::Engine as _; + let b64 = base64::engine::general_purpose::STANDARD.encode(text); + write!(stdout, "\x1b]52;c;{b64}\x07")?; + stdout.flush() +} + /// The core single-process render/input loop. /// /// Flow: until `state.quit` → drain expired toasts → draw the frame → @@ -665,6 +680,13 @@ fn run_loop_inner( for action in actions { apply_action(state, action); } + if let Some(text) = state.misc.pending_clipboard_copy.take() { + let _ = write_osc52(&mut io::stdout(), &text); + state.push_toast(app::state::types::Toast::new( + app::state::types::ToastKind::Success, + "Copied to clipboard".to_string(), + )); + } } } Event::Paste(text) => { @@ -699,3 +721,18 @@ fn run_loop_inner( terminal.clear()?; Ok(()) } + +#[cfg(test)] +mod tests { + use super::write_osc52; + + #[test] + fn write_osc52_formats_the_escape_sequence() { + let mut buf: Vec = Vec::new(); + write_osc52(&mut buf, "hello").unwrap(); + use base64::Engine as _; + let b64 = base64::engine::general_purpose::STANDARD.encode("hello"); + let expected = format!("\x1b]52;c;{b64}\x07"); + assert_eq!(String::from_utf8(buf).unwrap(), expected); + } +}