summaryrefslogtreecommitdiffstats
path: root/rsnix/src/wm.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-21 20:13:59 +0200
committersalmonik <salmonik@tuta.io>2026-08-21 20:13:59 +0200
commit2f4cf666c0c7bd224932fa48973e7334360c1de3 (patch)
tree7526314b18728b05c4e9d285b9a41cf6fcd6d6d6 /rsnix/src/wm.rs
parentimage: single dd-able system.img replaces vvfat+data-disk pair (diff)
downloadrs-dos-2f4cf666c0c7bd224932fa48973e7334360c1de3.tar.gz
rs-dos-2f4cf666c0c7bd224932fa48973e7334360c1de3.zip
input: total keyboard routing + per-terminal discipline (ADR 0006 ph1)
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.
Diffstat (limited to '')
-rw-r--r--rsnix/src/wm.rs144
1 files changed, 80 insertions, 64 deletions
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)`.