diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-20 02:51:59 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-20 02:51:59 +0300 |
| commit | ed478be9835e984c42bc1e30fe84b5fb01a8c5d4 (patch) | |
| tree | 353b6ea5c7368a9f3b477bc8f1bbcd248e377577 /src/client | |
| parent | 2b97799b6715fd6c355db9c225029765971915e6 (diff) | |
fix: bridge remote clipboard image paste
refs #205
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/mod.rs | 58 |
1 files changed, 56 insertions, 2 deletions
diff --git a/src/client/mod.rs b/src/client/mod.rs index 101e168c..ff5c144f 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -32,8 +32,8 @@ use tracing::{debug, info, warn}; use crate::server::headless::client_socket_path; use crate::server::protocol::{ - self, ClientMessage, NotifyKind, RenderEncoding, ServerMessage, MAX_FRAME_SIZE, - MAX_GRAPHICS_FRAME_SIZE, PROTOCOL_VERSION, + self, ClientMessage, NotifyKind, RenderEncoding, ServerMessage, MAX_CLIPBOARD_IMAGE_PAYLOAD, + MAX_FRAME_SIZE, MAX_GRAPHICS_FRAME_SIZE, PROTOCOL_VERSION, }; static RECEIVED_KITTY_GRAPHICS_IDS: OnceLock<Mutex<HashSet<u32>>> = OnceLock::new(); @@ -640,6 +640,34 @@ async fn run_client_loop( } data }; + if should_bridge_clipboard_image_paste(&data) { + if let Some(image) = crate::platform::read_clipboard_image() { + if image.bytes.len() > MAX_CLIPBOARD_IMAGE_PAYLOAD { + warn!( + bytes = image.bytes.len(), + max = MAX_CLIPBOARD_IMAGE_PAYLOAD, + "local clipboard image is too large to bridge" + ); + continue; + } + info!( + bytes = image.bytes.len(), + extension = image.extension, + "bridging local clipboard image paste to remote server" + ); + let msg = ClientMessage::ClipboardImage { + extension: image.extension.to_owned(), + data: image.bytes, + }; + if let Err(e) = write_to_server(&mut write_stream, &msg) { + return Err(ClientError::ConnectionLost(e)); + } + continue; + } + info!( + "clipboard image paste trigger received, but local clipboard has no image" + ); + } let msg = ClientMessage::Input { data }; if let Err(e) = write_to_server(&mut write_stream, &msg) { return Err(ClientError::ConnectionLost(e)); @@ -875,6 +903,21 @@ fn sound_from_notify_message(message: &str) -> Option<crate::sound::Sound> { } } +fn should_bridge_clipboard_image_paste(data: &[u8]) -> bool { + if data == b"\x1b[200~\x1b[201~" { + return true; + } + + let events = crate::raw_input::parse_raw_input_bytes_sync(data); + matches!( + events.as_slice(), + [crate::raw_input::RawInputEvent::Key(key)] + if key.kind == crossterm::event::KeyEventKind::Press + && key.modifiers == crossterm::event::KeyModifiers::CONTROL + && matches!(key.code, crossterm::event::KeyCode::Char('v' | 'V')) + ) +} + // --------------------------------------------------------------------------- // Clipboard forwarding // --------------------------------------------------------------------------- @@ -1069,6 +1112,17 @@ mod tests { use super::*; #[test] + fn clipboard_image_paste_bridge_triggers_on_ctrl_v_and_empty_paste() { + assert!(should_bridge_clipboard_image_paste(&[0x16])); + assert!(should_bridge_clipboard_image_paste(b"\x1b[118;5u")); + assert!(should_bridge_clipboard_image_paste(b"\x1b[200~\x1b[201~")); + assert!(!should_bridge_clipboard_image_paste( + b"\x1b[200~text\x1b[201~" + )); + assert!(!should_bridge_clipboard_image_paste(b"v")); + } + + #[test] fn graphics_bytes_are_written_after_blit_with_saved_cursor() { let mut output = Vec::new(); write_encoded_frame_with_graphics( |
