Window Manager
The window manager (/bin/wm.elf, src/usr/wm/) is a userspace compositor process. The kernel provides present/compose syscalls and event plumbing; the WM owns window metadata, decorations, focus, overlays, the cursor, and every frame submitted to the display.
Module Layout
Headers are layered: wm_types.h (POD structs/constants) -> wm_rect.h (rect math) -> subsystem headers (wm_metrics.h, wm_damage.h, wm_window.h, wm_input.h, wm_render.h, wm_settings.h) -> pipeline/feature headers (wm_present.h, wm_overlays.h, wm_main.h). Pure rect/queue/present-policy math shared with the kernel ktest suite lives in include/wm/interaction_policy.h.
| Module | Contents |
|---|---|
wm_main.cpp |
main(): bootstrap + per-frame pipeline orchestration (no shared state definitions) |
wm_bootstrap.cpp |
Registry allocation, swapchain setup, shell window seeding, first frame, registry publish |
wm_events.cpp |
Event pump and dispatch (pointer/keyboard/scroll, overlay modal handling) |
wm_registry_sync.cpp |
Settings/storage/wallpaper/notification generation consumption |
wm_adopt.cpp |
Window adoption scan, failure tombstones, dead-owner reaping |
wm_commit.cpp |
Entry sampling, buffer remap/validation, metadata sync, damage pop |
wm_frame_build.cpp |
Dirty-set normalization, overlay damage expansion, compose pass, scene alias state |
wm_present.cpp |
Present slots, stale tracking, display events, submit/idle/wait policy, frame sequences, scene/display globals |
wm_cursor.cpp |
Hardware cursor planes, software-cursor damage bookkeeping |
wm_stats.cpp |
TSC timing, frame stats, benchmark driver, stats overlay drawing |
wm_damage.cpp |
Dirty-rect queue (enqueue/normalize/clip/collapse), dirty-set queries |
wm_window_geometry.cpp, wm_window_cache.cpp, wm_window_stack.cpp, wm_window_bounds.cpp, wm_window_resize.cpp, wm_window_scroll.cpp, wm_window_damage.cpp |
Window bounds/visibility predicates, visibility caches, focus/z-order/lifecycle, move/snap, synchronous resize protocol, content scrolling, damage marking |
wm_hit_test.cpp, wm_client_events.cpp, wm_pointer.cpp |
Hit testing, client event delivery, move/hover/cursor-kind tracking |
wm_pixel.cpp, wm_wallpaper.cpp, wm_shell_blur.cpp, wm_decoration.cpp, wm_client_draw.cpp, wm_blur.cpp, wm_compose.cpp |
Pixel math/SIMD blits, wallpaper + desktop base, menubar/dock blur, decorations, client blit, blur kernels, rect composition |
wm_index.cpp, wm_control_center.cpp, wm_context_menu.cpp, wm_storage_prompt.cpp, wm_notifications.cpp, wm_actions.cpp |
Overlay features: each owns its state, logic, and clipped drawing |
wm_settings.cpp, wm_metrics.cpp |
Runtime settings load/persist, scaled metrics |
Shared Registry
The protocol is a shared-memory Registry (include/uapi/gui.h) — no sockets or message passing:
- The WM allocates the first shared-memory block at boot (id 0), maps it, and calls
SYS_GUI_REGISTER_WM(which records the WM pid and boosts its priority). - Clients map block 0 and spin until
magic == REGISTRY_MAGIC(0x52454749).
The registry carries: mouse state, menubar/dock canvas block ids and click flags (the menubar also publishes its live date-button left edge as mb_cc_zone_x so the WM's control-center fast path matches the drawn button), plus cp_toggle_requested, which any shell component can set to make the WM toggle the control center, focus (window index + owner pid), theme mode, settings generation + system flags, network/animation/transparency/volume settings, storage mode + request generation, wallpaper generation/status/requested/active paths, a toast-notification request slot (notify_title/notify_message + notify_generation, written by gui_notify and consumed once per generation by the WM), an app open-request slot (open_path + open_generation, written by gui_open_request_submit before a launcher fork/execs a viewer and taken once at startup by gui_open_request_take, which clears the generation; single slot, a new request overwrites a pending one), the focused app's published MenuModel (menu_model, seq-stability protocol, rendered by the menubar), a shared text clipboard (clipboard + clipboard_len + clipboard_seq), the work area (work_x/work_y/work_w/work_h: screen minus menubar and dock, published once at bootstrap — libgui centers a new window's initial placement in it, with a small per-slot cascade, instead of the old fixed (100, 100) origin), window count, and 32 WindowEntry slots. Each WindowEntry also carries menu dispatch fields (menu_command_id + menu_command_seq) written by the menubar and polled by the owning app.
Windows
Slots 0 and 1 are reserved for the menubar and dock; user windows take slots 2-31 (MAX_WINDOWS = 32).
Registration (client side). gui_register_window_ex:
- Claims a slot with an atomic CAS on
shm_id. - Creates backing:
memfd_create+ftruncate(w*h*4)+mmap(MAP_SHARED). - Transfers the memfd to the WM with
SYS_FD_TRANSFER(pid 0 resolves to the WM pid). - Publishes
shm_id = wm_fd | 0x40000000(memfd tag), owner pid, geometry, generation counters, then setsreadybehind store fences.
Adoption (WM side). Each frame the WM scans ready entries and adopts valid ones: it validates the buffer size against SYS_FSIZE (a memfd mapped past EOF would fault the compositor), clamps dimensions, and maps. Adoption failures leave tombstones (keyed by shm_id + owner_pid, 5000-tick expiry) to stop retry storms. An unadopted entry whose owner is already dead (crash, kill) is reset in place — ready cleared, slot returned to WIN_SHM_INVALID, damage zeroed, tombstone dropped — so it stops burning syscalls and cannot poison the adoption key of a relaunched app that reuses the same fd and pid.
Validation hardening. Window entries are sampled twice across load fences and accepted only when bitwise stable (bounded retries). Buffer dimensions are clamped to 8192. Dimension growth is re-validated against SYS_FSIZE/SYS_SHM_INFO and forced back on violations; SysV blocks must be owned by the entry's owner pid. Titles are copied with forced NUL termination. Liveness is checked with kill(pid, 0) every 30 frames; dead owners' windows are closed without killing recycled pids.
Window Chrome and Unified Headerbar
Windows are seamless: the client buffer is the whole window face and there is no titlebar strip above it. The edge is a single 1-px semi-transparent hairline — gui_window_outer_stroke_color, 0x24000000 (14% black) in the light theme and 0x26FFFFFF (15% white) in the dark theme. A low-alpha stroke is used rather than an opaque mid-grey because an opaque grey reads harsh and muddy against wallpapers, whereas a translucent stroke darkens (light theme) or catches light on (dark theme) whatever sits behind it and stays crisp over any wallpaper. There is deliberately no inner rim: a second ring read as a 90s bevel/halo, so the edge is one razor-thin line. The shadow does the heavy lifting for separation; it is the same gui_draw_panel_shadow every floating surface uses: a soft symmetric ambient shadow computed from a rounded-rect distance field, sampled at pixel centres, blended only over the rim between the silhouette and the padded outer bounds (the body covers the interior), with a continuous (1-t)^1.5 falloff over the gui_panel_shadow_pad() spread (16 px base, scaled) and a per-theme peak alpha (light 0x48, dark 0x55 — dark runs slightly hotter to read equal against a dark backdrop). To give the glow room on every side, the window's outer bounds extend symmetrically by the shadow pad (wm_frame_shadow_offset_y()), and the decoration cache centres the window inside that pad. The shadow is translucent, so overlay redraws that run once per damage rect must use the _clipped variants (gui_draw_panel_shadow_clipped, gui_draw_popup_menu_clipped) to blend only inside the recomposed rect — an unclipped redraw accumulates on every extra rect (cursor movement, hover). Menubar dropdowns are the one exception: they attach flush to the bar (menu_y() == menubar_h(), no top shadow — the menu hangs off the bar like a single surface), and the menubar canvas budget below the menu is the full shadow pad. The client blit is inset by the one-pixel edge (wm_frame_body_inset() = 1 px) on all four sides, leaving the hairline visible all the way around — top edge included — with concentric corner masks, and mouse events are translated by the same inset so client pixel (0,0) maps to the first pixel inside the frame. The edge and shadow are rendered once into the cached decoration frame, so composition stays a cheap blit. The cache is rebuilt on theme, size, buffer-remap or title change (invalidate_window_decoration_cache). The chrome tint is the client's own background, sampled from its buffer; that tint is locked after the first opaque sample (decoration_bg_locked) and released again by any invalidation. A theme switch additionally releases every window's tint and keeps re-sampling the live buffer for a short window (decoration_resample_until), so the chrome follows each app's re-themed canvas before locking the final color. Locking matters during launch: a fresh buffer transitions from the zeroed mmap through several progressive frames, and re-sampling the tint on every commit would rebuild the shadow cache — the expensive part of the chrome — over and over, stalling the compositor. Until the tint is locked the soft shadow is skipped in the cache path (flat hairline chrome is presented instead), so the window appears immediately and the shadow renders once the client's first opaque frame lands; the lock flipping re-damages the frame so the shadow appears without waiting for input. An active resize bypasses the cache and re-locks the tint from the resize snapshot first, so the shadow stays on screen through the drag; the shadow is drawn clipped to the damage rect on that path. A theme switch additionally schedules a full-screen redraw right after the settle window (wm_sync_registry), because the re-lock only happens inside a compose pass. The recipe is shared by the whole desktop — gui_draw_window_frame (opaque body + edge), gui_draw_window_frame_no_rim / gui_draw_window_rim (for surfaces that draw content flush to the edge) and gui_draw_window_ring (glass bodies) — so popup menus, dialogs, the control center, notification center, launcher, storage prompt, the dock and every app window are pixel-identical.
The top gui_headerbar_h() pixels of the client (44 px base, clamped 40-48 with UI scale) form the unified headerbar band. The WM overlays the traffic lights (close/minimize/maximize, macOS colors) at the band's left — inset x 14, size 13, spacing 21, vertically centered in the band — after the client blit, so apps lay out against gui_traffic_lights_w() (cluster right edge, 69 px base) without ever drawing the buttons themselves. Apps place toolbar controls inside the band or start content below it; no app paints its own title strip, and there is no divider under the band.
Hit-testing inside the band cascades: traffic-light buttons, then resize grips, then the client's header input rects, then the drag zone. A mouse-down in the drag zone moves the window (double-click toggles maximize, ABA-guarded, the pair reset by any non-titlebar click); the hover cursor shows the move arrow outside interactive regions. A right-click in the drag zone (outside header input rects and traffic lights) opens the window context menu — maximize/minimize/close plus Settings and Storage Mode — GNOME-style; right-clicks on header controls and body content are forwarded to the client. Clicks that land in the shadow rim between a window's silhouette and its outer bounds fall through to lower windows instead of dead-ending (which would also spuriously defocus). Hover feedback (traffic-light highlight, resize and move cursors) follows the topmost window actually under the pointer — including a visible background window — not merely the z-top.
Menubar menu tracking is modal: while a menubar dropdown is open (the menubar window is grown past its 36 px bar), any click outside it sets mb_menu_dismiss_requested and is consumed — it does not also raise a window or press a control underneath. The same yield applies when the WM's control-center fast path toggles the center from the menubar's date button. That button's live left edge is published in mb_cc_zone_x (screen space, 0 = not yet drawn) so the fast path matches the drawn button exactly; the menubar also processes the click itself through mb_clicked when the fast path does not apply.
Header input regions. Interactive controls an app places in the band (toolbar buttons, fields, segmented controls) are exempted from the drag zone via WindowEntry.header_input_count + header_input[8], written by gui_window_set_header_input in client canvas coordinates behind the header_input_seq fence protocol (seq bumped before and after the payload; the WM snapshots with bounded retries). The WM hit-tests them by mapping the pointer into canvas space (minus the frame inset, plus the scroll offset), so sticky controls of scrolled windows stay clickable. A click in a header input rect focuses the window and is forwarded like any client click (with the pointer grab); hovering one keeps the normal arrow cursor. Windows that publish nothing (e.g. Terminal) get a fully draggable band.
Maximize. A maximized window fills the work area between the menubar and the dock, keeping the same desktop-margin gap on all four sides, and keeps its rounded corners, frame and shadow; restore returns to the saved geometry. Maximize/restore supersede any resize configure posted for the previous state (cancel_window_resize_configure), and a titlebar drag on a maximized window restores it under the pointer and tracks the restored size even before the restore configure acks, so it cannot be left stuck at the maximized geometry.
Resize Protocol
For resizable windows:
- WM publishes the target geometry into the entry, writes
resize_serial, and posts anEVT_WINDOW_RESIZEevent with the size and serial. Only one configure is outstanding per window at a time: while one is pending the newest pointer target is queued and published right after the ack lands, so the client is never asked for two sizes at once. - Client grows its backing in place when needed (new memfd + transfer; capacity grows with slack, so a drag reallocates only occasionally) and redraws. Growth swaps the shared window surface's buffer pointer, so clients must re-read it before drawing — a cached
Surfacecopy keeps pointing at the unmapped old backing and faults on the next fill. - Client acks by publishing
buffer_resize_serial— the exact serial it synced to and rendered, not the newest serial in the entry, which a fast drag may have advanced past the frame just drawn. The WM resends the outstanding configure if the client goes quiet. - Retired buffers are released when the WM's acknowledge generation reaches theirs.
Resize is synchronous and flip-based. The visible bounds never move ahead of what the client has drawn: when a configure is posted the WM records the pending geometry and keeps the window at its last committed size, presenting a WM-owned snapshot of the last committed frame (resize_snapshot, captured at configure post, freed on close) because the client is actively overwriting the shared backing during the redraw window. When the client acks, the WM flips the bounds to the pending geometry in one step so backing and bounds land in the same frame, then publishes the next queued target if the pointer moved on. Content is therefore only ever blitted 1:1 - never scaled, stale, or partially drawn - and the window follows the pointer at the client render rate, like a toolkit live resize. The same path serves interactive edge-drags, maximize, tiling, and client-requested resizes. There is no stretch renderer and no committed-size tracking; the blit clamps to the mapped backing and scrolled (content-sized) windows read their visible slice via the scroll offset. Shell surfaces (menubar, dock) bypass the configure protocol and redraw their whole entry, so their size tracks the registry geometry directly; this lets the menubar grow its window to expose the system dropdown.
Focus and Z-Order
Z-order is the window array order; focusing raises. SYS_GUI_SET_FOCUS (WM-only) records the focused pid and boosts its scheduling priority. Alt+Tab cycles visible user windows; the mouse wheel over empty desktop rotates stacking; titlebar double-click toggles maximize (ABA-guarded).
On every focus transition the WM posts EVT_FOCUS/EVT_UNFOCUS to the affected owners, posts EVT_MOUSE_LEAVE when the pointer leaves a client area (or a drag/overlay takes over), and posts EVT_WINDOW_SCROLL when it scrolls a window's content — and when a configure ack or manual resize clamps the scroll offset to the new bounds, so the client redraws sticky panels at the corrected position instead of leaving them off-canvas until the next interaction. The wheel scrolls WM-side content first (a content-sized window) and is forwarded to the client only when there is nothing to scroll; horizontal wheel (scroll_x) is sign-inverted versus vertical — a positive wheel-y reveals earlier content (offset decreases), a positive wheel-x (tilt right) reveals later content (offset increases), matching the scroll-view widget. A mouse-down delivered to a client grabs the pointer for that window until the matching release, so moves and the release reach it even outside the frame. See Input.
Damage and Composition
- Clients push damage rectangles into a lock-free SPSC ring (8 entries) per window.
- The WM pops damage, offsets it into screen space, intersects it with visible bounds, and feeds the dirty-rect queue (128 rects). Normalization clips, merges touching/overlapping rects, applies a union heuristic, and collapses to bounds when the interactive/non-interactive limit is exceeded.
- Per dirty rect, composition paints bottom-up: wallpaper (skipped when an opaque window covers the rect), user windows with decorations, system windows (menubar, dock), overlays (context menus, storage prompt, launcher, control center, toasts), then the cursor.
- Frames submit via
SYS_DISPLAY_COMPOSE_SUBMIT(single opaque layer + damage + frame sequence + vblank present + hardware cursor) or the copy path. Up to 2 presents are in flight across 3 buffer slots; completion is tracked from display events, and present policy (submit/wait/skip) gates the frame loop. Idle frames sleep ~16 ms. - Identity-alias mode: on copy-path backends advertising
DISPLAY_FLAG_SYNCHRONOUS_PRESENTthe scene buffer is created as the single present buffer, so compositing draws directly into the buffer the kernel copies to VRAM and the scene→present blit is skipped. The software cursor then bakes into the scene; each frame prepends the previous baked cursor rect to the damage set (composed before any window-move pixel shifts) and re-bakes the cursor after compositing. Any allocation failure falls back to the two-buffer path. - The cursor uses a hardware cursor when the compositor backend supports page flips, otherwise a software cursor rendered into the present buffer. Cursor movement only adds damage on the software path — the hardware plane draws out of band, so damaging every move would inflate the dirty set (and trip the resize collapse heuristic).
Wallpaper, Blur, Overlays
- Wallpaper: the
.uowppath comes from/data/WALLPAPR.CFG(fallback/etc/wallpaper.confor the registry request); the theme variant is loaded, scaled cover-style to the screen, and published in the registry. Default:/usr/share/wallpapers/default.uowp. - Blur: the WM owns menubar/dock blur surfaces, regenerated on backdrop changes.
- Scratch surfaces (
g_blur_scratchand friends, viaensure_surface_capacity) reuse their allocation across region sizes: capacity is tracked on theSurface(capacity_w/h), not the last logical size, so alternating region sizes no longer destroy and re-malloc the buffer (which returned whole heap regions tomunmapseveral times per frame). - Backdrop rects are captured from the composed desktop with attribution: only user-window content damage and geometry transitions (moves/closes/adds capture their own old+new bounds) re-capture the shell strips — the shell apps' own redraw damage must never feed back, or generation bump → shell redraw → damage → re-blur loops forever at frame rate. Dirty rects are re-clamped against the current shell geometry before recomposition, so a moved/resized shell never blurs a stale slice; capture is suspended while a window drag or resize is in flight and refreshed once it settles. Each regeneration runs the material pipeline on the captured strip: an edge-replicated box downsample (4x, falling back to 2x), three SSE2 separable box-blur passes approximating a Gaussian (sliding-window sums, fixed-point reciprocal multiply, no per-pixel division; a scalar fallback covers radii beyond the u16 sum range), a saturation/brightness material adjustment fused into the final pass (saturation > 100% so wallpaper color stays vibrant through the glass), and a center-aligned bilinear upsample back to native resolution. The result is written into the
mb_blur_shm_id/dk_blur_shm_idblocks and published by bumpingmb_blur_generation/dk_blur_generationatomically after the pixels land. Debug builds (orSYSTEM_FLAG_WM_PIXEL_SELFTEST) run a blur self-test that checks the SIMD kernels against a scalar reference and constant-color invariance. Blur runs on page-flip backends and on copy-path backends that still advertise a compositor (DISPLAY_FLAG_HAS_COMPOSITOR, e.g. the UEFI GOP framebuffer used on real hardware); it is only suppressed on a truly compositor-less copy path, where there is nothing to amortize the translucent shell surfaces against. - Overlays: the Index launcher (app catalog + system actions, fuzzy scoring), control center, right-click menus, storage mode prompt, and toast notifications (4 s, 32-slot ring; expiry is pumped from the main loop every iteration —
wm_pump_notification_expiry— so toast lifetime does not depend on frame damage, which an idle desktop with the hardware cursor plane would otherwise never produce).
Debug Telemetry and Benchmarking
The WM keeps a WmFrameStats block updated during the frame loop: built/submitted/skipped frame counts, dirty rect count and area, full vs clipped repaints, stale-slot repairs, cursor path counts, and TSC-timed compose/present/frame durations plus input-to-submit latency.
SYSTEM_FLAG_SHOW_DEBUG_STATSrenders a mono-font overlay (below the menubar, left) showing last/max frame time, compose/present ms, input-to-submit latency, damage kpx + rect count, and the kernel's last VRAM copy pixels/ticks fromDisplayStatus.SYSTEM_FLAG_WM_BENCH_DRAG/SYSTEM_FLAG_WM_BENCH_RESIZErun a scripted 1000-frame benchmark: the WM drives synthetic window translation (drag) or geometry oscillation (resize) on the first visible user window, measures compose/present/frame costs and dirty area, logs a summary line, restores the window, and clears the flag. The resize mode routes every step throughset_window_bounds, so it exercises the real synchronous configure/ack/flip pipeline; the summary additionally reportsresize flips(ack-driven geometry flips) andstale(acks for superseded configures) counts. No manual input is needed, so deltas are reproducible on hardware.SYSTEM_FLAG_WM_PIXEL_SELFTESTruns the libgui SIMD pixel-op self-test and the blur-kernel self-test at startup and logs PASS/FAIL for each. Debug builds always run them at boot. See Userspace runtime.
Settings
The WM loads settings from /data/SYSTEM.CFG (fallback /etc/system.conf), applies them, and persists changes during idle frames. Settings propagate live to clients through the registry's settings_generation counter. See Runtime configuration.