WM v1 platform — what exists, what broke, what is next (Rex brief)

This document is the single place to answer “what happened after the windowing refactor” and what still has to be built for a real desktop stack around WM v1.

Warning

This roadmap is a historical WM-focused brief and is not the complete current platform contract. For canonical current-state docs, use newdocs/README.md and:

  • newdocs/system-architecture.md

  • newdocs/settings-control-pattern.md

  • newdocs/game-engine-architecture.md

  • newdocs/doom-renderer-roadmap.md

See also: Documentation map — which file owns which topic (WM close regressions → foundation-debt §5; client RGBA mmap → §5.3). · System architecture — full-stack diagram + §7 shell/terminal gaps & target wiring (ioctl, signals, job control, TERM). · Terminal stack — PTY wiring, gterm vs dps ownership, display sanitizer vs VT emulation. · PTY v1 design — kernel + userspace PTY contract, sys_read flags, Phase D status. · SDK/README.md — SDK layout (p1std, p1window, cstd).

Application SDK — UI policy (Apr 2026)

For compositor-backed WM v1 windows, use p1window{.external} (or the p1std{.external} umbrella). Use Window::open_decorated, present_rgba, and try_recv_event / WmEvent for the main loop — not raw p1_ipc_send + magic message_type values for those flows.

  • cstd::wm_v1 is the wire layer (p1window calls it).

  • Raw cstd::p1ui is fine for bootstrap datagrams not wrapped yet (e.g. display query 6 / 7).

New apps should prefer one dependency: p1std{.external} (p1std::window + p1std::libc).

What WM v1 is today

  • Wire format: apps/deskgui/src/api/window.rs defines message_type values, pack_* / unpack_*, and WM v1 create / present / geometry opcodes.

  • Transport: Syscall 50 (sys_ipc_send) with a user IpcMessage; kernel may remap present pointers into the compositor’s address space for types 2, 5, 18. Each sender owns a 16 MiB VA band in the compositor (0x6000_1000_0000 + sender_task_id * 0x1_000_000); num_pages is clamped so map/teardown never crosses into the next band (see foundation-debt.md §3.5.6).

  • Recv: Syscall 51 is the canonical sys_ipc_recv (syscall 31 is a duplicate handler in the kernel — user libc now uses 51 via SDK/lib/cstd/src/p1ui.rs).

  • Compositor: apps/deskgui decodes datagrams, owns z-order, focus, title chrome, and blits client RGBA with WindowManager::render (client Y = window.y + WM_V1_TITLE_BAR_H).

  • Clients (policy): Windowed apps use p1window{.external} (or p1std{.external}) for create / present / input. Legacy code may still call wm_v1 / p1ui directly; new code should not add hand-rolled WM loops.

Bugs found and fixes landed (terminal / WM / exec)

1. Decorated window geometry vs. client buffer

Window::h is client height. The compositor draws title bar + client. Clamping win.y with ch - win.h allowed the outer frame to extend TITLE_H pixels past the work area or sit too high, so draw_external_frame skipped most source rows (final_y < clip_y). Symptom: almost black client, only a strip at the bottom (e.g. the > line).

Fix: clamp_window_with_rect uses outer height h + WM_V1_TITLE_BAR_H for vertical clamping, caps client height to ch - TITLE_H on snap, and Window::contains uses the same outer height. Constant: WM_V1_TITLE_BAR_H in api/window.rs.

2. sys_execve children did not inherit the parent’s FD table

spawn_task always installed a fresh fd_table (0→0, 1→1, …). That matches the global VFS singleton stdin/stdout only if the parent never diverged; more importantly, next_fd and any extra mappings were not inherited, so parent/child I/O behaviour could drift from Unix expectations.

Fix: spawn_task(..., inherit_fds_from: Option<usize>). sys_execve passes Some(current_task_idx) so the child clones the parent’s fd_table and next_fd. Boot paths (shell.rs init / helper spawn) pass None (unchanged bootstrap behaviour).

