summaryrefslogtreecommitdiffstats
path: root/rsnix/src/tty.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rsnix/src/tty.rs')
-rw-r--r--rsnix/src/tty.rs239
1 files changed, 239 insertions, 0 deletions
diff --git a/rsnix/src/tty.rs b/rsnix/src/tty.rs
new file mode 100644
index 0000000..8d745b3
--- /dev/null
+++ b/rsnix/src/tty.rs
@@ -0,0 +1,239 @@
1//! Line discipline over the PS/2 keyboard — the kernel's tty layer.
2//!
3//! Two modes, like a real UNIX terminal:
4//! - **canonical** (`read_line`): input is echoed, buffered until Enter,
5//! ^C cancels the line, ^D on an empty line is EOF.
6//! - **raw** (`read_byte` / `try_read_byte`): keys are translated to the
7//! classic terminal byte protocol (arrows → `ESC [ A–D`, Backspace →
8//! `0x7f`, ^X → `0x18`, …) and delivered one byte at a time, with no echo.
9//! Multi-byte sequences are queued internally so `read_byte` returns them
10//! in order.
11//!
12//! `interrupt_pressed` is the cooperative-interrupt check used by streaming
13//! commands (cat/hexdump): it drains the keyboard non-blockingly and reports
14//! whether ^C was seen.
15
16use crate::ps2::{self, KeyEvent};
17use crate::strbuf;
18use crate::{gop, serial};
19
20pub const LINE_BUF_SIZE: usize = 256;
21
22/// Result of one canonical line-input session.
23pub enum LineInput {
24 Line(strbuf::String<LINE_BUF_SIZE>),
25 Interrupted, // ^C
26 Eof, // ^D on an empty line
27}
28
29// Raw-mode byte queue: holds the tail of a multi-byte key sequence.
30static mut RAW_QUEUE: [u8; 8] = [0; 8];
31static mut RAW_LEN: usize = 0;
32
33// ---------------------------------------------------------------------------
34// canonical mode
35// ---------------------------------------------------------------------------
36
37/// Read one line in canonical mode: echo every key, ^C cancels the line,
38/// ^D on an empty line is EOF. Echo goes to the terminal (GOP) and serial.
39pub fn read_line() -> LineInput {
40 let mut buf = strbuf::String::<LINE_BUF_SIZE>::new();
41 loop {
42 match ps2::poll_key() {
43 KeyEvent::Enter => {
44 gop::put_char('\n');
45 serial::write_str("\r\n");
46 return LineInput::Line(buf);
47 }
48 KeyEvent::Ctrl('c') => {
49 // Cancel the current line — like a real terminal, echo ^C.
50 buf.clear();
51 gop::put_char('^');
52 gop::put_char('C');
53 gop::put_char('\n');
54 serial::write_str("^C\r\n");
55 return LineInput::Interrupted;
56 }
57 KeyEvent::Ctrl('d') => {
58 if buf.is_empty() {
59 // EOF on an empty line — the caller decides what to do.
60 gop::put_char('^');
61 gop::put_char('D');
62 gop::put_char('\n');
63 serial::write_str("^D\r\n");
64 return LineInput::Eof;
65 }
66 // ^D with text on the line: ignored (bash semantics).
67 }
68 KeyEvent::Char(c) if c.is_ascii_graphic() || c == ' ' => {
69 if buf.push(c).is_ok() {
70 gop::put_char(c);
71 let mut s = [0u8; 4];
72 let s = c.encode_utf8(&mut s);
73 serial::write_str(s);
74 }
75 }
76 KeyEvent::Backspace => {
77 if buf.pop().is_some() {
78 gop::put_char('\x08');
79 // Serial backspace: BS SP BS (standard terminal protocol)
80 serial::write_str("\x08 \x08");
81 }
82 }
83 KeyEvent::Tab => {
84 for _ in 0..4 {
85 if buf.push(' ').is_ok() {
86 gop::put_char(' ');
87 serial::write_str(" ");
88 }
89 }
90 }
91 _ => {}
92 }
93 }
94}
95
96/// Non-blocking drain of the keyboard: true if a ^C is pending. Used by
97/// streaming commands (cat/hexdump) so a long dump can be interrupted.
98pub fn interrupt_pressed() -> bool {
99 let mut pressed = false;
100 while let Some(ev) = ps2::try_poll_key() {
101 if matches!(ev, KeyEvent::Ctrl('c')) {
102 pressed = true;
103 }
104 }
105 pressed
106}
107
108// ---------------------------------------------------------------------------
109// raw mode
110// ---------------------------------------------------------------------------
111
112/// Blocking read of the next raw-mode byte. Keys arrive translated to the
113/// terminal byte protocol; multi-byte sequences are delivered in order.
114pub fn read_byte() -> u8 {
115 loop {
116 if let Some(b) = pop_raw() {
117 return b;
118 }
119 let ev = ps2::poll_key();
120 enqueue_key(ev);
121 }
122}
123
124/// Non-blocking raw read: Some(byte) if one is available, else None.
125pub fn try_read_byte() -> Option<u8> {
126 if let Some(b) = pop_raw() {
127 return Some(b);
128 }
129 match ps2::try_poll_key() {
130 Some(ev) => {
131 enqueue_key(ev);
132 pop_raw()
133 }
134 None => None,
135 }
136}
137
138fn pop_raw() -> Option<u8> {
139 unsafe {
140 if RAW_LEN == 0 {
141 return None;
142 }
143 let b = RAW_QUEUE[0];
144 for i in 1..RAW_LEN {
145 RAW_QUEUE[i - 1] = RAW_QUEUE[i];
146 }
147 RAW_LEN -= 1;
148 Some(b)
149 }
150}
151
152fn enqueue_key(ev: KeyEvent) {
153 let mut q = [0u8; 8];
154 let n: usize = match ev {
155 KeyEvent::Char(c) if c.is_ascii() => {
156 q[0] = c as u8;
157 1
158 }
159 KeyEvent::Enter => {
160 q[0] = 0x0d;
161 1
162 }
163 KeyEvent::Backspace => {
164 q[0] = 0x7f;
165 1
166 }
167 KeyEvent::Tab => {
168 q[0] = 0x09;
169 1
170 }
171 KeyEvent::Escape => {
172 q[0] = 0x1b;
173 1
174 }
175 KeyEvent::Ctrl(c) => {
176 q[0] = (c as u8) & 0x1f;
177 1
178 }
179 KeyEvent::ArrowUp => {
180 q[..3].copy_from_slice(b"\x1b[A");
181 3
182 }
183 KeyEvent::ArrowDown => {
184 q[..3].copy_from_slice(b"\x1b[B");
185 3
186 }
187 KeyEvent::ArrowRight => {
188 q[..3].copy_from_slice(b"\x1b[C");
189 3
190 }
191 KeyEvent::ArrowLeft => {
192 q[..3].copy_from_slice(b"\x1b[D");
193 3
194 }
195 KeyEvent::CtrlArrow(0) => {
196 q[..6].copy_from_slice(b"\x1b[1;5A");
197 6
198 }
199 KeyEvent::CtrlArrow(1) => {
200 q[..6].copy_from_slice(b"\x1b[1;5B");
201 6
202 }
203 KeyEvent::CtrlArrow(2) => {
204 q[..6].copy_from_slice(b"\x1b[1;5D");
205 6
206 }
207 KeyEvent::CtrlArrow(3) => {
208 q[..6].copy_from_slice(b"\x1b[1;5C");
209 6
210 }
211 KeyEvent::Home => {
212 q[..3].copy_from_slice(b"\x1b[H");
213 3
214 }
215 KeyEvent::End => {
216 q[..3].copy_from_slice(b"\x1b[F");
217 3
218 }
219 KeyEvent::PageUp => {
220 q[..4].copy_from_slice(b"\x1b[5~");
221 4
222 }
223 KeyEvent::PageDown => {
224 q[..4].copy_from_slice(b"\x1b[6~");
225 4
226 }
227 KeyEvent::Delete => {
228 q[..4].copy_from_slice(b"\x1b[3~");
229 4
230 }
231 _ => return,
232 };
233 unsafe {
234 for (i, b) in q.iter().take(n).enumerate() {
235 RAW_QUEUE[i] = *b;
236 }
237 RAW_LEN = n;
238 }
239}