From 819778f292ee4e1294603b98662e083e49e23463 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 1 Aug 2026 19:52:05 +0200 Subject: rsnix v0.5.0: graphics layer + cursor compositor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first visible GUI surface: real rect primitives, a single shadow→display push path, and an on-screen mouse cursor that can never race the text surface. gop: - fill_rect + push_rect(x,y,w,h): every raster op rasterises into the RAM shadow, then push_rect copies the rect to the display (write-only, still never read) and composites the cursor on top. blit_glyph and scroll shifts now route through it. - cursor overlay: 12x16 pointer (const-fn built from column lists) drawn white with a black offset outline; position in one AtomicU64 (x:high 32, y:low 32) — unpack masks the low word (a bare 'as usize' cast is a no-op on 64-bit). The cursor never enters the shadow, so erasing is just a shadow push of the old rect. - cursor_redraw after scroll shifts and once at spawn (the vt diff never re-blits idle cells). cursor task (task 2, spawned at boot when a mouse is present): - owns the PS/2 ring; polls every 10 ms, moves the cursor (clamped), tracks buttons, and forwards every event to a task's queue when 'mouse ' armed it (the window-manager input path). - /dev/mouse is now a position device (x/y/buttons snapshot); 'mouse' watches the cursor live (prints on change); 'cursor' prints one-shot state; 'gfx' draws a status-bar demo; 'gfx check' is an in-kernel pixel self-test (shadow readback, display readback, cursor overlay purity) — PASS/FAIL per stage. Tests (headless QEMU + monitor injection): - make test-gfx (new): all four compositor checks PASS. - make test-mouse (rewritten): cursor tracks mouse_move deltas, buttons ride along, /dev/mouse snapshot, live watch, real-event queue routing. - make test-msg: task numbering shifted (cursor is task 2; msgrecv is task 3) — updated, still PASS. - make test-disk regression re-passes. 0 build warnings. --- CONTEXT.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 14 deletions(-) (limited to 'CONTEXT.md') diff --git a/CONTEXT.md b/CONTEXT.md index 9ceb81d..bfe0170 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -113,9 +113,10 @@ Workspace version is `a.b.c` (single version for all crates): Bump the workspace version in the root `Cargo.toml` when a change of that class lands — do not leave the version to drift. Current: -`0.4.0` — the GUI input foundation (PS/2 mouse + scheduler message -queues) landed as a `b` bump; the GUI era itself is reserved for the -`a` bump (1.0.0). +`0.5.0` — the GUI era landed (graphics layer + cursor compositor) as a +`b` bump from the 0.4.0 input foundation; a future major GUI overhaul +(graphics windows beyond the text surface) would be the `a` bump +(1.0.0). ## Project layout @@ -426,19 +427,24 @@ Workspace of four crates, all built for the `x86_64-unknown-uefi` target: relative** (`dx` + = right, `dy` + = down — the PS/2 Y-up wire convention is negated), buttons decoded; 9-bit sign extension via the packet sign bits. -- `fs`: `/dev/mouse` char device — `cat /dev/mouse` drains up to 32 - pending events (`dx=+N dy=+N buttons=L-R`), reports - `mouse: no PS/2 mouse` / `mouse: no events` otherwise. -- shell: `mouse` command streams live events (10 ms poll via - `sleep_ms`) until ^C. +- `fs`: `/dev/mouse` is now a **position device** — `cat /dev/mouse` + prints the cursor's current state (`x=N y=N buttons=---`), since + the cursor task owns the PS/2 ring; `mouse: no PS/2 mouse` + otherwise. +- shell: `mouse` **watches the on-screen cursor** (x/y/buttons, + printed only on change, 10 ms poll) until ^C; `mouse ` also + routes every decoded event to that task's queue (`cursor still + tracks`, route cleared on exit). `cursor` prints a one-shot state + line (the parseable test surface). Verified in QEMU (`make test-mouse`, headless, via the monitor's - `mouse_move`/`mouse_button` HMP commands): boot line `ps2: mouse - ready`, `/dev/mouse` drains a pre-consumer move (`8,4`), the live - stream decodes `mouse_move 20 7` → `dx=+20 dy=+7 buttons=---`, - `mouse_button 1` → `buttons=L--`, a held-button move - (`-5,-3` while L down) → `dx=-5 dy=-3 buttons=L--`, release, - and ^C stops the stream. `make test-disk` regression re-passes. + `mouse_move`/`mouse_button` HMP commands): the cursor starts centred, + `mouse_move 20 7` moves it +20/+7 (asserted against `cursor` output), + `mouse_button 1` → `buttons=L--`, a held-button move (`-5,-3`) + tracks position *and* keeps the button, release clears, `/dev/mouse` + reports the same position snapshot, the live `mouse` watch prints the + changed line and ^C stops it, and `mouse 3` routes a real move into + the msgrecv task's queue. `make test-disk` regression re-passes. ✅ **Scheduler message queues (the GUI-era primitive from ADR 0003)** — every task gets a static 16-slot ring of `TaskMsg` (4 KB in .bss, @@ -475,6 +481,46 @@ Workspace of four crates, all built for the `x86_64-unknown-uefi` target: the shell's never-consumed queue), and `jobs` shows q=0 + task back to `blocked` after draining. `make test-disk` regression re-passes. +✅ **Graphics layer + cursor compositor** — the first visible GUI + surface: real rect primitives, a shadow→display push path, and an + on-screen mouse cursor that never races the text surface: + +- `gop`: `fill_rect` + `push_rect(x,y,w,h)` — the **single display + write path**. Every raster op rasterises into the RAM shadow + first, then `push_rect` copies that rect to the display (write- + only; the display is still never *read*) and composites the + cursor on top. `blit_glyph` and scroll shifts now route through + it, so text redraws can never erase or corrupt the cursor. The + cursor lives *only* in the overlay — never in the shadow — which + makes save/restore impossible to get wrong: erasing = push the + old rect (the shadow simply doesn't contain the cursor there). +- cursor bitmap: 12×16 pointer built by a const fn from explicit + column lists (hand-mangled bit literals were the one real bug + this round — twice), drawn white with a black offset outline + (+1,+1). Position lives in one `AtomicU64` (x:high 32, y:low 32) + so a relaxed load never sees a torn pair — *and* the unpack must + mask the low word (a bare `as usize` cast is a no-op on 64-bit + and returned the whole packed value). +- `cursor` task (spawned at boot when the mouse is present, task 2): + polls the PS/2 ring every 10 ms, moves the cursor via + `cursor_move` (clamped to the screen; erase-old + draw-new are + both shadow pushes), tracks buttons, and forwards every event to + a task's queue when `mouse ` armed it. The cursor is drawn + once at spawn (the vt diff never re-blits idle cells) and stays + visible because every display write composites it; scrolls + re-overlay it after the bulk shift. +- shell: `gfx` draws a status-bar demo scene (navy strip + RGB + boxes); `gfx check` is an in-kernel pixel self-test — draws a red + rect, reads it back from the shadow *and* the display, asserts + the cursor tip is white on the display but absent from the shadow + (overlay purity), and prints PASS/FAIL per stage. + + Verified in QEMU (`make test-gfx`): `fill_rect/shadow: OK`, + `display push: OK`, `cursor overlay: OK`, `cursor not in shadow: + OK`, `gfx check: PASS`; the demo draws; the cursor tracks injected + mouse input (test-mouse) with the overlay intact. `make test-disk` + regression re-passes. + ## Known issues / perf notes - **Scroll cost** — scrolling still shifts the whole framebuffer (a -- cgit v1.2.3