aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorakbash <akbash@herdr.dev>2026-09-02 01:02:25 +0300
committerGitHub <noreply@github.com>2026-09-02 00:02:25 +0200
commit0032c3b42751b6da9c5b1a91546b3c1a425d67f1 (patch)
treefc3923b64e7ddd3deec43ae540e2ecae7c71a746 /src
parent99c23cd1ea7468bd3661f6483c7105396503b417 (diff)
fix(windows): preserve remote multiline paste (#3282)
refs #3209 Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
Diffstat (limited to 'src')
-rw-r--r--src/client/input/windows_vti.rs65
1 files changed, 59 insertions, 6 deletions
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<crate::raw_input::RawInputEvent>,
+ mut events: Vec<crate::raw_input::RawInputEvent>,
) -> Vec<crate::protocol::ClientInputEvent> {
- 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 {
@@ -1368,6 +1378,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~");
records.extend(win32_input_mode_encoded_record(WindowsKeyRecord {