diff --git a/echo_erase_kill_behavior b/echo_erase_kill_behavior new file mode 100644 index 0000000..d94b716 --- /dev/null +++ b/echo_erase_kill_behavior @@ -0,0 +1,14 @@ +echoctl: Control echoes give literal ^ + printable +-echoctl: Control echoes give raw control character + +-echoe -echoprt: Backspace gives ^? +echoe -echoprt: Backspace gives character erase +(-)echoe echoprt: Backspace gives \ if last press wasn't Backspce, then erased characters, and / when non-backspace pressed or end of line erased + + +crtkill (-)echoe -echok: Kill gives ^U +crtkill -echoe echok: Kill gives ^U and newline (N_TTY comment says no newline, bug?) +crtkill echoe echok: Kill erases line with backspace + +-crtkill -echok: Kill gives ^U +-crtkill echok: Kill gives ^U and newline diff --git a/src/main.rs b/src/main.rs index 4efe3a9..5c01215 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use std::{ os::mikros::{ipc, syscalls}, path::{Path, PathBuf}, sync::{ - atomic::{AtomicBool, Ordering}, + atomic::{AtomicBool, AtomicUsize, Ordering}, Arc, }, }; @@ -20,6 +20,7 @@ struct Pty { output_buffer: Mutex>, termios: Termios, output_paused: AtomicBool, + column: AtomicUsize, } impl Pty { @@ -29,6 +30,7 @@ impl Pty { output_buffer: Mutex::new(Vec::new()), termios: Termios::default(), output_paused: AtomicBool::new(false), + column: AtomicUsize::new(0), } } @@ -47,43 +49,60 @@ impl Pty { if !self.termios.cflags.cread { return Ok(()); } - let mut input_buffer = self.input_buffer.lock(); - let mut output_buffer = self.output_buffer.lock(); - for &(mut byte) in data { + for &(mut ch) in data { if self.termios.iflags.istrip { - byte &= 0x7F; + ch &= 0x7F; } - if byte == b'\r' && self.termios.iflags.igncr { + if ch == b'\r' && self.termios.iflags.igncr { continue; - } else if byte == b'\r' && self.termios.iflags.icrnl { - byte = b'\n'; - } else if byte == b'\n' && self.termios.iflags.inlcr { - byte = b'\r'; + } else if ch == b'\r' && self.termios.iflags.icrnl { + ch = b'\n'; + } else if ch == b'\n' && self.termios.iflags.inlcr { + ch = b'\r'; } - if byte as char == self.termios.verase && self.termios.lflags.icanon { - if self.termios.lflags.echoe { - output_buffer.push(byte); - } - if input_buffer.last() != Some(&b'\n') { - input_buffer.pop(); - } - } else if byte as char == self.termios.vkill && self.termios.lflags.icanon { - while input_buffer.last().is_some() && input_buffer.last() != Some(&b'\n') { - if self.termios.lflags.echok { - output_buffer.push(8); + if ch as char == self.termios.verase && self.termios.lflags.icanon { + let buf_last = self.input_buffer.lock().last().copied(); + if let Some(erased_ch) = buf_last { + let erased_ch = erased_ch as char; + if erased_ch != '\n' { + self.input_buffer.lock().pop(); + self.erase_char(erased_ch); } - input_buffer.pop(); } - } else if byte as char == self.termios.vstop && self.termios.iflags.ixon { + } else if ch as char == self.termios.vkill && self.termios.lflags.icanon { + let buf_last = self.input_buffer.lock().last().copied(); + if let Some(erased_ch) = buf_last { + let erased_ch = erased_ch as char; + if erased_ch != '\n' { + self.input_buffer.lock().pop(); + self.erase_char(erased_ch); + } + } + } else if ch as char == self.termios.vstop && self.termios.iflags.ixon { self.output_paused.store(true, Ordering::Relaxed); - } else if (byte as char == self.termios.vstart && self.termios.iflags.ixon) + } else if (ch as char == self.termios.vstart && self.termios.iflags.ixon) || (self.termios.iflags.ixany && self.output_paused.load(Ordering::Relaxed)) { self.output_paused.store(false, Ordering::Relaxed); } else { - input_buffer.push(byte); - if self.termios.lflags.echo || (byte == b'\n' && self.termios.lflags.echonl) { - output_buffer.push(byte); + self.input_buffer.lock().push(ch); + if self.termios.lflags.echo || (ch == b'\n' && self.termios.lflags.echonl) { + let mut output_buffer = self.output_buffer.lock(); + if Self::is_nonprinting_control(ch as char) { + output_buffer.push(b'^'); + output_buffer.push(ch + b'@'); + self.column.fetch_add(2, Ordering::Relaxed); + } else if ch == b'\t' { + let curr_col = self.column.load(Ordering::Relaxed); + let num_cols = 8 - (curr_col % 8); + for _ in 0..num_cols { + output_buffer.push(b' '); + } + self.column.fetch_add(num_cols, Ordering::Relaxed); + } else { + output_buffer.push(ch); + self.column.fetch_add(1, Ordering::Relaxed); + } } } } @@ -108,17 +127,68 @@ impl Pty { for &byte in data { if self.termios.oflags.opost { if byte == b'\n' && self.termios.oflags.onlcr { - output_buffer.push(b'\r'); + if self.column.load(Ordering::Relaxed) != 0 { + output_buffer.push(b'\r'); + } output_buffer.push(b'\n'); + self.column.store(0, Ordering::Relaxed); } else if byte == b'\r' && self.termios.oflags.ocrnl { output_buffer.push(b'\n'); + if self.termios.oflags.onlret { + self.column.store(0, Ordering::Relaxed); + } } else { output_buffer.push(byte); } + } else if (byte == b'\n' && self.termios.oflags.onlret) || byte == b'\r' { + self.column.store(0, Ordering::Relaxed); } } Ok(()) } + + fn is_nonprinting_control(ch: char) -> bool { + ch.is_ascii_control() && ch != '\r' && ch != '\n' && ch != '\t' + } + + fn erase_char(&self, ch: char) { + if !self.termios.lflags.echoe { + return; + } + let mut output_buffer = self.output_buffer.lock(); + if Self::is_nonprinting_control(ch) { + output_buffer.push(8); + output_buffer.push(8); + self.column.fetch_sub(2, Ordering::Relaxed); + } else if ch == '\t' { + let curr_col = self.column.load(Ordering::Relaxed); + let mut before_tab_cols = 0; + let mut after_tab = false; + let input_buffer = self.input_buffer.lock(); + for &byte in input_buffer.iter().rev() { + if Self::is_nonprinting_control(byte as char) { + before_tab_cols += 2; + } else if byte == b'\t' { + after_tab = true; + break; + } else { + before_tab_cols += 1; + } + } + let num_cols = if after_tab { + 8 - before_tab_cols + } else { + curr_col - before_tab_cols + }; + for _ in 0..num_cols { + output_buffer.push(8); + } + self.column.fetch_sub(num_cols, Ordering::Relaxed); + } else { + output_buffer.push(8); + self.column.fetch_sub(1, Ordering::Relaxed); + } + } } #[derive(Clone)] @@ -180,7 +250,7 @@ impl file_rpc::Server for Serv { fn size(&self, _fd: u64) -> Option { None } - + fn dup(&self, fd: u64) -> Option { Some(fd) }