diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-01 19:52:05 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-01 19:52:05 +0200 |
| commit | 819778f292ee4e1294603b98662e083e49e23463 (patch) | |
| tree | 8df31b678432d2a89cac8e0d50f271a75a3882b5 /CONTEXT.md | |
| parent | rsnix v0.4.0: GUI input foundation — PS/2 mouse + scheduler message queues (diff) | |
| download | rs-dos-819778f292ee4e1294603b98662e083e49e23463.tar.gz rs-dos-819778f292ee4e1294603b98662e083e49e23463.zip | |
rsnix v0.5.0: graphics layer + cursor compositor
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 <id>' 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.
Diffstat (limited to 'CONTEXT.md')
| -rw-r--r-- | CONTEXT.md | 74 |
1 files changed, 60 insertions, 14 deletions
| @@ -113,9 +113,10 @@ Workspace version is `a.b.c` (single version for all crates): | |||
| 113 | 113 | ||
| 114 | Bump the workspace version in the root `Cargo.toml` when a change of | 114 | Bump the workspace version in the root `Cargo.toml` when a change of |
| 115 | that class lands — do not leave the version to drift. Current: | 115 | that class lands — do not leave the version to drift. Current: |
| 116 | `0.4.0` — the GUI input foundation (PS/2 mouse + scheduler message | 116 | `0.5.0` — the GUI era landed (graphics layer + cursor compositor) as a |
| 117 | queues) landed as a `b` bump; the GUI era itself is reserved for the | 117 | `b` bump from the 0.4.0 input foundation; a future major GUI overhaul |
| 118 | `a` bump (1.0.0). | 118 | (graphics windows beyond the text surface) would be the `a` bump |
| 119 | (1.0.0). | ||
| 119 | 120 | ||
| 120 | ## Project layout | 121 | ## Project layout |
| 121 | 122 | ||
| @@ -426,19 +427,24 @@ Workspace of four crates, all built for the `x86_64-unknown-uefi` target: | |||
| 426 | relative** (`dx` + = right, `dy` + = down — the PS/2 Y-up wire | 427 | relative** (`dx` + = right, `dy` + = down — the PS/2 Y-up wire |
| 427 | convention is negated), buttons decoded; 9-bit sign extension via | 428 | convention is negated), buttons decoded; 9-bit sign extension via |
| 428 | the packet sign bits. | 429 | the packet sign bits. |
| 429 | - `fs`: `/dev/mouse` char device — `cat /dev/mouse` drains up to 32 | 430 | - `fs`: `/dev/mouse` is now a **position device** — `cat /dev/mouse` |
| 430 | pending events (`dx=+N dy=+N buttons=L-R`), reports | 431 | prints the cursor's current state (`x=N y=N buttons=---`), since |
| 431 | `mouse: no PS/2 mouse` / `mouse: no events` otherwise. | 432 | the cursor task owns the PS/2 ring; `mouse: no PS/2 mouse` |
| 432 | - shell: `mouse` command streams live events (10 ms poll via | 433 | otherwise. |
| 433 | `sleep_ms`) until ^C. | 434 | - shell: `mouse` **watches the on-screen cursor** (x/y/buttons, |
| 435 | printed only on change, 10 ms poll) until ^C; `mouse <id>` also | ||
| 436 | routes every decoded event to that task's queue (`cursor still | ||
| 437 | tracks`, route cleared on exit). `cursor` prints a one-shot state | ||
| 438 | line (the parseable test surface). | ||
| 434 | 439 | ||
| 435 | Verified in QEMU (`make test-mouse`, headless, via the monitor's | 440 | Verified in QEMU (`make test-mouse`, headless, via the monitor's |
| 436 | `mouse_move`/`mouse_button` HMP commands): boot line `ps2: mouse | 441 | `mouse_move`/`mouse_button` HMP commands): the cursor starts centred, |
| 437 | ready`, `/dev/mouse` drains a pre-consumer move (`8,4`), the live | 442 | `mouse_move 20 7` moves it +20/+7 (asserted against `cursor` output), |
| 438 | stream decodes `mouse_move 20 7` → `dx=+20 dy=+7 buttons=---`, | 443 | `mouse_button 1` → `buttons=L--`, a held-button move (`-5,-3`) |
| 439 | `mouse_button 1` → `buttons=L--`, a held-button move | 444 | tracks position *and* keeps the button, release clears, `/dev/mouse` |
| 440 | (`-5,-3` while L down) → `dx=-5 dy=-3 buttons=L--`, release, | 445 | reports the same position snapshot, the live `mouse` watch prints the |
| 441 | and ^C stops the stream. `make test-disk` regression re-passes. | 446 | changed line and ^C stops it, and `mouse 3` routes a real move into |
| 447 | the msgrecv task's queue. `make test-disk` regression re-passes. | ||
| 442 | 448 | ||
| 443 | ✅ **Scheduler message queues (the GUI-era primitive from ADR 0003)** — | 449 | ✅ **Scheduler message queues (the GUI-era primitive from ADR 0003)** — |
| 444 | every task gets a static 16-slot ring of `TaskMsg` (4 KB in .bss, | 450 | 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: | |||
| 475 | the shell's never-consumed queue), and `jobs` shows q=0 + task back | 481 | the shell's never-consumed queue), and `jobs` shows q=0 + task back |
| 476 | to `blocked` after draining. `make test-disk` regression re-passes. | 482 | to `blocked` after draining. `make test-disk` regression re-passes. |
| 477 | 483 | ||
| 484 | ✅ **Graphics layer + cursor compositor** — the first visible GUI | ||
| 485 | surface: real rect primitives, a shadow→display push path, and an | ||
| 486 | on-screen mouse cursor that never races the text surface: | ||
| 487 | |||
| 488 | - `gop`: `fill_rect` + `push_rect(x,y,w,h)` — the **single display | ||
| 489 | write path**. Every raster op rasterises into the RAM shadow | ||
| 490 | first, then `push_rect` copies that rect to the display (write- | ||
| 491 | only; the display is still never *read*) and composites the | ||
| 492 | cursor on top. `blit_glyph` and scroll shifts now route through | ||
| 493 | it, so text redraws can never erase or corrupt the cursor. The | ||
| 494 | cursor lives *only* in the overlay — never in the shadow — which | ||
| 495 | makes save/restore impossible to get wrong: erasing = push the | ||
| 496 | old rect (the shadow simply doesn't contain the cursor there). | ||
| 497 | - cursor bitmap: 12×16 pointer built by a const fn from explicit | ||
| 498 | column lists (hand-mangled bit literals were the one real bug | ||
| 499 | this round — twice), drawn white with a black offset outline | ||
| 500 | (+1,+1). Position lives in one `AtomicU64` (x:high 32, y:low 32) | ||
| 501 | so a relaxed load never sees a torn pair — *and* the unpack must | ||
| 502 | mask the low word (a bare `as usize` cast is a no-op on 64-bit | ||
| 503 | and returned the whole packed value). | ||
| 504 | - `cursor` task (spawned at boot when the mouse is present, task 2): | ||
| 505 | polls the PS/2 ring every 10 ms, moves the cursor via | ||
| 506 | `cursor_move` (clamped to the screen; erase-old + draw-new are | ||
| 507 | both shadow pushes), tracks buttons, and forwards every event to | ||
| 508 | a task's queue when `mouse <id>` armed it. The cursor is drawn | ||
| 509 | once at spawn (the vt diff never re-blits idle cells) and stays | ||
| 510 | visible because every display write composites it; scrolls | ||
| 511 | re-overlay it after the bulk shift. | ||
| 512 | - shell: `gfx` draws a status-bar demo scene (navy strip + RGB | ||
| 513 | boxes); `gfx check` is an in-kernel pixel self-test — draws a red | ||
| 514 | rect, reads it back from the shadow *and* the display, asserts | ||
| 515 | the cursor tip is white on the display but absent from the shadow | ||
| 516 | (overlay purity), and prints PASS/FAIL per stage. | ||
| 517 | |||
| 518 | Verified in QEMU (`make test-gfx`): `fill_rect/shadow: OK`, | ||
| 519 | `display push: OK`, `cursor overlay: OK`, `cursor not in shadow: | ||
| 520 | OK`, `gfx check: PASS`; the demo draws; the cursor tracks injected | ||
| 521 | mouse input (test-mouse) with the overlay intact. `make test-disk` | ||
| 522 | regression re-passes. | ||
| 523 | |||
| 478 | ## Known issues / perf notes | 524 | ## Known issues / perf notes |
| 479 | 525 | ||
| 480 | - **Scroll cost** — scrolling still shifts the whole framebuffer (a | 526 | - **Scroll cost** — scrolling still shifts the whole framebuffer (a |
