//! Line discipline over the keyboard — the kernel's tty layer. //! //! Every key is routed by the WM input pump to the focused window's //! owning task as `TaskMsg::Key`; the tty layer consumes only that task's //! message queue (ADR 0006 Phase 1: total routing, per-terminal //! discipline). No code path reads the PS/2/USB scancode rings directly. //! //! Two modes, like a real UNIX terminal: //! - **canonical** (`read_line`): input is echoed, buffered until Enter, //! ^C cancels the line, ^D on an empty line is EOF. //! - **raw** (`read_byte` / `try_read_byte`): keys are translated to the //! classic terminal byte protocol (arrows → `ESC [ A–D`, Backspace → //! `0x7f`, ^X → `0x18`, …) and delivered one byte at a time, with no echo. //! Multi-byte sequences are queued internally so `read_byte` returns them //! in order. //! //! `interrupt_pressed` is the cooperative-interrupt check used by streaming //! commands (cat/hexdump): it drains the queue non-blockingly and reports //! whether ^C was seen. use crate::ps2::KeyEvent; use crate::strbuf; use crate::{gop, serial}; /// Max characters a single typed line can hold. Long enough that no /// realistic command line gets silently cut — the terminal wraps the /// echo at the screen edge, and the full line is what's submitted. pub const LINE_BUF_SIZE: usize = 1024; /// Result of one canonical line-input session. #[allow(clippy::large_enum_variant)] // stack buffer by design (no heap churn) pub enum LineInput { Line(strbuf::String), Interrupted, // ^C Eof, // ^D on an empty line } // Raw-mode byte queue: holds the tail of a multi-byte key sequence. static mut RAW_QUEUE: [u8; 8] = [0; 8]; static mut RAW_LEN: usize = 0; /// Read one line in canonical mode from the task's message queue. Keys /// arrive as `TaskMsg::Key`, pumped by the WM to the focused window's /// owner (this task, if its window is focused). Returns `None` on /// `TaskMsg::Close` (window was closed). /// /// Handles `TaskMsg::Focus` to toggle the global `ACTIVE` redirect so /// gop_print! goes to the right terminal buffer. pub fn read_line(term: &mut crate::vt::Terminal) -> Option { crate::vt::sync_scroll(); let mut buf = strbuf::String::::new(); loop { match crate::sched::recv_blocking() { crate::sched::TaskMsg::Key(ev) => match ev { KeyEvent::Enter => { gop::put_char('\n'); serial::write_str("\r\n"); return Some(LineInput::Line(buf)); } KeyEvent::Ctrl('c') => { buf.clear(); gop::put_char('^'); gop::put_char('C'); gop::put_char('\n'); serial::write_str("^C\r\n"); return Some(LineInput::Interrupted); } KeyEvent::Ctrl('d') => { if buf.is_empty() { gop::put_char('^'); gop::put_char('D'); gop::put_char('\n'); serial::write_str("^D\r\n"); return Some(LineInput::Eof); } } KeyEvent::Char(c) if c.is_ascii_graphic() || c == ' ' => { if buf.push(c).is_ok() { gop::put_char(c); let mut s = [0u8; 4]; let s = c.encode_utf8(&mut s); serial::write_str(s); } } KeyEvent::Backspace => { if buf.pop().is_some() { gop::put_char('\x08'); serial::write_str("\x08 \x08"); } } KeyEvent::Tab => { for _ in 0..4 { if buf.push(' ').is_ok() { gop::put_char(' '); serial::write_str(" "); } } } _ => {} }, crate::sched::TaskMsg::Focus(gained) => { if gained { crate::vt::set_active(term as *mut crate::vt::Terminal); } else { crate::vt::set_active(core::ptr::null_mut()); } } crate::sched::TaskMsg::Close => return None, _ => {} } } } /// Non-blocking drain of this task's queue: true if a ^C is pending. /// Used by streaming commands (cat/hexdump) so a long dump can be /// interrupted. Everything else in the queue is dropped — during a /// streaming command the terminal owns the input stream. pub fn interrupt_pressed() -> bool { let mut pressed = false; while let Some(m) = crate::sched::recv() { if matches!(m, crate::sched::TaskMsg::Key(KeyEvent::Ctrl('c'))) { pressed = true; } } pressed } // --------------------------------------------------------------------------- // raw mode // --------------------------------------------------------------------------- /// Blocking read of the next raw-mode byte. Keys arrive from the task's /// message queue (WM-pumped), translated to the terminal byte protocol; /// multi-byte sequences are delivered in order. pub fn read_byte() -> u8 { // Interactive (raw) input: push deferred scrolls so the full-screen // app draws over a current framebuffer. crate::vt::sync_scroll(); loop { if let Some(b) = pop_raw() { return b; } match crate::sched::recv_blocking() { crate::sched::TaskMsg::Key(ev) => enqueue_key(ev), _ => {} } } } /// Non-blocking raw read: Some(byte) if one is available, else None. pub fn try_read_byte() -> Option { if let Some(b) = pop_raw() { return Some(b); } match crate::sched::recv() { Some(crate::sched::TaskMsg::Key(ev)) => { enqueue_key(ev); pop_raw() } _ => None, } } fn pop_raw() -> Option { unsafe { if RAW_LEN == 0 { return None; } let b = RAW_QUEUE[0]; for i in 1..RAW_LEN { RAW_QUEUE[i - 1] = RAW_QUEUE[i]; } RAW_LEN -= 1; Some(b) } } fn enqueue_key(ev: KeyEvent) { let mut q = [0u8; 8]; let n: usize = match ev { KeyEvent::Char(c) if c.is_ascii() => { q[0] = c as u8; 1 } KeyEvent::Enter => { q[0] = 0x0d; 1 } KeyEvent::Backspace => { q[0] = 0x7f; 1 } KeyEvent::Tab => { q[0] = 0x09; 1 } KeyEvent::Escape => { q[0] = 0x1b; 1 } KeyEvent::Ctrl(c) => { q[0] = (c as u8) & 0x1f; 1 } KeyEvent::ArrowUp => { q[..3].copy_from_slice(b"\x1b[A"); 3 } KeyEvent::ArrowDown => { q[..3].copy_from_slice(b"\x1b[B"); 3 } KeyEvent::ArrowRight => { q[..3].copy_from_slice(b"\x1b[C"); 3 } KeyEvent::ArrowLeft => { q[..3].copy_from_slice(b"\x1b[D"); 3 } KeyEvent::CtrlArrow(0) => { q[..6].copy_from_slice(b"\x1b[1;5A"); 6 } KeyEvent::CtrlArrow(1) => { q[..6].copy_from_slice(b"\x1b[1;5B"); 6 } KeyEvent::CtrlArrow(2) => { q[..6].copy_from_slice(b"\x1b[1;5D"); 6 } KeyEvent::CtrlArrow(3) => { q[..6].copy_from_slice(b"\x1b[1;5C"); 6 } KeyEvent::Home => { q[..3].copy_from_slice(b"\x1b[H"); 3 } KeyEvent::End => { q[..3].copy_from_slice(b"\x1b[F"); 3 } KeyEvent::PageUp => { q[..4].copy_from_slice(b"\x1b[5~"); 4 } KeyEvent::PageDown => { q[..4].copy_from_slice(b"\x1b[6~"); 4 } KeyEvent::Delete => { q[..4].copy_from_slice(b"\x1b[3~"); 4 } _ => return, }; unsafe { for (i, b) in q.iter().take(n).enumerate() { RAW_QUEUE[i] = *b; } RAW_LEN = n; } }