summaryrefslogtreecommitdiffstats
path: root/rsnix/src/tty.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rsnix/src/tty.rs149
1 files changed, 27 insertions, 122 deletions
diff --git a/rsnix/src/tty.rs b/rsnix/src/tty.rs
index c44dca8..1358fa9 100644
--- a/rsnix/src/tty.rs
+++ b/rsnix/src/tty.rs
@@ -1,8 +1,9 @@
1//! Line discipline over the keyboard — the kernel's tty layer. 1//! Line discipline over the keyboard — the kernel's tty layer.
2//! 2//!
3//! Keys come from either the PS/2 controller (IRQ-fed scancode ring) or the 3//! Every key is routed by the WM input pump to the focused window's
4//! USB HID boot keyboard (polled UHCI driver). Both are translated to the 4//! owning task as `TaskMsg::Key`; the tty layer consumes only that task's
5//! same `KeyEvent` space, so the shell is input-source agnostic. 5//! message queue (ADR 0006 Phase 1: total routing, per-terminal
6//! discipline). No code path reads the PS/2/USB scancode rings directly.
6//! 7//!
7//! Two modes, like a real UNIX terminal: 8//! Two modes, like a real UNIX terminal:
8//! - **canonical** (`read_line`): input is echoed, buffered until Enter, 9//! - **canonical** (`read_line`): input is echoed, buffered until Enter,
@@ -14,10 +15,10 @@
14//! in order. 15//! in order.
15//! 16//!
16//! `interrupt_pressed` is the cooperative-interrupt check used by streaming 17//! `interrupt_pressed` is the cooperative-interrupt check used by streaming
17//! commands (cat/hexdump): it drains the keyboard non-blockingly and reports 18//! commands (cat/hexdump): it drains the queue non-blockingly and reports
18//! whether ^C was seen. 19//! whether ^C was seen.
19 20
20use crate::ps2::{self, KeyEvent}; 21use crate::ps2::KeyEvent;
21use crate::strbuf; 22use crate::strbuf;
22use crate::{gop, serial}; 23use crate::{gop, serial};
23 24
@@ -38,115 +39,14 @@ pub enum LineInput {
38static mut RAW_QUEUE: [u8; 8] = [0; 8]; 39static mut RAW_QUEUE: [u8; 8] = [0; 8];
39static mut RAW_LEN: usize = 0; 40static mut RAW_LEN: usize = 0;
40 41
41/// Combined blocking key poll: USB HID keyboard first (polled UHCI), then 42/// Read one line in canonical mode from the task's message queue. Keys
42/// the PS/2 path (which parks via `hlt` until an IRQ-fed scancode lands). 43/// arrive as `TaskMsg::Key`, pumped by the WM to the focused window's
43fn poll_key() -> KeyEvent { 44/// owner (this task, if its window is focused). Returns `None` on
44 loop { 45/// `TaskMsg::Close` (window was closed).
45 // A GUI window has focus: the keyboard pump owns the keys. Park so
46 // the shell/editor don't steal input from the focused app.
47 if crate::wm::focused().is_some() {
48 crate::sched::sleep_ms(5);
49 continue;
50 }
51 if let Some(ev) = crate::uhci::try_poll_key() {
52 return ev;
53 }
54 if let Some(ev) = ps2::try_poll_key() {
55 return ev;
56 }
57 // No key ready: park briefly, then re-check focus (so a focus change
58 // to a GUI window is noticed instead of blocking on a stolen key).
59 crate::sched::sleep_ms(5);
60 }
61}
62
63/// Combined non-blocking key poll (USB first, then PS/2). Returns `None`
64/// while a GUI window is focused (the keyboard pump owns the keys).
65fn try_poll_key() -> Option<KeyEvent> {
66 if crate::wm::focused().is_some() {
67 return None;
68 }
69 crate::uhci::try_poll_key().or_else(ps2::try_poll_key)
70}
71
72// ---------------------------------------------------------------------------
73// canonical mode
74// ---------------------------------------------------------------------------
75
76/// Read one line in canonical mode: echo every key, ^C cancels the line,
77/// ^D on an empty line is EOF. Echo goes to the terminal (GOP) and serial.
78pub fn read_line() -> LineInput {
79 // Interactive input: make sure deferred scrolls are pushed first so
80 // the screen shows the current state (no-op when nothing is pending).
81 crate::vt::sync_scroll();
82 let mut buf = strbuf::String::<LINE_BUF_SIZE>::new();
83 loop {
84 match poll_key() {
85 KeyEvent::Enter => {
86 gop::put_char('\n');
87 serial::write_str("\r\n");
88 return LineInput::Line(buf);
89 }
90 KeyEvent::Ctrl('c') => {
91 // Cancel the current line — like a real terminal, echo ^C.
92 buf.clear();
93 gop::put_char('^');
94 gop::put_char('C');
95 gop::put_char('\n');
96 serial::write_str("^C\r\n");
97 return LineInput::Interrupted;
98 }
99 KeyEvent::Ctrl('d') => {
100 if buf.is_empty() {
101 // EOF on an empty line — the caller decides what to do.
102 gop::put_char('^');
103 gop::put_char('D');
104 gop::put_char('\n');
105 serial::write_str("^D\r\n");
106 return LineInput::Eof;
107 }
108 // ^D with text on the line: ignored (bash semantics).
109 }
110 KeyEvent::Char(c) if c.is_ascii_graphic() || c == ' ' => {
111 if buf.push(c).is_ok() {
112 gop::put_char(c);
113 let mut s = [0u8; 4];
114 let s = c.encode_utf8(&mut s);
115 serial::write_str(s);
116 }
117 }
118 KeyEvent::Backspace => {
119 if buf.pop().is_some() {
120 gop::put_char('\x08');
121 // Serial backspace: BS SP BS (standard terminal protocol)
122 serial::write_str("\x08 \x08");
123 }
124 }
125 KeyEvent::Tab => {
126 for _ in 0..4 {
127 if buf.push(' ').is_ok() {
128 gop::put_char(' ');
129 serial::write_str(" ");
130 }
131 }
132 }
133 _ => {}
134 }
135 }
136}
137
138/// Read one line in canonical mode from the task's message queue (for
139/// windowed shells). Same semantics as `read_line` but keys arrive as
140/// `TaskMsg::Key` instead of from the global PS/2 queue. Returns `None`
141/// on `TaskMsg::Close` (window was closed).
142/// Read one line in canonical mode from the task's message queue (for
143/// windowed shells). Same semantics as `read_line` but keys arrive as
144/// `TaskMsg::Key` instead of from the global PS/2 queue. Returns `None`
145/// on `TaskMsg::Close` (window was closed).
146/// 46///
147/// Handles `TaskMsg::Focus` to toggle the global `ACTIVE` redirect so 47/// Handles `TaskMsg::Focus` to toggle the global `ACTIVE` redirect so
148/// gop_print! goes to the right terminal buffer. 48/// gop_print! goes to the right terminal buffer.
149pub fn read_line_from_queue(term: &mut crate::vt::Terminal) -> Option<LineInput> { 49pub fn read_line(term: &mut crate::vt::Terminal) -> Option<LineInput> {
150 crate::vt::sync_scroll(); 50 crate::vt::sync_scroll();
151 let mut buf = strbuf::String::<LINE_BUF_SIZE>::new(); 51 let mut buf = strbuf::String::<LINE_BUF_SIZE>::new();
152 loop { 52 loop {
@@ -211,12 +111,14 @@ pub fn read_line_from_queue(term: &mut crate::vt::Terminal) -> Option<LineInput>
211 } 111 }
212} 112}
213 113
214/// Non-blocking drain of the keyboard: true if a ^C is pending. Used by 114/// Non-blocking drain of this task's queue: true if a ^C is pending.
215/// streaming commands (cat/hexdump) so a long dump can be interrupted. 115/// Used by streaming commands (cat/hexdump) so a long dump can be
116/// interrupted. Everything else in the queue is dropped — during a
117/// streaming command the terminal owns the input stream.
216pub fn interrupt_pressed() -> bool { 118pub fn interrupt_pressed() -> bool {
217 let mut pressed = false; 119 let mut pressed = false;
218 while let Some(ev) = try_poll_key() { 120 while let Some(m) = crate::sched::recv() {
219 if matches!(ev, KeyEvent::Ctrl('c')) { 121 if matches!(m, crate::sched::TaskMsg::Key(KeyEvent::Ctrl('c'))) {
220 pressed = true; 122 pressed = true;
221 } 123 }
222 } 124 }
@@ -227,8 +129,9 @@ pub fn interrupt_pressed() -> bool {
227// raw mode 129// raw mode
228// --------------------------------------------------------------------------- 130// ---------------------------------------------------------------------------
229 131
230/// Blocking read of the next raw-mode byte. Keys arrive translated to the 132/// Blocking read of the next raw-mode byte. Keys arrive from the task's
231/// terminal byte protocol; multi-byte sequences are delivered in order. 133/// message queue (WM-pumped), translated to the terminal byte protocol;
134/// multi-byte sequences are delivered in order.
232pub fn read_byte() -> u8 { 135pub fn read_byte() -> u8 {
233 // Interactive (raw) input: push deferred scrolls so the full-screen 136 // Interactive (raw) input: push deferred scrolls so the full-screen
234 // app draws over a current framebuffer. 137 // app draws over a current framebuffer.
@@ -237,8 +140,10 @@ pub fn read_byte() -> u8 {
237 if let Some(b) = pop_raw() { 140 if let Some(b) = pop_raw() {
238 return b; 141 return b;
239 } 142 }
240 let ev = poll_key(); 143 match crate::sched::recv_blocking() {
241 enqueue_key(ev); 144 crate::sched::TaskMsg::Key(ev) => enqueue_key(ev),
145 _ => {}
146 }
242 } 147 }
243} 148}
244 149
@@ -247,12 +152,12 @@ pub fn try_read_byte() -> Option<u8> {
247 if let Some(b) = pop_raw() { 152 if let Some(b) = pop_raw() {
248 return Some(b); 153 return Some(b);
249 } 154 }
250 match try_poll_key() { 155 match crate::sched::recv() {
251 Some(ev) => { 156 Some(crate::sched::TaskMsg::Key(ev)) => {
252 enqueue_key(ev); 157 enqueue_key(ev);
253 pop_raw() 158 pop_raw()
254 } 159 }
255 None => None, 160 _ => None,
256 } 161 }
257} 162}
258 163