From 2f4cf666c0c7bd224932fa48973e7334360c1de3 Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 21 Aug 2026 20:13:59 +0200 Subject: input: total keyboard routing + per-terminal discipline (ADR 0006 ph1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wm: FOCUSED is now a plain window id — window 0 (home terminal) is an ordinary focusable window in the z-order. The CONSOLE sentinel (usize::MAX) and the focused()==0 special cases are gone; focus_console() is an alias for focus(0). Desktop clicks focus window 0. The WM input pump routes every key unconditionally: IRQ ring -> pump -> focused window's owner; nothing polls the scancode rings directly any more. tty: read_line() reads from the calling task's message queue (the old global-ring read_line and the duplicate read_line_from_queue are merged); raw mode (read_byte/try_read_byte) consumes the same queue; interrupt_pressed drains the queue instead of the ring. shell: task 0 owns home terminal window 0 and blocks on its queue like a windowed shell; cat/hexdump stdin paths pass the home terminal handle. sched: watchdog comments updated (shell parks on recv_blocking). Verified in QEMU: F1 spawns terminals, F2 cycles focus home->term1-> term2, keystrokes land only in the focused window, ^C cancels a line, edit runs raw over the queue and exits to a working prompt; test-disk regression PASS. --- rsnix/src/tty.rs | 149 ++++++++++--------------------------------------------- 1 file changed, 27 insertions(+), 122 deletions(-) (limited to 'rsnix/src/tty.rs') 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 @@ //! Line discipline over the keyboard — the kernel's tty layer. //! -//! Keys come from either the PS/2 controller (IRQ-fed scancode ring) or the -//! USB HID boot keyboard (polled UHCI driver). Both are translated to the -//! same `KeyEvent` space, so the shell is input-source agnostic. +//! 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, @@ -14,10 +15,10 @@ //! in order. //! //! `interrupt_pressed` is the cooperative-interrupt check used by streaming -//! commands (cat/hexdump): it drains the keyboard non-blockingly and reports +//! commands (cat/hexdump): it drains the queue non-blockingly and reports //! whether ^C was seen. -use crate::ps2::{self, KeyEvent}; +use crate::ps2::KeyEvent; use crate::strbuf; use crate::{gop, serial}; @@ -38,115 +39,14 @@ pub enum LineInput { static mut RAW_QUEUE: [u8; 8] = [0; 8]; static mut RAW_LEN: usize = 0; -/// Combined blocking key poll: USB HID keyboard first (polled UHCI), then -/// the PS/2 path (which parks via `hlt` until an IRQ-fed scancode lands). -fn poll_key() -> KeyEvent { - loop { - // A GUI window has focus: the keyboard pump owns the keys. Park so - // the shell/editor don't steal input from the focused app. - if crate::wm::focused().is_some() { - crate::sched::sleep_ms(5); - continue; - } - if let Some(ev) = crate::uhci::try_poll_key() { - return ev; - } - if let Some(ev) = ps2::try_poll_key() { - return ev; - } - // No key ready: park briefly, then re-check focus (so a focus change - // to a GUI window is noticed instead of blocking on a stolen key). - crate::sched::sleep_ms(5); - } -} - -/// Combined non-blocking key poll (USB first, then PS/2). Returns `None` -/// while a GUI window is focused (the keyboard pump owns the keys). -fn try_poll_key() -> Option { - if crate::wm::focused().is_some() { - return None; - } - crate::uhci::try_poll_key().or_else(ps2::try_poll_key) -} - -// --------------------------------------------------------------------------- -// canonical mode -// --------------------------------------------------------------------------- - -/// Read one line in canonical mode: echo every key, ^C cancels the line, -/// ^D on an empty line is EOF. Echo goes to the terminal (GOP) and serial. -pub fn read_line() -> LineInput { - // Interactive input: make sure deferred scrolls are pushed first so - // the screen shows the current state (no-op when nothing is pending). - crate::vt::sync_scroll(); - let mut buf = strbuf::String::::new(); - loop { - match poll_key() { - KeyEvent::Enter => { - gop::put_char('\n'); - serial::write_str("\r\n"); - return LineInput::Line(buf); - } - KeyEvent::Ctrl('c') => { - // Cancel the current line — like a real terminal, echo ^C. - buf.clear(); - gop::put_char('^'); - gop::put_char('C'); - gop::put_char('\n'); - serial::write_str("^C\r\n"); - return LineInput::Interrupted; - } - KeyEvent::Ctrl('d') => { - if buf.is_empty() { - // EOF on an empty line — the caller decides what to do. - gop::put_char('^'); - gop::put_char('D'); - gop::put_char('\n'); - serial::write_str("^D\r\n"); - return LineInput::Eof; - } - // ^D with text on the line: ignored (bash semantics). - } - 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 backspace: BS SP BS (standard terminal protocol) - serial::write_str("\x08 \x08"); - } - } - KeyEvent::Tab => { - for _ in 0..4 { - if buf.push(' ').is_ok() { - gop::put_char(' '); - serial::write_str(" "); - } - } - } - _ => {} - } - } -} - -/// Read one line in canonical mode from the task's message queue (for -/// windowed shells). Same semantics as `read_line` but keys arrive as -/// `TaskMsg::Key` instead of from the global PS/2 queue. Returns `None` -/// on `TaskMsg::Close` (window was closed). -/// Read one line in canonical mode from the task's message queue (for -/// windowed shells). Same semantics as `read_line` but keys arrive as -/// `TaskMsg::Key` instead of from the global PS/2 queue. Returns `None` -/// on `TaskMsg::Close` (window was closed). +/// 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_from_queue(term: &mut crate::vt::Terminal) -> Option { +pub fn read_line(term: &mut crate::vt::Terminal) -> Option { crate::vt::sync_scroll(); let mut buf = strbuf::String::::new(); loop { @@ -211,12 +111,14 @@ pub fn read_line_from_queue(term: &mut crate::vt::Terminal) -> Option } } -/// Non-blocking drain of the keyboard: true if a ^C is pending. Used by -/// streaming commands (cat/hexdump) so a long dump can be interrupted. +/// 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(ev) = try_poll_key() { - if matches!(ev, KeyEvent::Ctrl('c')) { + while let Some(m) = crate::sched::recv() { + if matches!(m, crate::sched::TaskMsg::Key(KeyEvent::Ctrl('c'))) { pressed = true; } } @@ -227,8 +129,9 @@ pub fn interrupt_pressed() -> bool { // raw mode // --------------------------------------------------------------------------- -/// Blocking read of the next raw-mode byte. Keys arrive translated to the -/// terminal byte protocol; multi-byte sequences are delivered in order. +/// 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. @@ -237,8 +140,10 @@ pub fn read_byte() -> u8 { if let Some(b) = pop_raw() { return b; } - let ev = poll_key(); - enqueue_key(ev); + match crate::sched::recv_blocking() { + crate::sched::TaskMsg::Key(ev) => enqueue_key(ev), + _ => {} + } } } @@ -247,12 +152,12 @@ pub fn try_read_byte() -> Option { if let Some(b) = pop_raw() { return Some(b); } - match try_poll_key() { - Some(ev) => { + match crate::sched::recv() { + Some(crate::sched::TaskMsg::Key(ev)) => { enqueue_key(ev); pop_raw() } - None => None, + _ => None, } } -- cgit v1.2.3