From 5437b5e33c542c801bdc557be2ae93bfec6e153d Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sun, 14 Jun 2026 21:04:51 +0900 Subject: docs: plan RML compositing direction + roadmap re-scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major architecture direction (gated by a spike): make the RMLUi substrate the content compositor — toplevels, layer-shell clients (incl. wallpaper), and UI chrome become RML elements backed by live, shared GL textures, with layout, animation, and 3D effects expressed in RCSS. wlroots stays the foundation + hardware cursor plane + a deferred fullscreen-video scanout bypass. Lost wlr_scene damage/scanout is mitigated by our own dirty-gated rendering (not a RMLUi built-in) + the deferred scanout bypass. - notes/rml-compositing.md: full architecture, the de-risking already done (shared EGLDisplay; RmlUi transform-aware hit-testing + RTT/filters/shaders; slice 3 + stage dock proved dmabuf/EGLImage), division of labour, perf posture, Phase 0 spike acceptance criteria, Phase 1/2, naming proposal. - notes/plan.md §2: decision row (reopens the compositing half of row 51) with rationale + explicit spike fallback. - tasks.md: slice 13 (RML compositing spike) = ACTIVE core; slice 7 (tiling) DEFERRED behind it; "Now" leads with the pivot. Roadmap re-scope (earlier this session): - notes/tiling-spec.md: baseline tiling design (primary/stack, configurable side + new-window insertion, hot-reloadable [tiling] config) — deferred. - notes/tiling-layouts-reference.md: dwm layout catalogue for later. - notes/status-bar-home-screen.md: status bar + iPad-style home screen ideas (replace the cut window-list taskbar); tasks.md slices 6 (cut), 11, 12. --- notes/plan.md | 1 + notes/rml-compositing.md | 129 ++++++++++++++++++++++++++++++ notes/status-bar-home-screen.md | 79 ++++++++++++++++++ notes/tiling-layouts-reference.md | 78 ++++++++++++++++++ notes/tiling-spec.md | 164 ++++++++++++++++++++++++++++++++++++++ tasks.md | 28 ++++++- 6 files changed, 475 insertions(+), 4 deletions(-) create mode 100644 notes/rml-compositing.md create mode 100644 notes/status-bar-home-screen.md create mode 100644 notes/tiling-layouts-reference.md create mode 100644 notes/tiling-spec.md diff --git a/notes/plan.md b/notes/plan.md index db92d58..b0ea7cd 100644 --- a/notes/plan.md +++ b/notes/plan.md @@ -68,6 +68,7 @@ solves), and the trigger that would reopen it. | **VT switching (Ctrl+Alt+Fn) is kernel-hardwired** before the key_filter (`wlr_session_change_vt`) | It is the session escape hatch — must work even if an extension throws or greedily consumes keys; not a rebindable feature (user decision) | — | | **Stage dock** (ext-stage-dock, standard) = the Stage-Manager-style left-edge dock of minimized-window **previews**, revealed by a left-edge **swipe**. **Fork B**: previews are toplevel snapshots imported as textures INTO the ui substrate's RMLUi context and shown as `` in ONE RML document | Closest to the iPad Stage Manager north star; one ui surface animates as a unit via RCSS; reuses the slice-3 dmabuf/EGLImage bridge in reverse (wlr pixels → RMLUi texture) instead of two-layer scene/RML lockstep | Cross-context texture import proves infeasible on crocus → fall back to Fork A (previews as `wlr_scene` snapshot nodes) | | **Mechanism in kernel/core, policy in ext-stage-dock.** Kernel ui substrate gains: preview-snapshot, list/container bindings, a gesture-CLAIM input path. ext-xdg-shell gains: `Toplevel::hide()/show()` (≠ unmap), `geometry()`, `scene_tree()`. ext-stage-dock owns: the "minimized" set, dock layout, gesture recognition, easing | Keeps "kernel names no feature" — snapshot/claim/list-bindings are generic primitives; minimize-to-dock is the only policy and lives in one standard extension | — | +| **RML compositing (DIRECTION, gated by a spike — reopens the compositing half of row 51).** RMLUi becomes the CONTENT compositor: toplevels, layer-shell clients (incl. wallpaper), and UI chrome are all RMLUi elements backed by LIVE, SHARED GL textures (zero per-frame copy via the existing shared EGLDisplay). Layout/animation/3D effects are RCSS. wlroots stays the foundation + plane manager (backend/output/renderer/seat, hardware cursor plane, fullscreen-video scanout bypass). Lost wlr_scene damage/scanout is mitigated by OUR dirty-gated rendering (idle ≈ no work) + a deferred fullscreen scanout bypass. Full design + spike acceptance criteria: `notes/rml-compositing.md` | The appeal is Hyprland-grade window effects via DECLARATIVE RCSS (layout, transitions, 3D transforms, blur/shaders) with hot-reload — and RmlUi already does transform-aware hit-testing + RTT/filters, while slice 3 + the stage dock already proved dmabuf/EGLImage texture handoff on this exact crocus HW. Doing windows in RMLUi means tiling/effects/stage-manager are all RCSS policy on top | The spike fails on the CF-AX3 (no zero-copy shared-texture path, input-routing dead-end, surface-tree composition impractical, or idle/perf out of budget) → fall back to wlr_scene compositing + transient snapshot-through-RMLUi effects | ## 3. Architecture diff --git a/notes/rml-compositing.md b/notes/rml-compositing.md new file mode 100644 index 0000000..ada1b8b --- /dev/null +++ b/notes/rml-compositing.md @@ -0,0 +1,129 @@ +# RML compositing — architecture direction (gated by a spike) + +> **Status: DIRECTION CHOSEN, gated by a GO/NO-GO spike.** This reopens the +> compositing half of plan.md §2 row 51 (wlr_scene for compositing). Nothing is +> committed until the spike (Phase 0) passes on the real CF-AX3. Naming below is +> PROPOSED — needs user sign-off before it lands in GLOSSARY.md. + +## Thesis +The kernel's UI substrate (RMLUi) becomes the **content compositor**: application +toplevels, layer-shell clients (wallpaper, panels), and the existing UI chrome +are all RMLUi elements backed by **live, shared GL textures** — so window +**layout, animation, and 3D effects are expressed in RCSS**, with no per-frame +texture copies. Tiling, stage-manager, effects, etc. become RCSS + extension +policy on top of this. + +## Why this is viable (the de-risking already done) +- **Shared EGLDisplay already exists** (plan.md §2 row 51): the wlr renderer and + RMLUi's GLES 3.2 context share an EGLDisplay. Slice 3 + the stage dock already + move client/scene pixels into RMLUi as textures via dmabuf/EGLImage on this + exact Haswell/crocus HW — the texture handoff is proven; the spike's job is to + make it **live + zero-copy + shared-handle** rather than snapshot. +- **RmlUi does transform-aware hit-testing for us.** `Context::ProcessMouse*` / + `ProcessTouch*` pick the element under a point THROUGH `transform`/3D + `perspective` and dispatch DOM-style events + `:hover` (auto-updated in + `Context::Update`). So the hard geometry of routing input through a 3D-tilted + window is handled upstream — we only translate "element X at local (lx,ly)" to + `wl_seat` (surface-local coords + implicit grab), which the kernel already + models. +- **RmlUi GL3 renderer supports** transforms, clip masks, **filters (blur, + drop-shadow)**, **shaders**, and **render-to-texture** — and caches compiled + geometry + effect passes (an unchanged blur isn't recomputed). Our custom + RenderInterface must implement these hooks (the "custom is fine" work). + +## Division of labour (the end state) +- **RMLUi = content compositor.** ALL on-screen content: toplevels, layer-shell + clients (incl. wallpaper — user decision), UI chrome. Layout/animation/effects + in RCSS. +- **wlroots = foundation + plane manager** (NOT optional; RMLUi can't talk to + DRM): + - backend/DRM, output management, modeset, vblank/frame scheduling, GLES + renderer, buffer import (dmabuf→texture), allocation, `wl_seat`/input; + - the **hardware cursor plane** (drawing the cursor in RMLUi would force a full + recomposite on every move — keep it a wlr plane); + - the **fullscreen-video scanout bypass** (deferred optimization — see below). +- This is NOT "hybrid compositing": in steady state RMLUi composites everything + and `wlr_scene` is reduced to **present the RMLUi buffer + cursor plane + + scanout bypass**. `wlr_scene` may stay as that thin presenter (it can even + scan out the RMLUi buffer itself on the primary plane). + +## Performance posture (replacing wlr_scene's damage/scanout) +What we give up by moving content off `wlr_scene`: per-surface damage, occlusion +culling, and direct scanout. Mitigations: +- **Dirty-gated rendering (OUR mechanism — NOT a RMLUi built-in).** RMLUi tracks + internal dirty flags (so `Update()` is cheap at idle) and knows when animations + are active, but it does NOT provide screen damage or a "skip this frame" + signal — and the most important dirty source here (a client buffer updating) is + OUR shared texture changing OUTSIDE RMLUi, which RMLUi can't see anyway. So WE + gate: only schedule + `Render()` a frame when a signal we already own fires — a + client buffer commit (wlroots), an active RCSS animation (RMLUi tells us), or an + input-driven state change (hover/focus/drag). This keeps a static desktop at + ~zero GPU — the big battery/thermal win on a 15 W fanless ultrabook — WITHOUT + per-surface damage. (`request_frames` already stops scheduling at rest; we gate + on dirtiness on top.) The spike must CONFIRM idle ≈ no work in practice. +- **Fullscreen-video scanout bypass (deferred).** When exactly one window is + fullscreen with nothing composited on top, pull that surface OUT of the RMLUi + composite and hand it to `wlr_scene`/scanout directly (RMLUi draws nothing that + frame). Trigger = the fullscreen STATE (e.g. VLC's fullscreen button), not the + click. Impact of NOT having it: more battery/heat during long fullscreen video, + not breakage — so it's a later optimization, sized by a spike measurement. +- What we lose and accept for now: partial-region redraw when one small thing + changes (minor; the HD4400 can repaint 1080p of simple quads within budget). + +## Phase 0 — THE SPIKE (kernel/substrate; throwaway; one GO/NO-GO) +Acceptance criteria, measured on the real CF-AX3: +1. A **live** toplevel buffer sampled by RmlUi via a shared GL context — **zero + per-frame copy** — drawn as an element. +2. An **RCSS 3D transform + transition** applied to it (visual proof of payoff). +3. **Pointer + touch + keyboard routed back** to that client through RmlUi + picking → `wl_seat`, correct under a transform. +4. A toplevel **with a popup + subsurface** composited correctly → decides the + surface-tree question: **per-subsurface elements** vs **per-window + render-to-texture**. +5. A **layer-shell client (wallpaper)** also rendered as an element (proves the + "wallpaper through RMLUi" decision; same mechanism as #1). +6. **Perf**: ~4 windows @1080p incl. one continuously updating (terminal/video); + measure frame time AND confirm **idle = ~no work** with dirty-gating on; also + measure the cost of pushing fullscreen video through RMLUi (to size the + scanout bypass). +7. **Present path**: reuse the existing RMLUi-FBO → `wlr_scene_buffer` bridge + (fastest to truth); cursor stays a wlr plane. + +Output: report + GO/NO-GO + chosen answers to (4) and the present-path, + the +perf numbers. + +## Phase 1 — Architecture (if GO): a design doc settling +- The **surface-element model** + surface-tree handling (from spike #4). +- The **shared-texture handoff** API in the substrate. +- **Unified input**: fold the kernel's pointer/touch routing into RmlUi picking + (dovetails with the existing "substrate gets input first" implicit-grab path). +- The **extension-facing contract**: how a future tiling/effects/stage-dock + extension places & animates windows — RCSS docs + data bindings (existing + substrate contract) vs. a new typed window-layout service. +- Update plan.md §2 row 51 to the new compositing model. + +## Phase 2 — Implementation (phased, behind a flag; session stays usable) +substrate: surface-element + shared-texture handoff → kernel: route toplevels + +layer-shell into the substrate instead of `wlr_scene`; switch input → port +focus/move/resize/fullscreen → re-express stage-dock minimize as RCSS → revisit +tiling (now trivial: RCSS layout over surface elements) → effects. + +## Proposed naming (NEEDS SIGN-OFF before GLOSSARY.md) +- **RML compositing** — the approach: RMLUi composites all on-screen content. +- **surface element** — an RML element backed by a live client surface's shared + texture (a toplevel OR a layer surface presented inside RMLUi). Uses the + canonical "surface"; avoids the "window" alias. + +## Open questions the spike resolves +- per-subsurface elements vs per-window render-to-texture (the #1 unknown); +- present path: reuse FBO→scene_buffer vs eventually render direct to output; +- real perf headroom on the HD4400 (frame time + idle + video). + +## Relationship to other work +- **Tiling is deferred** and becomes much smaller on top of this (RCSS layout + over surface elements; the pure layout core in `notes/tiling-spec.md` still + applies — it's renderer-agnostic). +- The **stage dock** already prototypes the texture-import half (frozen + previews); its minimize/restore becomes RCSS on the new path. +- Status bar / home screen (slices 11–12) are RMLUi chrome already — they fit + natively. diff --git a/notes/status-bar-home-screen.md b/notes/status-bar-home-screen.md new file mode 100644 index 0000000..52d1a9a --- /dev/null +++ b/notes/status-bar-home-screen.md @@ -0,0 +1,79 @@ +# Status bar + Home screen — early ideas (DRAFT, NOT yet designed) + +> **Status: IDEAS ONLY — needs fleshing out.** Nothing here is a committed +> design. Every section below has open questions that must be resolved (and +> user-signed-off) before implementation. These two features **replace the cut +> window-list taskbar** (old slice 6) and are **sequenced AFTER slice 7 +> (tiling)** — see `tasks.md` slices 11–12. +> +> Unit names (`ext-statusbar`, `ext-home`) are **tentative**; a canonical name +> needs a `GLOSSARY.md` entry with user sign-off (no synonym coinage rule). +> +> Rationale for replacing the taskbar: this is a touchscreen CF-AX3 with an +> iPad-Stage-Manager north star. A conventional window-list taskbar overlaps the +> stage dock and costs scarce screen + GPU. What's genuinely missing is (1) +> system status and (2) an iOS-style app launch surface — these two items. + +## 1. Status bar (tentative: `ext-statusbar`) + +An iPad/iPhone-style bar pinned to the **top edge** of the screen. + +- **Left:** clock. +- **Right:** a system **tray** — wifi, volume, battery. +- **Left / middle / right** sections are **configurable** (which items appear and + in what order), presumably via `unbox.toml`. + +### Open questions / to flesh out +- Item model: a fixed set vs a plugin/registry of "status items" extensions can + contribute. How does config reference them (typed, not string-keyed)? +- Data sources: battery (sysfs/UPower?), wifi (NetworkManager/iwd via D-Bus?), + volume (PipeWire/WirePlumber — already in the session per `start-unbox`), + clock (local). Each is an effect at the edge; keep pure formatting cores. +- Surface: a `wlr-layer-shell` top-anchored surface with an **exclusive zone** + (reserves top space; tiling/usable-area must account for it — ties into the + ext-layer-shell usable-area model already built). +- Tap behavior of tray items (popovers for volume slider, wifi list, etc.). +- Touch target sizing (touch-mode is state-only, no auto-scale — extension + adapts affordances explicitly via `on_touch_mode_changed`). +- Theming/RCSS; per-output (one bar per output? primary only?). + +## 2. Home screen (iPad "Springboard"-style; tentative: `ext-home`) + +A full-screen surface that shows **app icons**; the iOS home screen analogue. + +- **Tap/click an app:** + - if the app is **not open** → launch it. + - if it **is open** (single instance) → raise/focus that window. + - if it is open with **multiple instances** → show all of them as options and + let the user pick which to bring forward. +- **Add / remove** apps from the home screen (some management flow/UI). +- **Swipe up from the bottom edge** → enter the home screen. + +### Open questions / to flesh out +- App catalog source: XDG `.desktop` entries (Exec/Name/Icon) for the + add-app picker? How is the on-screen set stored/persisted (append-only state, + reconcile-on-boot — mirrors slice 9's durability model)? +- Icons: needs an XDG **icon-theme** dependency — overlaps `favicon-spec.md` + (the dock favicon work). Resolve once, share. +- **Instance tracking (the hard part):** "is this app already open, and which + windows are its instances?" requires a window↔app association (app-id / + `.desktop` ↔ `xdg_toplevel` app_id). Likely a kernel/core capability the + home screen + stage dock + a future taskbar-tray all reuse. Define the + contract once. +- The multi-instance picker UI — could reuse the stage-dock preview-snapshot + pipeline (live thumbnails of the candidate windows). +- Gesture: swipe-up-from-bottom mirrors the stage dock's left-edge reveal + recognizer; reuse the gesture-CLAIM input path / recognizer cores. +- Layout: grid, pages/folders, reorder (drag). How it coexists with tiling and + the stage dock (is the home screen a workspace? a layer? what has focus?). +- Launch reuses the existing `spawn` mechanism + `WAYLAND_DISPLAY` export. + +## Shared primitives these two (probably) need +- **Window↔app association / instance enumeration** (home screen; reusable). +- **XDG icon-theme lookup** (home screen icons; shared with favicon-spec). +- **Top-edge exclusive-zone layer surface + usable-area accounting** (status bar). +- **Bottom-edge gesture reveal** (home screen; same family as stage dock's + left-edge reveal). +- Keep "kernel names no feature": snapshots, gesture-claim, list bindings, + instance enumeration are generic; status-item set and home-screen contents are + the policy and live in the standard extensions. diff --git a/notes/tiling-layouts-reference.md b/notes/tiling-layouts-reference.md new file mode 100644 index 0000000..ff7a915 --- /dev/null +++ b/notes/tiling-layouts-reference.md @@ -0,0 +1,78 @@ +# Tiling layouts — dwm reference (for slice 7, ext-window-tiling) + +> **Status: REFERENCE / research only.** A catalogue of dwm's tiling layouts to +> draw from when designing `ext-window-tiling` (slice 7). Revisit once we have a +> baseline tiling core to work off. These are X11/dwm patches — NOT something we +> apply; they're a design reference. Each maps cleanly onto the slice-7 goal of a +> **pure layout core**: `(client count, master count, mfact, area) -> list of +> rects`, with zero wlroots types. Source: dwm.suckless.org/patches + the +> bakkeby/dwm-flexipatch bundle (which ships these as git-applicable diffs). + +## Built-in dwm layouts (no patch) + +| Layout | Description | +|---|---| +| **tile** | Default master/stack: `nmaster` windows in a left master column, the rest stacked vertically on the right. `mfact` sets the master/stack split. | +| **monocle** | Every window maximized to the full area; one shown at a time. | +| **floating** | Windows moved/resized freely (no tiling). | + +## Layout patches + +### Master / stack variants +| Layout | Description | Patch | +|---|---|---| +| **bstack** (bottomstack) | Master on **top** (full width); stack tiled left-to-right in a row beneath. | https://dwm.suckless.org/patches/bottomstack/ | +| **bstackhoriz** | Bottom-stack variant: stack clients stacked top-to-bottom beneath the master. | (same patch as bstack) | +| **columns** (col) | Like `tile`, but master-area clients are arranged in columns (left-to-right) instead of a single column. | https://dwm.suckless.org/patches/columns/ | +| **deck** | Master area normal; stack clients "decked" — piled on top of each other, only the focused one visible (monocle for the stack). | https://dwm.suckless.org/patches/deck/ | + +### Centered master +| Layout | Description | Patch | +|---|---|---| +| **centeredmaster** | Master column centered horizontally; stack split to the **left and right** of it. | https://dwm.suckless.org/patches/centeredmaster/ | +| **centeredfloatingmaster** | Master floats centered **on top** of the full-width tiled stack. | (same patch) | + +### Grids +| Layout | Description | Patch | +|---|---|---| +| **gridmode** (grid) | All windows in an even grid. | https://dwm.suckless.org/patches/gridmode/ | +| **gaplessgrid** | Grid with balanced columns; the last column absorbs the remainder so there are no empty cells. | https://dwm.suckless.org/patches/gaplessgrid/ | +| **horizgrid** | Splits into a top and bottom row (horizontal grid). | https://dwm.suckless.org/patches/horizgrid/ | +| **nrowgrid** | Grid whose number of rows is controlled by `nmaster`. | https://dwm.suckless.org/patches/nrowgrid/ | + +### Spiral +| Layout | Description | Patch | +|---|---|---| +| **fibonacci → spiral** | Fibonacci tiling: each window takes half the remaining space, spiraling inward. | https://dwm.suckless.org/patches/fibonacci/ | +| **fibonacci → dwindle** | Same split, but windows dwindle toward the bottom-right instead of spiraling. | (same patch) | + +### Meta / configurable +| Layout | Description | Patch | +|---|---|---| +| **flextile-deluxe** (supersedes **flextile**) | Configurable meta-layout: pick a split mode (horizontal / vertical / centered / floating / fixed) and a per-split tile arrangement (stack h/v, grids, fibonacci). Can reproduce tile, deck, monocle, centeredmaster, bstack/bstackhoriz, gapplessgrid, and more. | https://github.com/bakkeby/patches/wiki/flextile-deluxe/ (orig: https://dwm.suckless.org/patches/flextile/) | + +### Also on the suckless site (not in the flexipatch bundle) +| Layout | Description | Patch | +|---|---|---| +| **tatami** | Symmetric "tatami mat" arrangement. | https://dwm.suckless.org/patches/tatami/ | +| **three-column / tcl** | Master in a center column with stacks on both sides; sized for wide screens. | https://dwm.suckless.org/patches/tcl/ | + +## Adjacent (not layouts, but pair with them) +| Patch | Description | Patch | +|---|---|---| +| **vanitygaps** | Configurable inner/outer gaps between tiled windows and screen edges. | https://dwm.suckless.org/patches/vanitygaps/ | +| **cfacts** | Per-client size weights within the stack (give some stack windows more space). | https://dwm.suckless.org/patches/cfacts/ | + +## Notes for the unbox design (to flesh out later) +- The whole set reduces to one pure function shape: + `arrange(area: Box, clients: int, nmaster: int, mfact: float, params...) -> [Box]`. + That's the slice-7 core (100% doctest-coverable, zero wlroots types). +- Touchscreen / iPad direction (CF-AX3): not all of these make sense for touch. + Likely-relevant shortlist to decide on: tile, monocle, deck, bstack, + centeredmaster, spiral/dwindle. Grids and flextile-deluxe are powerful but + keyboard-heavy. Decide the starter set when we have a baseline. +- `flextile-deluxe` is the "one layout to rule them all" approach — worth + studying as a model for a single configurable engine vs. many discrete + layouts. +- Gaps (vanitygaps) and per-client weights (cfacts) are orthogonal modifiers; + design the core so they're parameters, not separate layouts. diff --git a/notes/tiling-spec.md b/notes/tiling-spec.md new file mode 100644 index 0000000..bd736cf --- /dev/null +++ b/notes/tiling-spec.md @@ -0,0 +1,164 @@ +# ext-window-tiling — baseline design (slice 7) + +> **Status: BASELINE DECIDED — build off this.** The agreed starting point for +> the tiling extension. Layout catalogue to grow into later lives in +> `notes/tiling-layouts-reference.md`. Open questions at the bottom are to be +> resolved as we build. + +## Naming (replaces dwm's "master") + +dwm's "master/stack" → we use: + +- **primary** — the window that gets the most space / most attention. +- **stack** — every other tiled window. + +Rationale: "master" is unclear (and carries master/slave baggage). "primary" is +plain and position-agnostic (it works whether the big window is left or right). +Considered alternatives: `hero`, `main` (fine), `focus` (REJECTED — collides +with keyboard focus), `stage` (REJECTED — taken by the stage dock). + +**Pending: final user sign-off + `GLOSSARY.md` entry** for `primary` / `stack` +(no-synonym-coinage rule). + +## The baseline layout + +A single default tiling layout. Mirror of dwm's `tile`; the primary column sits +on the **right by default but the side is configurable** (`primary_side`), so it +can be flipped to a left primary + right stack. + +- **New windows auto-tile** — opening a window drops it into the layout + automatically; no manual placement step. Where it lands in the stack (top or + bottom) is **configurable** (`new_window`). +- Window-count behaviour: + - **1 window** → fills the whole area (fullscreen). + - **2 windows** → split 50/50: primary right half, the one stack window left + half. + - **3+ windows** → primary takes the right portion (`split_ratio` of the + width); the remaining windows share the LEFT portion, stacked vertically and + sized evenly. + +``` + 1 window 2 windows 3+ windows + ┌──────────┐ ┌─────┬─────┐ ┌────┬────────┐ + │ │ │ │ │ │ S1 │ │ + │ P │ │ S │ P │ ├────┤ P │ + │ │ │ │ │ │ S2 │ │ + └──────────┘ └─────┴─────┘ ├────┤ │ + │ S3 │ │ + └────┴────────┘ + P = primary (right) S = stack (left, vertical) +``` + +## The ordering model (decided) + +One **ordered list** of tiled windows. The **head of the list is the primary**; +the remaining elements are the **stack**, rendered top-to-bottom in list order. + +- **New windows enter at the BOTTOM of the stack** (append to the list end) — + they do NOT steal the primary slot. +- **The top of the stack is promoted to primary** when the primary slot is + vacated (the primary window closes or is moved out). Because the primary is + just the head of the list, removing the head makes the next element (the old + top-of-stack) the new primary automatically. +- Net effect of the two defaults: opening windows never disturbs your current + primary; closing the primary hands the big slot to the longest-waiting stack + window (the one at the top). + +## Config (`unbox.toml`, `[tiling]` table) + +```toml +[tiling] +primary_side = "right" # "right" (default) or "left" +new_window = "bottom" # "bottom" (default) or "top" of the stack +split_ratio = 0.55 # fraction of width given to the primary column +primary_count = 1 # windows in the primary area (baseline 1) +``` + +- **`primary_side`** — which side the primary column sits on. Default **right**. +- **`new_window`** — where a newly-opened window enters the stack. Default + **bottom**. (`top` makes the newest window the first in the stack and thus the + next in line for promotion to primary.) +- **`split_ratio`** (dwm `mfact`) — primary column width fraction. **Static + config for now** (no runtime keybind/drag yet — add when friction demands). +- **`primary_count`** (dwm `nmaster`) — baseline **1**; model allows >1 later + without redesign. + +### Hot-reloadable (all four) + +All four values **live-reload** — edit `unbox.toml`, save, and the change applies +with no restart. This reuses the existing kernel primitive +`Host::watch_file(path, cb) -> FileWatch` that already backs ext-keybindings' +config reload (RAII, coalesced, editor-save/create-safe, error-isolated; one +session inotify is shared). ext-window-tiling watches the same `unbox.toml` and +re-parses its own `[tiling]` table. + +On a save: +- Re-read + re-parse `[tiling]` (pure). **Keep-old-on-bad:** a malformed / + mid-edit file keeps the current values and logs one warning — never drops a + working layout (same contract as the keybindings reload). +- **`primary_side`, `split_ratio`, `primary_count`** take effect immediately: + swap the live values and **re-arrange every output's tiled set** that frame. +- **`new_window`** updates the stored value but only affects windows opened + *after* the change (it governs insertion order, not existing windows) — no + re-arrange needed. + +The watcher holds no tiling state; it just hands new parsed values to the same +pure core, so reload is a value swap + a re-arrange call (no re-subscribe, no +window churn). + +## The pure core (slice-7 contract shape) + +Two pure pieces, both wlroots-free and 100% doctest-coverable (the slice-7 "pure +decision core" rule): + +``` +# geometry: ordered window list -> one Box each, in list order +arrange(area: Box, n: int, primary_count: int, split_ratio: float, + primary_side: Side) -> [Box] + +# list management: where a new window is inserted (top|bottom) and primary +# promotion on removal — pure operations over the ordered list of opaque tokens +``` + +- `area` = the usable region (already minus the status bar / any reserved zones). +- `arrange` returns one `Box` per window in list order (head = primary). +- `new_window` (top/bottom) and promotion-on-remove govern the LIST order, not + the geometry — keep them separate from `arrange`. +- Effects (assigning rects to real toplevels via the scene) live in thin glue. + +## Decided +- **Primary side:** configurable (`primary_side`), default **right**. +- **New-window insertion:** configurable (`new_window`), default **bottom** of + the stack. +- **Primary promotion:** when the primary leaves, the **top of the stack** is + promoted to primary (automatic — it's the new list head). + +## Scope discipline + +Keep the baseline minimal and **fully automatic**: **NO new keybindings or +gestures for tiling at this stage.** Windows auto-tile on open; the primary is +the list head; that's it. Everything below is **deferred until real-use friction +makes it the next thing to fix** — the WM is being dogfooded on the CF-AX3 and +features get added incrementally, largest friction first. + +## Deferred until needed (NOT in baseline) + +- **Manual primary swap / move-in-stack** — promoting an arbitrary stack window + or reordering. (Needs a keybind/gesture → out of scope for now.) +- **Runtime `split_ratio` / `primary_count` changes** — config-only for now. +- **Focus + cycling across primary/stack** — existing ext-keybindings focus ring + (Alt+Tab) already moves focus; no tiling-specific focus controls added now. +- **Floating exceptions** — dialogs / fixed-size / transient windows staying + floating. Confirm + implement when a real app needs it. +- **Gaps** (dwm `vanitygaps`) — inner/outer gaps as a parameter (not a layout). +- **Touch ergonomics (CF-AX3):** many stack windows → thin left column; cap stack + count or collapse to deck/monocle past N (a future second layout, not baseline). +- **Interaction with the rest of unbox:** minimize-to-stage-dock removing a + window from the tiled set and restoring re-inserting it; home-screen / + fullscreen-app vs the tiled set. +- **Per-output / per-workspace scope** — what tiling state is keyed to (workspace + model is itself a deferred decision — see plan.md §7). + +## Not in baseline (build off this later) +See `notes/tiling-layouts-reference.md` — deck, monocle, bstack, centeredmaster, +spiral/dwindle, grids, flextile-deluxe-style configurability, cfacts weights. diff --git a/tasks.md b/tasks.md index f2a4d2f..7b353fa 100644 --- a/tasks.md +++ b/tasks.md @@ -5,6 +5,19 @@ ## Now +**ACTIVE (core, user-driven) — Slice 13: RML COMPOSITING SPIKE.** Big direction +change: RMLUi becomes the content compositor — toplevels + layer-shell (incl. +wallpaper) + chrome are RML elements backed by LIVE, SHARED GL textures, with +layout/animation/3D effects in RCSS; wlroots stays foundation + cursor plane + +(deferred) fullscreen scanout bypass. Lost wlr_scene damage/scanout is mitigated +by OUR dirty-gated rendering (NOT a RMLUi built-in) + a deferred scanout bypass. +GATED BY A SPIKE before commit. Full spec + acceptance criteria: +`notes/rml-compositing.md`; decision row in `notes/plan.md` §2. +NEXT ACTION: write the spike brief (kernel/substrate) and summon it. +Tiling (slice 7) is DEFERRED behind this (becomes RCSS over surface elements; +pure layout core in `notes/tiling-spec.md` carries over). Stage dock (slice 10) +real-seat feel check is paused under this pivot. + **DEV WORKFLOW (RML/RCSS hot-reload — use this):** UI documents are external assets under `assets//` (e.g. `assets/ext-stage-dock/dock.rml` + `dock.rcss`), loaded via `UiSurfaceSpec::rml_path`. Launch unbox with @@ -76,8 +89,12 @@ real-seat feel pending):** minimize-keybind migration (ext-keybindings action + a stage-dock Service), and favicon (needs an XDG icon-theme dep) follow. -Slice 6 (ext-taskbar + ext-launcher) is paused; the stage dock matures the same -ui-substrate gaps it would have (list bindings, first real interactive consumer). +Slice 6 re-scoped: the **window-list taskbar is CUT** (overlaps the stage dock, +conflicts with the touch/iPad direction; its contract-exercise purpose was met by +the stage dock). Replaced by two future, not-yet-designed features sequenced +AFTER slice 7 (tiling): **status bar** (slice 11) and **home screen** (slice 12) +— ideas + open questions captured in `notes/status-bar-home-screen.md`. Launching +is covered by fuzzel today (and the home screen later). Still queued whenever UI work resumes: keyboard-into-ui-surfaces, removing the deprecated no-op `Options::ui_spike`, retiring host-bin's demo ui. @@ -92,11 +109,14 @@ deprecated no-op `Options::ui_spike`, retiring host-bin's demo ui. | 4 | Extension host + contracts: bus, manifests, static registration; xdg-shell/layer-shell refactored OUT of kernel into core extensions | **DONE** 2026-06-12 | met: kernel boots featureless (names no feature); typed Event/Filter bus error-isolated + topo activation; ext-xdg-shell (toplevels, focus, grabs via pure GrabMachine, button/axis routing, Ctrl+Alt+Backspace quit) + ext-layer-shell (fuzzel verified, pure arrangement core) pass suites; typed surface→scene-tree registry replaced the data-field convention; first protocol codegen (wlr-layer-shell XML vendored); user hands-on: all input paths verified incl. touch; 68 cases green + ASan clean; idle RSS ≈73 MiB | | 5 | Input routing + ergonomics contract: unified pointer/touch→RMLUi events, keybinding filter chain, touch-mode RCSS variables | **DONE** 2026-06-13 | met (user hands-on): real ui substrate (`Host::ui()` → UiSurface, scalar+event bindings, dmabuf+fence+swapchain); same demo surface driven by mouse AND finger; consume-or-pass with implicit-grab ownership (press owner gets release, per touch point too); touch-mode = state+notification only, NO visual scaling (user decision); touch-initiated grabs incl. pointer/touch alternation (seat release-leak fixed); keybinding chain satisfied by slice-4 Filter (ext-keybindings deferred); 113 doctest cases green, ASan clean, idle RSS ≈78 MiB | | 5b | Usability: `ext-keybindings` (config-driven `unbox.toml`) — Super→fuzzel, Alt+Tab focus rotation; ext-xdg-shell keybinds migrated; kernel exports `WAYLAND_DISPLAY` for spawned clients | **DONE** 2026-06-13 | met (real-seat, user-confirmed): fuzzel opens on Super, Alt+Tab cycles all windows, quit works; build + build-asan both green (3rd-party Mesa/RmlUi sanitizer noise suppressed; a real libwayland leak in the layer-shell client test fixed) | -| 6 | First standard extensions: ext-taskbar + ext-launcher | pending | proves the ui-substrate contract is complete (friction = bad contract) | -| 7 | ext-window-tiling: pure layout core + thin scene glue | pending | layout math 100% doctest-covered, zero wlroots types in core | +| 6 | ~~ext-taskbar~~ + ext-launcher | **taskbar CUT / re-scoped** | window-list taskbar dropped (overlaps the stage dock + conflicts with the touch/iPad direction). Replaced by slices 11–12. Launching is covered by fuzzel today and the home screen later. The contract-exercise purpose was met by the stage dock. | +| 7 | ext-window-tiling: pure layout core + thin scene glue | **DEFERRED pending slice 13** (baseline designed) | layout math 100% doctest-covered, zero wlroots types in core. Baseline = `primary` (right) + `stack` (left), auto-tile, 1=full/2=50-50/3+=stack-left; see `notes/tiling-spec.md` (+ `notes/tiling-layouts-reference.md`). Held until RML compositing (slice 13) lands — tiling then becomes RCSS layout over surface elements; the pure core is renderer-agnostic and carries over. (`Toplevel::set_box` prototype reverted; recreate from `prompts/ext-xdg-shell.md` when tiling resumes.) | | 8 | ext-osk: RML keyboard ui surface injecting via wlr_seat | pending | type into foot via touch only; auto-show on text-input focus | | 9 | Session hardening: s6 user service, TTY launch on seat0, layout persistence (append-only state + pure reconcile on boot) | pending | survives `kill -9` + s6 restart with workspaces restored | | 10 | **Stage dock** (ext-stage-dock): minimized-window previews on a left-edge swipe (Fork B) | **a1–d1 landed; previews real-seat-verified** | DONE: Super+M minimize→RMLUi-imported preview snapshot→dock slot→hide (previews confirmed rendering on hardware); RCSS dock slide-in + slot settle. NEXT: confirm tap-to-restore + animation feel; 1 boundary call (input-transparent UiSurface flag) → c1 gesture-claim → e1 gesture reveal/drag-out; then config-driven minimize keybind + favicon (XDG icon dep) | +| 11 | **Status bar** (tent. ext-statusbar): iPad/iOS top bar — clock (left), configurable left/middle/right sections, tray (right) wifi/volume/battery | **IDEA — needs design** | sequenced AFTER slice 7 (tiling); replaces cut taskbar. Details + open questions: `notes/status-bar-home-screen.md` | +| 12 | **Home screen** (tent. ext-home, iPad springboard): app grid; tap = launch-or-raise (instance picker if >1 open); add/remove apps; swipe-up-from-bottom to enter | **IDEA — needs design** | sequenced AFTER slice 7 (tiling); replaces cut taskbar. Details + open questions: `notes/status-bar-home-screen.md` | +| 13 | **THE SPIKE: RML compositing** — RMLUi becomes the content compositor (toplevels + layer-shell incl. wallpaper + chrome = RML elements backed by LIVE, SHARED GL textures; layout/animation/3D effects in RCSS). wlroots = foundation + cursor plane + (deferred) fullscreen scanout bypass. | **ACTIVE (core) — spike** | GO/NO-GO on the CF-AX3: (1) live toplevel texture in RmlUi via shared context, ZERO per-frame copy; (2) RCSS 3D transform on it; (3) pointer+touch+keyboard routed back through RmlUi picking → wl_seat; (4) window w/ popup+subsurface composited (decides per-subsurface-elements vs per-window RTT); (5) wallpaper as an element; (6) perf ~4 windows@1080p + idle≈no-work (our dirty-gating) + video cost; (7) present via existing FBO→scene_buffer bridge. Full spec + decision row: `notes/rml-compositing.md`, plan.md §2. | ## Deferred decisions (decide when reached — see notes/plan.md §7) -- cgit v1.2.3