Forward terminal input bytes without cursor rewriting
ci / test (push) Has been cancelled
ci / remote-bench (push) Has been cancelled

This commit is contained in:
DuProcess
2026-06-11 22:22:03 -04:00
parent 6580e75fbf
commit fd7849f889
+1 -74
View File
@@ -605,7 +605,6 @@ async fn run_terminal(
let mut status_tick = tokio::time::interval(Duration::from_secs(1)); let mut status_tick = tokio::time::interval(Duration::from_secs(1));
let mut resize_tick = tokio::time::interval(Duration::from_millis(250)); let mut resize_tick = tokio::time::interval(Duration::from_millis(250));
let mut last_size = size().unwrap_or((80, 24)); let mut last_size = size().unwrap_or((80, 24));
let mut input_normalizer = InputNormalizer::default();
if let Some(frame) = first_frame { if let Some(frame) = first_frame {
render_frame(&frame)?; render_frame(&frame)?;
if frame.closed { if frame.closed {
@@ -642,7 +641,6 @@ async fn run_terminal(
break; break;
} }
if cred.mode != "view-only" { if cred.mode != "view-only" {
let bytes = input_normalizer.normalize(&bytes);
let body = protocol::to_body(&Input { bytes })?; let body = protocol::to_body(&Input { bytes })?;
let packet = protocol::encode_encrypted( let packet = protocol::encode_encrypted(
PacketKind::Input, PacketKind::Input,
@@ -725,54 +723,6 @@ async fn run_terminal(
Ok(()) Ok(())
} }
#[derive(Default)]
struct InputNormalizer {
state: InputNormalizerState,
}
#[derive(Default)]
enum InputNormalizerState {
#[default]
Ground,
Esc,
Ss3,
}
impl InputNormalizer {
fn normalize(&mut self, bytes: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(bytes.len());
for &byte in bytes {
match self.state {
InputNormalizerState::Ground => {
out.push(byte);
if byte == 0x1b {
self.state = InputNormalizerState::Esc;
}
}
InputNormalizerState::Esc => {
if byte == b'O' {
self.state = InputNormalizerState::Ss3;
} else {
out.push(byte);
self.state = InputNormalizerState::Ground;
}
}
InputNormalizerState::Ss3 => {
if matches!(byte, b'A'..=b'D') {
out.push(b'[');
out.push(byte);
} else {
out.push(b'O');
out.push(byte);
}
self.state = InputNormalizerState::Ground;
}
}
}
out
}
}
async fn send_resize( async fn send_resize(
socket: &UdpSocket, socket: &UdpSocket,
addr: SocketAddr, addr: SocketAddr,
@@ -916,30 +866,7 @@ const TERMINAL_CLEANUP: &[u8] = concat!(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{InputNormalizer, parse_ssh_config_hostname, ssh_destination_host}; use super::{parse_ssh_config_hostname, ssh_destination_host};
#[test]
fn normalizes_ss3_cursor_keys_to_csi() {
let mut normalizer = InputNormalizer::default();
assert_eq!(normalizer.normalize(b"\x1bOA"), b"\x1b[A");
assert_eq!(normalizer.normalize(b"\x1bOB"), b"\x1b[B");
assert_eq!(normalizer.normalize(b"\x1bOC"), b"\x1b[C");
assert_eq!(normalizer.normalize(b"\x1bOD"), b"\x1b[D");
}
#[test]
fn preserves_split_ss3_cursor_state_between_reads() {
let mut normalizer = InputNormalizer::default();
assert_eq!(normalizer.normalize(b"\x1b"), b"\x1b");
assert_eq!(normalizer.normalize(b"O"), b"");
assert_eq!(normalizer.normalize(b"B"), b"[B");
}
#[test]
fn leaves_non_cursor_ss3_sequences_raw() {
let mut normalizer = InputNormalizer::default();
assert_eq!(normalizer.normalize(b"\x1bOP"), b"\x1bOP");
}
#[test] #[test]
fn parses_ssh_config_hostname() { fn parses_ssh_config_hostname() {