From 0032c3b42751b6da9c5b1a91546b3c1a425d67f1 Mon Sep 17 00:00:00 2001 From: akbash Date: Wed, 2 Sep 2026 01:02:25 +0300 Subject: fix(windows): preserve remote multiline paste (#3282) refs #3209 Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com> --- src/client/input/windows_vti.rs | 65 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) (limited to 'src/client/input') diff --git a/src/client/input/windows_vti.rs b/src/client/input/windows_vti.rs index 2f469161..ce322d27 100644 --- a/src/client/input/windows_vti.rs +++ b/src/client/input/windows_vti.rs @@ -334,13 +334,13 @@ impl WindowsInputPump { fn process_raw_events( &mut self, - events: Vec, + mut events: Vec, ) -> Vec { - if events - .iter() - .any(|event| matches!(event, crate::raw_input::RawInputEvent::Paste(_))) - { - self.paste_from_win32_key_records = false; + for event in &mut events { + if let crate::raw_input::RawInputEvent::Paste(text) = event { + decode_windows_terminal_paste_enters(text); + self.paste_from_win32_key_records = false; + } } Self::raw_events_to_client_events(events) } @@ -387,6 +387,16 @@ impl PlatformInputItem { } } +fn decode_windows_terminal_paste_enters(text: &mut String) { + const ENTER_REPORT_PAIR: &str = "\x1b[13;28;13;1;0;1_\x1b[13;28;13;0;0;1_"; + + // Windows Terminal can encode pasted newlines as an adjacent unmodified + // Enter press/release pair. Keep every other report-shaped payload opaque. + if text.contains(ENTER_REPORT_PAIR) { + *text = text.replace(ENTER_REPORT_PAIR, "\r"); + } +} + #[cfg(test)] #[derive(Default)] struct WindowsInputTranslator { @@ -1367,6 +1377,49 @@ mod tests { ); } + #[test] + fn vti_remote_bracketed_paste_decodes_reported_enter_records() { + let records = concat!( + "\x1b[200~ - line one", + "\x1b[13;28;13;1;0;1_\x1b[13;28;13;0;0;1_", + " - line two", + "\x1b[13;28;13;1;0;1_\x1b[13;28;13;0;0;1_", + " - line three\x1b[201~", + ) + .chars() + .map(key_char); + + assert_eq!( + translate(records), + vec![crate::protocol::ClientInputEvent::Paste { + text: " - line one\r - line two\r - line three".into(), + }] + ); + } + + #[test] + fn vti_remote_paste_keeps_incomplete_enter_report_pairs_opaque() { + let records = concat!( + "\x1b[200~before", + "\x1b[13;28;13;1;0;1_middle\x1b[13;28;13;0;0;1_", + "after\x1b[201~", + ) + .chars() + .map(key_char); + + assert_eq!( + translate(records), + vec![crate::protocol::ClientInputEvent::Paste { + text: concat!( + "before", + "\x1b[13;28;13;1;0;1_middle\x1b[13;28;13;0;0;1_", + "after", + ) + .into(), + }] + ); + } + #[test] fn vti_win32_input_mode_decoded_paste_handles_shift_repeats_and_releases() { let mut records = win32_input_mode_encoded_key_bytes(b"\x1b[200~"); -- cgit v1.2.3