3. gterm ↔ DPS integration (PTY master / redraw)

  • PTY output: poll_dps_output drains the PTY master in a loop with sys_read_with_flags(..., READ_FLAG_NONBLOCK_PTY) (see PTY v1 design §1.2) and feeds chunks to append_pty_master_bytes (line splitting + display fold — not read(1) on a pipe).

  • Lines: completed lines strip trailing \r before fold + push; tail is folded each frame for the live prompt.

  • UI noise: Removed the yellow F:… K:… debug overlay (constant invalidation contributed to perceived flicker).

4. IPC recv syscall alignment

p1_ipc_recv now uses syscall 51, matching apps/init and avoiding confusion with the legacy duplicate 31.

5. Exec cost (ramdisk)

Previously documented: ramdisk_bytes_if_cached + single clone in sys_execve avoids double full-ELF clone for cached ramdisk ELFs. Remaining launch latency is dominated by ELF map / page tables / first run unless we add prelinking or keep warm workers.

6. Multi-window close / present map — root causes fixed (Apr 2026)

Several bugs stacked together so “close order” looked superstitious; none of them were “apps must depend on the first terminal.” Full write-up: foundation-debt.md §3, §5, §5.1, §5.2 (serial log signature for the “close front → survivor #PF” case), §5.3 (client RGBA mmap vs brk). Short list so nobody re-debugs from zero:

Failure mode

What was wrong

Where fixed

Kill top terminal → #PF or hang on next draw_external_frame for the other PID

spawn_process_pml4 clones PML4[192] into every user task. Syscall 50 installs compositor present PTEs under that shared subtree. cleanup_user_page_tables walked p4_idx 1..255 including 192, freeing leaves/PTs still used by deskgui for other clients.

src/mm/memory.rs — skip p4_idx == 192 in user cleanup.

Close order seemed to depend on “which band” / PID layout

Uncapped num_pages could map or vmm_unmap_user_pages past one 16 MiB sender slot into the neighbor’s compositor VA.

src/kernel/syscalls.rs — clamp pages; pre-translate all sender pages before map + registry upsert.

Wrong window got clicks when frames overlapped

Hit test used Vec::rev() assuming vec order matched z_order (it does not after focus bumps / until render sorts).

apps/deskgui/src/deskgui/window_manager.rs — topmost_window_index_at, sort after alt-tab, same rule for mouse forward.

Blit raced teardown

Window row still existed across SIGKILL return.

Remove rows before kill; sys_ipc_unmap_present_slot before SIGKILL on title-bar close.

Torn blit row alignment (wide buffers)

src_idx stride bug when w > 2048.

apps/deskgui/src/api/render_context.rs — draw_external_frame row advance.

Rainbow / garbage in client area when client buffer width > window width

Present metadata carried window w but source RGBA rows were padded to a larger stride (e.g. gterm FB_CAP_W); compositor used w as row stride → read wrong offsets / “random” pixels.

gterm: wm_v1::send_present_rgba with stride_u16 = FB_CAP_W, buffer_bytes = stride * FB_CAP_H * 4. Compositor: WmPresentSurface passes stride_px into wm_v1_set_frame; Window.buffer_stride_px; draw_external_frame(..., src_stride_px) uses sy * src_stride for row base when src_stride_px > 0. 0 = legacy “tight **w×h” (e.g. embedded dialog canvas).

gterm #GP / heap corruption under multi-window + resize

Large ping-pong RGBA lived on the same brk arena as alloc (Window, strings); growing the surface risked crossing live objects.

p1_alloc_framebuffer (sys_mmap_anon) for FB_MEM; heap stays brk-only in SysAllocator. apps/termd/src/main.rs; SDK/lib/cstd/src/p1_framebuffer.rs.

Debt (not “fixed”, only mitigated): shared PML4[192] means user cleanup skips that subtree — leaks or manual compositor teardown may be needed when deskgui truly exits; Track B in this file (surface table + non-shared map identity) is still the structural fix.

6b. Desktop chrome follow-ups (Apr 2026)

Feature

Behavior

Where

Task manager drag

User can move the floating task manager by dragging its title bar (close button excluded); position clamped to the work area.

apps/deskgui/src/deskgui/compositor.rs — task_mgr_x / task_mgr_y, task_mgr_dragging, handle_task_manager_mouse (uses left_down + left_pressed).

Per-core CPU HUD

Top bar / task manager footer can show “busy / online” logical cores using sys_cpu_core_load: writes [u32; 2] — online (AP count capped), busy_mask (bit i = core i not running kernel idle). Syscall 225; cstd: SYS_CPU_CORE_LOAD, sys_cpu_core_load.

Kernel: src/kernel/syscalls.rs (case 225); compositor: compositor.rs, taskmgr.rs; SDK/lib/cstd/src/sys.rs.

These are WM/compositor concerns, not PTY logic, but they ship in the same desktop milestone as the terminal fixes.

What Rex asked for next — honest scope

Delivering all of the list below is multi-sprint OS work, not a single patch. This section is the agreed product breakdown so planning and reviews stay grounded.

1. Client library (p1window / p1std)

Shipped (Apr 2026): p1window at SDK/lib/p1window — typed WM v1 client. Consumers: gterm, Surf. p1std at SDK/lib/p1std re-exports p1window (as window) and cstd (as libc) plus SdkError.

Low layer: SDK/lib/cstd/src/wm_v1.rs + p1ui.rs — for p1window and bootstrap IPC; not the supported app-facing API for ordinary windows (see Application SDK — UI policy above).

Work: Harden p1window (resize helpers, more typed errors from compositor codes, optional RAII); migrate Wander / Settings / evnt; add small wrappers in p1std over time.

2. Real input system

Today: Compositor broadcasts type 3 (mouse) and 4 (keyboard, scancode + down flag) to one focused PID; no per-window queues, no repeat/keymap layer in WM.

Targets:

  • Per-surface mailbox or ring buffer in the broker (not a single global vec — see src/sys/ipc.rs SystemBroker).

  • Focus + capture (mouse relative / confine to client rect below title bar).

  • Wheel already has a field in gterm’s mouse p3 — wire it consistently from PS/2 / VirtIO.

  • Optional cooked text path (layout → Unicode) vs raw scancodes (games).

3. Memory management

Today (Apr 2026): Kernel still remaps the sender’s physical pages into the compositor on each qualifying syscall 50 present. gterm allocates large client RGBA with p1_alloc_framebuffer → sys_mmap_anon (syscall 9), not by extending brk beside alloc — see foundation-debt.md §5.3. Ordinary heap may still use brk via GlobalAlloc until a general allocator lands.

Targets:

  • Capabilities: export a shm id from compositor, import in client, single mapping.

  • Damage rects in present (dirty set or RLE) to reduce memcpy.

  • GPU path: deferred until a stable scanout / GL/Vulkan story exists.

4. Compositor features

Targets: Dirty-region compositing, fullscreen/modal layers, clip stack per window, vsync-gated present (or double-buffered GOP) to kill tearing.

5. Shell integration (DPS as first-class)

Today (Apr 2026) — PTY path is live for the dock terminal:

  • gterm (apps/termd): sys_openpty → child sys_pty_rebind_std so dps stdin/stdout are the slave; gterm holds the master. Keystrokes go to the master with sys_write; output is read from the master via sys_read_with_flags(..., READ_FLAG_NONBLOCK_PTY) in poll_dps_output so the WM client never blocks on an empty PTY queue.

  • dps (apps/dps): blocking sys_read on fd 0 (slave) for readline; read == 0 + successful TIOCGWINSZ on fd 0 ⇒ treat as PTY EOF and exit; otherwise short sleep (non-PTY bootstrap).

  • Prompt / output: dps is written for a dumb viewer: plain PS1 by default; gterm still folds CSI/OSC for safe pixels (see PTY v1 design §9).

Still “targets” (not done to POSIX bar):

  • Per-PTY foreground for sys_signal_foreground (global PID today — see PTY v1 A.3).

  • Full job control, SIGWINCH delivery after resize, optional kernel line discipline or shared libline.

  • Init policy: only one interactive consumer of /dev/console unless session multiplexing exists.

Suggested next implementation order

  1. PTY correctness after the pair ships — A.3 per-PTY foreground + sys_signal_foreground scope; Phase C SIGWINCH + WM bounds → TIOCSWINSZ (see PTY v1 design §2.0).

  2. libwindow — removes foot-guns from every app.

  3. Broker mailboxes per PID (or per surface) — removes head-of-line blocking across unrelated messages.

  4. Damage + vsync present — user-visible quality win.

WM / compositor state — beyond Vec<Window> (planned)

Honest label: WindowManager today is a prototype: a Vec of window rows plus z-order and focus. That is not the same thing as a retained scene graph with reference-counted surfaces and a single lifecycle invariant between kernel present maps, IPC, and compositor rows. Closing a window that is not the last one surfaced bugs where teardown order, logging, and row lifetime were not jointly defined — patching one symptom at a time without locking invariants stays brittle.

The move is two tracks at once (not either/or):

Track A — Safe removal on the current shape (short horizon)

Goal: any delete leaves no dangling refs, defined stacking, idempotent purge.

Requirement

Work

No buffer_ptr after kernel unmap

Drop WM row or zero buffer_ptr before / as the present slot goes away; PEER_DIED + title-bar kill paths stay idempotent.

Z-order + focus

One documented rule after every remove: e.g. top = max z_order among non-minimized, tie-break by window_id (or stable node id) so max_by_key never leaves ambiguous focus.

Draw order

Per-frame sort_by_key is acceptable only if invariants hold; if order bugs persist, move to an explicit z-sorted structure (linked list or BTreeMap keyed by (z_order, id)).

Observability

Throttled PRE_WM hides n_win == 1 stalls — use a cheap render-entry tag (e.g. tick) when debugging close races.

Exit: open several terminals; close in arbitrary order; serial + UI show no compositor death and no frame where a row exists for an unmapped present band. Met in Apr 2026 only after PML4[192] cleanup skip, 16 MiB present clamp, translate-all present, deskgui topmost hit-test + kill ordering — see §6 above; run §5.1 in foundation-debt.md before declaring regressions “mysterious.”

Track B — Target compositor model (medium horizon)

Split Surface from Window (frame):

  • Surface: stable surface_id, owner_pid, buffer metadata, explicit lifecycle (Attached / Detaching / dead); compositor-owned surface table (not “whatever the Vec says”).

  • Window / frame chrome: placement, decoration, z-order, focus — references surface_id; does not solely own kernel map teardown.

Kernel: present-map registry moves from (receiver, sender) only toward (receiver, sender, surface_id) or a shm handle so multi-surface-per-PID and symmetric teardown are real (see §3 memory targets above).

Retained scene graph + damage (§4 compositor features): after Track A is green and Track B has a surface table — otherwise the graph inherits the same dangling lifetime bugs.

Ordering

  1. Track A to green — stops embarrassing close-any-window death; proves removal semantics on today’s code.

  2. Track B surface table + map identity — removes the structural class of “Vec row vs kernel map” drift.

  3. Scene graph + damage + vsync — perf and polish; not a substitute for (1)(2).

Small Vec-level fixes remain fine when they cite an invariant from this section and add a manual or scripted close-order check.

Files touched in the latest integration pass

Area

File(s)

WM geometry / hit test / z-order / close

apps/deskgui/src/deskgui/window_manager.rs, apps/deskgui/src/deskgui/window.rs, apps/deskgui/src/api/window.rs

WM destroy + present unmap

apps/deskgui/src/deskgui/compositor.rs, SDK/lib/cstd/src/sys.rs (syscall 224)

Blit stride + present metadata (external buffers)

apps/deskgui/src/api/render_context.rs (draw_external_frame src_stride_px), apps/deskgui/src/deskgui/compositor.rs (WmPresentSurface → wm_v1_set_frame), apps/deskgui/src/deskgui/window_manager.rs (wm_v1_set_frame stores buffer_stride_px), apps/deskgui/src/deskgui/window.rs (buffer_stride_px field), apps/deskgui/src/api/window.rs / pack helpers (stride_px in present meta)

Embedded UI using tight buffers

e.g. apps/deskgui/src/dialog.rs — passes src_stride_px = 0 where the buffer is exactly w × h.

Shared PML4[192] + user cleanup

src/mm/memory.rs (cleanup_user_page_tables), src/mm/memory.rs (spawn_process_pml4 — clone site)

Syscall 50 present map + clamp + translate

src/kernel/syscalls.rs

Present registry + teardown

src/sys/ipc_present_maps.rs, src/kernel/syscalls.rs (sys_kill ordering)

Exec FD inheritance

src/kernel/task.rs, src/kernel/syscalls.rs, src/kernel/shell.rs

IPC recv syscall

SDK/lib/cstd/src/p1ui.rs

Per-core CPU load

src/kernel/syscalls.rs (225), SDK/lib/cstd/src/sys.rs, apps/deskgui/src/deskgui/compositor.rs, apps/deskgui/src/taskmgr.rs

Task manager drag + footer

apps/deskgui/src/deskgui/compositor.rs

gterm I/O, fold, stride, present

apps/termd/src/main.rs, SDK/lib/cstd/src/wm_v1.rs (present helpers as used by gterm)

dps blocking stdin / PTY EOF

apps/dps/src/main.rs

Ramdisk exec fast path

src/fs/vfs.rs, src/kernel/syscalls.rs (earlier commit)

How to show Rex “what happened”

  1. WM v1 is real — protocol in api/window.rs, compositor decode in compositor.rs, clients on syscall 50/51.

  2. Two hard bugs — outer-frame clamp (black client) and exec FD inheritance (shell I/O).

  3. The rest is roadmap — libwindow, full POSIX job control / per-PTY signals, damage, per-surface input queues: scoped above so nobody pretends the entire desktop shipped in one refactor (PTY pair + dock path did land — see §5).

When QEMU is rebuilt with these changes: expect visible line input, DPS output after Enter, correct RGBA blitting when the client buffer is wider than the window, draggable task manager, per-core busy hints in the HUD, and fewer “wrong offset” / escape-garbage artifacts in gterm. Tearing and multi-second cold launch still need the compositor / vsync / damage follow-ups above.

Follow-up (same week — concrete latency + IPC)

  • sys_execve user stack: mapping 512 × 4 KiB pages per launch dominated dock latency. Default is now 96 pages (~384 KiB); paths whose bytes contain surf, doom, or wander (case-insensitive) still get 384 pages (~1.5 MiB) for heavy UI ELFs.

  • p1_ipc_recv / p1_getpid / p1_ipc_send inline asm: int 0x80 can clobber rcx/r11; missing lateout clobbers can corrupt caller state and break IPC handling in subtle ways. Clobbers were added; preserves_flags was dropped on these paths.

  • gterm IPC recv target: matches init/Surf — p1_ipc_recv(0, …) so the kernel always resolves “receive for current task” the same way.

Doc maintenance (WM / multi-window)

  • Index of platform docs: documentation-map.md.

  • Before merging changes to spawn_process_pml4, cleanup_user_page_tables, syscall 50 present arm, ipc_present_maps, draw_external_frame / present stride, or window_manager close/hit-test: run foundation-debt.md §5.1 and skim §5.2 so serial traces stay interpretable.

  • Do not revert the PML4[192] skip or the 16 MiB page clamp without a replacement design recorded in this file + foundation-debt §3.5.6.

  • PTY + terminal display contract: PTY v1 design and Terminal stack; keep aligned when changing sys_read flags, fold_pty_*, or present stride.