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/wm.rs | 144 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 80 insertions(+), 64 deletions(-) (limited to 'rsnix/src/wm.rs') diff --git a/rsnix/src/wm.rs b/rsnix/src/wm.rs index 39ad1be..9f3806f 100644 --- a/rsnix/src/wm.rs +++ b/rsnix/src/wm.rs @@ -16,8 +16,9 @@ use crate::sched::TaskMsg; /// Height of a window's title bar in pixels (one 8×16 glyph row). pub const TITLE_BAR_H: usize = 16; -/// Sentinel: `FOCUSED == CONSOLE` means the console (vt) has focus. -pub const CONSOLE: usize = usize::MAX; +/// Window 0 is the home terminal — an ordinary focusable window like any +/// other (ADR 0006 Phase 1: no CONSOLE sentinel, keyboard routing is +/// total: every key goes to `focused()`'s owner). const MAX_WINDOWS: usize = 16; const MIN_W: usize = 64; @@ -87,7 +88,7 @@ const EMPTY: Window = Window { static mut WINDOWS: [Window; MAX_WINDOWS] = [EMPTY; MAX_WINDOWS]; static mut ZORDER: [usize; MAX_WINDOWS] = [usize::MAX; MAX_WINDOWS]; static mut ZLEN: usize = 0; -static FOCUSED: AtomicUsize = AtomicUsize::new(CONSOLE); +static FOCUSED: AtomicUsize = AtomicUsize::new(0); static mut CASCADE_X: usize = 0; static mut CASCADE_Y: usize = 0; @@ -111,7 +112,6 @@ pub fn init() { CASCADE_X = 0; CASCADE_Y = 0; } - FOCUSED.store(CONSOLE, Ordering::Relaxed); } /// Create window 0 (the home terminal): full-screen, chrome-less, @@ -124,7 +124,7 @@ pub fn create_home_window(buf: *mut u32, w: usize, h: usize) -> usize { state: WinState::Visible, title: [0; TITLE_MAX], title_len: 0, - owner: usize::MAX, + owner: crate::sched::SHELL_TID, x: 0, y: 0, w, @@ -135,12 +135,17 @@ pub fn create_home_window(buf: *mut u32, w: usize, h: usize) -> usize { ZORDER[0] = id; ZLEN = 1; } + FOCUSED.store(0, Ordering::Relaxed); id } pub fn focused() -> Option { let v = FOCUSED.load(Ordering::Relaxed); - if v == CONSOLE || v == 0 { None } else { Some(v) } + if v < MAX_WINDOWS && get(v).is_some() { + Some(v) + } else { + None + } } fn get(id: usize) -> Option { @@ -231,8 +236,10 @@ pub fn flush(id: usize) { let outer_h = win.h + if win.chrome { TITLE_BAR_H } else { 0 }; let pad = if id != 0 { 1 } else { 0 }; composite_rect( - win.x.saturating_sub(pad), win.y.saturating_sub(pad), - win.w + 2 * pad, outer_h + 2 * pad, + win.x.saturating_sub(pad), + win.y.saturating_sub(pad), + win.w + 2 * pad, + outer_h + 2 * pad, ); } @@ -256,8 +263,8 @@ pub fn destroy(id: usize) { if focused() == Some(id) { if unsafe { ZLEN } > 0 { focus(unsafe { ZORDER[ZLEN - 1] }); - } else { - focus_console(); + } else if id != 0 { + focus(0); } } composite_rect(x, y, w, h); @@ -277,7 +284,7 @@ pub fn minimize(id: usize) { } remove_from_zorder(id); if focused() == Some(id) { - focus_console(); + focus(0); } composite_rect(x, y, w, h); } @@ -307,20 +314,12 @@ pub fn focus(id: usize) { return; } let prev = focused(); - // Window 0 (home terminal) is the console — store the CONSOLE - // sentinel so `focused()` returns None and the tty layer reads - // keyboard input directly (same as focus_console). - if id == 0 { - FOCUSED.store(CONSOLE, Ordering::Relaxed); - } else { - FOCUSED.store(id, Ordering::Relaxed); - } - if id != 0 { - remove_from_zorder(id); - unsafe { - ZORDER[ZLEN] = id; - ZLEN += 1; - } + FOCUSED.store(id, Ordering::Relaxed); + // Every window lives in the z-order now, window 0 included. + remove_from_zorder(id); + unsafe { + ZORDER[ZLEN] = id; + ZLEN += 1; } if let Some(p) = prev && p != id @@ -331,8 +330,10 @@ pub fn focus(id: usize) { let oh = pw.h + if pw.chrome { TITLE_BAR_H } else { 0 }; let pad = if p != 0 { 1 } else { 0 }; composite_rect( - pw.x.saturating_sub(pad), pw.y.saturating_sub(pad), - pw.w + 2 * pad, oh + 2 * pad, + pw.x.saturating_sub(pad), + pw.y.saturating_sub(pad), + pw.w + 2 * pad, + oh + 2 * pad, ); } let _ = crate::sched::send(win.owner, TaskMsg::Focus(true)); @@ -340,35 +341,26 @@ pub fn focus(id: usize) { let oh = win.h + if win.chrome { TITLE_BAR_H } else { 0 }; let pad = if id != 0 { 1 } else { 0 }; composite_rect( - win.x.saturating_sub(pad), win.y.saturating_sub(pad), - win.w + 2 * pad, oh + 2 * pad, + win.x.saturating_sub(pad), + win.y.saturating_sub(pad), + win.w + 2 * pad, + oh + 2 * pad, ); } +/// Legacy name — the home terminal is now just window 0. pub fn focus_console() { - let prev = focused(); - FOCUSED.store(CONSOLE, Ordering::Relaxed); - if let Some(p) = prev - && let Some(pw) = get(p) - { - let _ = crate::sched::send(pw.owner, TaskMsg::Focus(false)); - let oh = pw.h + if pw.chrome { TITLE_BAR_H } else { 0 }; - let pad = if p != 0 { 1 } else { 0 }; - composite_rect( - pw.x.saturating_sub(pad), pw.y.saturating_sub(pad), - pw.w + 2 * pad, oh + 2 * pad, - ); - } + focus(0); } pub fn focus_next() { unsafe { - if ZLEN == 0 { - focus_console(); - return; - } match focused() { - None => focus(ZORDER[ZLEN - 1]), + None => { + if ZLEN > 0 { + focus(ZORDER[ZLEN - 1]); + } + } Some(cur) => { let mut idx = usize::MAX; for i in 0..ZLEN { @@ -378,7 +370,9 @@ pub fn focus_next() { } } if idx == usize::MAX || idx == 0 { - focus_console(); + if ZLEN > 0 { + focus(ZORDER[ZLEN - 1]); + } } else { focus(ZORDER[idx - 1]); } @@ -530,6 +524,7 @@ fn mouse_press(x: i32, y: i32, buttons: u8) { Hit::None => {} } } else { + // Click on the bare desktop: focus the home terminal (window 0). focus_console(); } } @@ -606,8 +601,18 @@ fn move_window(id: usize, nx: usize, ny: usize) { } let pad: usize = 1; let oh = w.h + TITLE_BAR_H; - composite_rect(ox.saturating_sub(pad), oy.saturating_sub(pad), w.w + 2 * pad, oh + 2 * pad); - composite_rect(nx.saturating_sub(pad), ny.saturating_sub(pad), w.w + 2 * pad, oh + 2 * pad); + composite_rect( + ox.saturating_sub(pad), + oy.saturating_sub(pad), + w.w + 2 * pad, + oh + 2 * pad, + ); + composite_rect( + nx.saturating_sub(pad), + ny.saturating_sub(pad), + w.w + 2 * pad, + oh + 2 * pad, + ); } pub fn owner(id: usize) -> Option { @@ -637,13 +642,14 @@ pub fn spawn_input_pump() -> Option { } fn pump_main() { + // Total routing (ADR 0006 Phase 1): every key from every source is + // pumped to the focused window's owner. No task ever polls the + // global rings directly; when nothing is focused the keys drop. loop { - if let Some(id) = focused() { - let o = owner(id).unwrap_or(0); - while let Some(ev) = crate::ps2::try_poll_key().or_else(crate::uhci::try_poll_key) { - if !handle_key(ev) { - let _ = crate::sched::send(o, TaskMsg::Key(ev)); - } + let o = focused().and_then(owner).unwrap_or(crate::sched::SHELL_TID); + while let Some(ev) = crate::ps2::try_poll_key().or_else(crate::uhci::try_poll_key) { + if !handle_key(ev) { + let _ = crate::sched::send(o, TaskMsg::Key(ev)); } } crate::sched::sleep_ms(5); @@ -669,11 +675,19 @@ fn composite_rect(x: usize, y: usize, w: usize, h: usize) { /// 1px border drawn around every window except the root (id 0). /// Blue when focused, grey when not. fn draw_window_border(id: usize, rx: usize, ry: usize, rw: usize, rh: usize) { - if id == 0 { return; } + if id == 0 { + return; + } let win = unsafe { WINDOWS[id] }; - if !win.used || win.state != WinState::Visible { return; } + if !win.used || win.state != WinState::Visible { + return; + } let outer_h = win.h + if win.chrome { TITLE_BAR_H } else { 0 }; - let bg = if focused() == Some(id) { TITLE_BG_FOCUS } else { TITLE_BG }; + let bg = if focused() == Some(id) { + TITLE_BG_FOCUS + } else { + TITLE_BG + }; let bc = gop::rgb(bg.0, bg.1, bg.2); // Border sits 1px outside the window's outer rect. let bx = win.x.saturating_sub(1); @@ -684,12 +698,14 @@ fn draw_window_border(id: usize, rx: usize, ry: usize, rw: usize, rh: usize) { let iy = by.max(ry); let ix2 = (bx + bw).min(rx + rw); let iy2 = (by + bh).min(ry + rh); - if ix >= ix2 || iy >= iy2 { return; } + if ix >= ix2 || iy >= iy2 { + return; + } // Four edges. - gop::shadow_fill_rect(ix, iy, ix2 - ix, 1, bc); // top - gop::shadow_fill_rect(ix, iy2 - 1, ix2 - ix, 1, bc); // bottom - gop::shadow_fill_rect(ix, iy + 1, 1, iy2 - iy - 2, bc); // left - gop::shadow_fill_rect(ix2 - 1, iy + 1, 1, iy2 - iy - 2, bc); // right + gop::shadow_fill_rect(ix, iy, ix2 - ix, 1, bc); // top + gop::shadow_fill_rect(ix, iy2 - 1, ix2 - ix, 1, bc); // bottom + gop::shadow_fill_rect(ix, iy + 1, 1, iy2 - iy - 2, bc); // left + gop::shadow_fill_rect(ix2 - 1, iy + 1, 1, iy2 - iy - 2, bc); // right } /// Blit the portion of a window that intersects `(rx, ry, rw, rh)`. -- cgit v1.2.3