summaryrefslogtreecommitdiffstats
path: root/rsnix/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rsnix/src/sched.rs13
-rw-r--r--rsnix/src/tty.rs149
-rw-r--r--rsnix/src/wm.rs144
3 files changed, 114 insertions, 192 deletions
diff --git a/rsnix/src/sched.rs b/rsnix/src/sched.rs
index 39ecb65..0c4938e 100644
--- a/rsnix/src/sched.rs
+++ b/rsnix/src/sched.rs
@@ -201,7 +201,7 @@ const WATCHDOG_SPIN_TICKS: u32 = 1500;
201const WATCHDOG_BOOT_SPIN_TICKS: u32 = 750; 201const WATCHDOG_BOOT_SPIN_TICKS: u32 = 750;
202 202
203/// True once the shell REPL is live (set by main.rs before shell::run()). 203/// True once the shell REPL is live (set by main.rs before shell::run()).
204/// The shell legitimately parks in poll_key with a fixed frame when it is; 204/// The shell legitimately blocks on its message queue once live;
205/// before that, task 0 running boot code must not sit in a fixed frame. 205/// before that, task 0 running boot code must not sit in a fixed frame.
206static SHELL_RUNNING: AtomicBool = AtomicBool::new(false); 206static SHELL_RUNNING: AtomicBool = AtomicBool::new(false);
207 207
@@ -264,16 +264,17 @@ pub fn on_tick(cur: usize) -> usize {
264 TASKS[CURRENT].ticks += 1; 264 TASKS[CURRENT].ticks += 1;
265 // Spin detection: a task whose interrupted frame never moves 265 // Spin detection: a task whose interrupted frame never moves
266 // (identical RSP on every tick) is stuck in a hot loop — no yield, 266 // (identical RSP on every tick) is stuck in a hot loop — no yield,
267 // no sleep, no block. The shell (id 0) legitimately parks in 267 // no sleep, no block. The shell (id 0) legitimately blocks on
268 // poll_key with a fixed frame, and idle (id 1) hlt's; both exempt. 268 // recv_blocking with a fixed frame, and idle (id 1) hlt's; both
269 // exempt.
269 let id = CURRENT; 270 let id = CURRENT;
270 let w = &mut SPIN_WATCH[id]; 271 let w = &mut SPIN_WATCH[id];
271 if w.last_rsp == cur { 272 if w.last_rsp == cur {
272 w.same += 1; 273 w.same += 1;
273 // Background tasks (id >= 2): spinning is never legitimate. 274 // Background tasks (id >= 2): spinning is never legitimate.
274 // Task 0 (shell): exempt once the REPL is live (poll_key parks 275 // Task 0 (shell): exempt once the REPL is live (recv_blocking
275 // with a fixed frame); during the boot phase a fixed frame 276 // parks with a fixed frame); during the boot phase a fixed
276 // means usb/ehci init is wedged on real hardware. 277 // frame means usb/ehci init is wedged on real hardware.
277 let threshold = if id == SHELL_TID && !SHELL_RUNNING.load(Ordering::Relaxed) { 278 let threshold = if id == SHELL_TID && !SHELL_RUNNING.load(Ordering::Relaxed) {
278 WATCHDOG_BOOT_SPIN_TICKS 279 WATCHDOG_BOOT_SPIN_TICKS
279 } else if id >= 2 { 280 } else if id >= 2 {
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
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;
16/// Height of a window's title bar in pixels (one 8×16 glyph row). 16/// Height of a window's title bar in pixels (one 8×16 glyph row).
17pub const TITLE_BAR_H: usize = 16; 17pub const TITLE_BAR_H: usize = 16;
18 18
19/// Sentinel: `FOCUSED == CONSOLE` means the console (vt) has focus. 19/// Window 0 is the home terminal — an ordinary focusable window like any
20pub const CONSOLE: usize = usize::MAX; 20/// other (ADR 0006 Phase 1: no CONSOLE sentinel, keyboard routing is
21/// total: every key goes to `focused()`'s owner).
21 22
22const MAX_WINDOWS: usize = 16; 23const MAX_WINDOWS: usize = 16;
23const MIN_W: usize = 64; 24const MIN_W: usize = 64;
@@ -87,7 +88,7 @@ const EMPTY: Window = Window {
87static mut WINDOWS: [Window; MAX_WINDOWS] = [EMPTY; MAX_WINDOWS]; 88static mut WINDOWS: [Window; MAX_WINDOWS] = [EMPTY; MAX_WINDOWS];
88static mut ZORDER: [usize; MAX_WINDOWS] = [usize::MAX; MAX_WINDOWS]; 89static mut ZORDER: [usize; MAX_WINDOWS] = [usize::MAX; MAX_WINDOWS];
89static mut ZLEN: usize = 0; 90static mut ZLEN: usize = 0;
90static FOCUSED: AtomicUsize = AtomicUsize::new(CONSOLE); 91static FOCUSED: AtomicUsize = AtomicUsize::new(0);
91static mut CASCADE_X: usize = 0; 92static mut CASCADE_X: usize = 0;
92static mut CASCADE_Y: usize = 0; 93static mut CASCADE_Y: usize = 0;
93 94
@@ -111,7 +112,6 @@ pub fn init() {
111 CASCADE_X = 0; 112 CASCADE_X = 0;
112 CASCADE_Y = 0; 113 CASCADE_Y = 0;
113 } 114 }
114 FOCUSED.store(CONSOLE, Ordering::Relaxed);
115} 115}
116 116
117/// Create window 0 (the home terminal): full-screen, chrome-less, 117/// 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 {
124 state: WinState::Visible, 124 state: WinState::Visible,
125 title: [0; TITLE_MAX], 125 title: [0; TITLE_MAX],
126 title_len: 0, 126 title_len: 0,
127 owner: usize::MAX, 127 owner: crate::sched::SHELL_TID,
128 x: 0, 128 x: 0,
129 y: 0, 129 y: 0,
130 w, 130 w,
@@ -135,12 +135,17 @@ pub fn create_home_window(buf: *mut u32, w: usize, h: usize) -> usize {
135 ZORDER[0] = id; 135 ZORDER[0] = id;
136 ZLEN = 1; 136 ZLEN = 1;
137 } 137 }
138 FOCUSED.store(0, Ordering::Relaxed);
138 id 139 id
139} 140}
140 141
141pub fn focused() -> Option<usize> { 142pub fn focused() -> Option<usize> {
142 let v = FOCUSED.load(Ordering::Relaxed); 143 let v = FOCUSED.load(Ordering::Relaxed);
143 if v == CONSOLE || v == 0 { None } else { Some(v) } 144 if v < MAX_WINDOWS && get(v).is_some() {
145 Some(v)
146 } else {
147 None
148 }
144} 149}
145 150
146fn get(id: usize) -> Option<Window> { 151fn get(id: usize) -> Option<Window> {
@@ -231,8 +236,10 @@ pub fn flush(id: usize) {
231 let outer_h = win.h + if win.chrome { TITLE_BAR_H } else { 0 }; 236 let outer_h = win.h + if win.chrome { TITLE_BAR_H } else { 0 };
232 let pad = if id != 0 { 1 } else { 0 }; 237 let pad = if id != 0 { 1 } else { 0 };
233 composite_rect( 238 composite_rect(
234 win.x.saturating_sub(pad), win.y.saturating_sub(pad), 239 win.x.saturating_sub(pad),
235 win.w + 2 * pad, outer_h + 2 * pad, 240 win.y.saturating_sub(pad),
241 win.w + 2 * pad,
242 outer_h + 2 * pad,
236 ); 243 );
237} 244}
238 245
@@ -256,8 +263,8 @@ pub fn destroy(id: usize) {
256 if focused() == Some(id) { 263 if focused() == Some(id) {
257 if unsafe { ZLEN } > 0 { 264 if unsafe { ZLEN } > 0 {
258 focus(unsafe { ZORDER[ZLEN - 1] }); 265 focus(unsafe { ZORDER[ZLEN - 1] });
259 } else { 266 } else if id != 0 {
260 focus_console(); 267 focus(0);
261 } 268 }
262 } 269 }
263 composite_rect(x, y, w, h); 270 composite_rect(x, y, w, h);
@@ -277,7 +284,7 @@ pub fn minimize(id: usize) {
277 } 284 }
278 remove_from_zorder(id); 285 remove_from_zorder(id);
279 if focused() == Some(id) { 286 if focused() == Some(id) {
280 focus_console(); 287 focus(0);
281 } 288 }
282 composite_rect(x, y, w, h); 289 composite_rect(x, y, w, h);
283} 290}
@@ -307,20 +314,12 @@ pub fn focus(id: usize) {
307 return; 314 return;
308 } 315 }
309 let prev = focused(); 316 let prev = focused();
310 // Window 0 (home terminal) is the console — store the CONSOLE 317 FOCUSED.store(id, Ordering::Relaxed);
311 // sentinel so `focused()` returns None and the tty layer reads 318 // Every window lives in the z-order now, window 0 included.
312 // keyboard input directly (same as focus_console). 319 remove_from_zorder(id);
313 if id == 0 { 320 unsafe {
314 FOCUSED.store(CONSOLE, Ordering::Relaxed); 321 ZORDER[ZLEN] = id;
315 } else { 322 ZLEN += 1;
316 FOCUSED.store(id, Ordering::Relaxed);
317 }
318 if id != 0 {
319 remove_from_zorder(id);
320 unsafe {
321 ZORDER[ZLEN] = id;
322 ZLEN += 1;
323 }
324 } 323 }
325 if let Some(p) = prev 324 if let Some(p) = prev
326 && p != id 325 && p != id
@@ -331,8 +330,10 @@ pub fn focus(id: usize) {
331 let oh = pw.h + if pw.chrome { TITLE_BAR_H } else { 0 }; 330 let oh = pw.h + if pw.chrome { TITLE_BAR_H } else { 0 };
332 let pad = if p != 0 { 1 } else { 0 }; 331 let pad = if p != 0 { 1 } else { 0 };
333 composite_rect( 332 composite_rect(
334 pw.x.saturating_sub(pad), pw.y.saturating_sub(pad), 333 pw.x.saturating_sub(pad),
335 pw.w + 2 * pad, oh + 2 * pad, 334 pw.y.saturating_sub(pad),
335 pw.w + 2 * pad,
336 oh + 2 * pad,
336 ); 337 );
337 } 338 }
338 let _ = crate::sched::send(win.owner, TaskMsg::Focus(true)); 339 let _ = crate::sched::send(win.owner, TaskMsg::Focus(true));
@@ -340,35 +341,26 @@ pub fn focus(id: usize) {
340 let oh = win.h + if win.chrome { TITLE_BAR_H } else { 0 }; 341 let oh = win.h + if win.chrome { TITLE_BAR_H } else { 0 };
341 let pad = if id != 0 { 1 } else { 0 }; 342 let pad = if id != 0 { 1 } else { 0 };
342 composite_rect( 343 composite_rect(
343 win.x.saturating_sub(pad), win.y.saturating_sub(pad), 344 win.x.saturating_sub(pad),
344 win.w + 2 * pad, oh + 2 * pad, 345 win.y.saturating_sub(pad),
346 win.w + 2 * pad,
347 oh + 2 * pad,
345 ); 348 );
346} 349}
347 350
351/// Legacy name — the home terminal is now just window 0.
348pub fn focus_console() { 352pub fn focus_console() {
349 let prev = focused(); 353 focus(0);
350 FOCUSED.store(CONSOLE, Ordering::Relaxed);
351 if let Some(p) = prev
352 && let Some(pw) = get(p)
353 {
354 let _ = crate::sched::send(pw.owner, TaskMsg::Focus(false));
355 let oh = pw.h + if pw.chrome { TITLE_BAR_H } else { 0 };
356 let pad = if p != 0 { 1 } else { 0 };
357 composite_rect(
358 pw.x.saturating_sub(pad), pw.y.saturating_sub(pad),
359 pw.w + 2 * pad, oh + 2 * pad,
360 );
361 }
362} 354}
363 355
364pub fn focus_next() { 356pub fn focus_next() {
365 unsafe { 357 unsafe {
366 if ZLEN == 0 {
367 focus_console();
368 return;
369 }
370 match focused() { 358 match focused() {
371 None => focus(ZORDER[ZLEN - 1]), 359 None => {
360 if ZLEN > 0 {
361 focus(ZORDER[ZLEN - 1]);
362 }
363 }
372 Some(cur) => { 364 Some(cur) => {
373 let mut idx = usize::MAX; 365 let mut idx = usize::MAX;
374 for i in 0..ZLEN { 366 for i in 0..ZLEN {
@@ -378,7 +370,9 @@ pub fn focus_next() {
378 } 370 }
379 } 371 }
380 if idx == usize::MAX || idx == 0 { 372 if idx == usize::MAX || idx == 0 {
381 focus_console(); 373 if ZLEN > 0 {
374 focus(ZORDER[ZLEN - 1]);
375 }
382 } else { 376 } else {
383 focus(ZORDER[idx - 1]); 377 focus(ZORDER[idx - 1]);
384 } 378 }
@@ -530,6 +524,7 @@ fn mouse_press(x: i32, y: i32, buttons: u8) {
530 Hit::None => {} 524 Hit::None => {}
531 } 525 }
532 } else { 526 } else {
527 // Click on the bare desktop: focus the home terminal (window 0).
533 focus_console(); 528 focus_console();
534 } 529 }
535} 530}
@@ -606,8 +601,18 @@ fn move_window(id: usize, nx: usize, ny: usize) {
606 } 601 }
607 let pad: usize = 1; 602 let pad: usize = 1;
608 let oh = w.h + TITLE_BAR_H; 603 let oh = w.h + TITLE_BAR_H;
609 composite_rect(ox.saturating_sub(pad), oy.saturating_sub(pad), w.w + 2 * pad, oh + 2 * pad); 604 composite_rect(
610 composite_rect(nx.saturating_sub(pad), ny.saturating_sub(pad), w.w + 2 * pad, oh + 2 * pad); 605 ox.saturating_sub(pad),
606 oy.saturating_sub(pad),
607 w.w + 2 * pad,
608 oh + 2 * pad,
609 );
610 composite_rect(
611 nx.saturating_sub(pad),
612 ny.saturating_sub(pad),
613 w.w + 2 * pad,
614 oh + 2 * pad,
615 );
611} 616}
612 617
613pub fn owner(id: usize) -> Option<usize> { 618pub fn owner(id: usize) -> Option<usize> {
@@ -637,13 +642,14 @@ pub fn spawn_input_pump() -> Option<usize> {
637} 642}
638 643
639fn pump_main() { 644fn pump_main() {
645 // Total routing (ADR 0006 Phase 1): every key from every source is
646 // pumped to the focused window's owner. No task ever polls the
647 // global rings directly; when nothing is focused the keys drop.
640 loop { 648 loop {
641 if let Some(id) = focused() { 649 let o = focused().and_then(owner).unwrap_or(crate::sched::SHELL_TID);
642 let o = owner(id).unwrap_or(0); 650 while let Some(ev) = crate::ps2::try_poll_key().or_else(crate::uhci::try_poll_key) {
643 while let Some(ev) = crate::ps2::try_poll_key().or_else(crate::uhci::try_poll_key) { 651 if !handle_key(ev) {
644 if !handle_key(ev) { 652 let _ = crate::sched::send(o, TaskMsg::Key(ev));
645 let _ = crate::sched::send(o, TaskMsg::Key(ev));
646 }
647 } 653 }
648 } 654 }
649 crate::sched::sleep_ms(5); 655 crate::sched::sleep_ms(5);
@@ -669,11 +675,19 @@ fn composite_rect(x: usize, y: usize, w: usize, h: usize) {
669/// 1px border drawn around every window except the root (id 0). 675/// 1px border drawn around every window except the root (id 0).
670/// Blue when focused, grey when not. 676/// Blue when focused, grey when not.
671fn draw_window_border(id: usize, rx: usize, ry: usize, rw: usize, rh: usize) { 677fn draw_window_border(id: usize, rx: usize, ry: usize, rw: usize, rh: usize) {
672 if id == 0 { return; } 678 if id == 0 {
679 return;
680 }
673 let win = unsafe { WINDOWS[id] }; 681 let win = unsafe { WINDOWS[id] };
674 if !win.used || win.state != WinState::Visible { return; } 682 if !win.used || win.state != WinState::Visible {
683 return;
684 }
675 let outer_h = win.h + if win.chrome { TITLE_BAR_H } else { 0 }; 685 let outer_h = win.h + if win.chrome { TITLE_BAR_H } else { 0 };
676 let bg = if focused() == Some(id) { TITLE_BG_FOCUS } else { TITLE_BG }; 686 let bg = if focused() == Some(id) {
687 TITLE_BG_FOCUS
688 } else {
689 TITLE_BG
690 };
677 let bc = gop::rgb(bg.0, bg.1, bg.2); 691 let bc = gop::rgb(bg.0, bg.1, bg.2);
678 // Border sits 1px outside the window's outer rect. 692 // Border sits 1px outside the window's outer rect.
679 let bx = win.x.saturating_sub(1); 693 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) {
684 let iy = by.max(ry); 698 let iy = by.max(ry);
685 let ix2 = (bx + bw).min(rx + rw); 699 let ix2 = (bx + bw).min(rx + rw);
686 let iy2 = (by + bh).min(ry + rh); 700 let iy2 = (by + bh).min(ry + rh);
687 if ix >= ix2 || iy >= iy2 { return; } 701 if ix >= ix2 || iy >= iy2 {
702 return;
703 }
688 // Four edges. 704 // Four edges.
689 gop::shadow_fill_rect(ix, iy, ix2 - ix, 1, bc); // top 705 gop::shadow_fill_rect(ix, iy, ix2 - ix, 1, bc); // top
690 gop::shadow_fill_rect(ix, iy2 - 1, ix2 - ix, 1, bc); // bottom 706 gop::shadow_fill_rect(ix, iy2 - 1, ix2 - ix, 1, bc); // bottom
691 gop::shadow_fill_rect(ix, iy + 1, 1, iy2 - iy - 2, bc); // left 707 gop::shadow_fill_rect(ix, iy + 1, 1, iy2 - iy - 2, bc); // left
692 gop::shadow_fill_rect(ix2 - 1, iy + 1, 1, iy2 - iy - 2, bc); // right 708 gop::shadow_fill_rect(ix2 - 1, iy + 1, 1, iy2 - iy - 2, bc); // right
693} 709}
694 710
695/// Blit the portion of a window that intersects `(rx, ry, rw, rh)`. 711/// Blit the portion of a window that intersects `(rx, ry, rw, rh)`.