summaryrefslogtreecommitdiffhomepage
path: root/packages/ext-layer-shell/ext-layer-shell.md
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-12 22:44:16 +0900
committerAdam Malczewski <[email protected]>2026-06-12 22:44:16 +0900
commitc102a1b67a70149b6f9c9b2cfd8b31ceb52c09b7 (patch)
treef6dea2875b939c0f661292d8bfa0d79a96fe67d7 /packages/ext-layer-shell/ext-layer-shell.md
parent6949c3582ed1e480e70aabfcfa3a11b78007cc12 (diff)
downloadunbox-c102a1b67a70149b6f9c9b2cfd8b31ceb52c09b7.tar.gz
unbox-c102a1b67a70149b6f9c9b2cfd8b31ceb52c09b7.zip
Slice 4: extension host + typed bus; xdg-shell/layer-shell extracted to core extensions
The kernel now names NO concrete feature. It owns: the extension host (install/topological activate, missing-dep/cycle = startup error), the typed Event/Filter bus (error-isolated: a throwing extension is disabled, never the session; RAII Subscriptions), the Host API (per-extension facade: borrows, scene layers, event catalogue, typed services), the public RAII Listener, and a typed surface→scene-tree registry (Host::host_surface/scene_tree_for) that replaced the untyped wlr_surface.data convention both extensions flagged. - ext-xdg-shell (core): toplevel/popup lifecycle, focus-on-map, click/tap-to-focus, pointer/touch routing incl. button+axis (the kernel only moves the cursor and emits — a contract-doc lie caught by user hands-on), interactive move/resize via pure GrabMachine (fixes the request-arrives-after-release race: grab requires request ∧ button-down, release always ends it), Alt+F1 cycle, Ctrl+Alt+Backspace terminate (labwc's default A-Escape=Exit killed the dev session once — never again; see nested-run skill). - ext-layer-shell (core): wlr-layer-shell v1 (proto v5) for external clients; pure doctest-hard arrangement core; fuzzel verified visually nested (fix: seed outputs from output_layout at activate — events-only tracking missed pre-activation outputs; plus a scene-node double-free). - First protocol codegen: vendored wlr-layer-shell XML + wayland-scanner server-header propagated through kernel_dep; wlr.hpp grew a namespace→_namespace keyword fix for the generated header. - Glossary: 'scene layer' (user-approved). New rules earned: parallel-wave-builds, contract-docs. - User hands-on verified: typing, click-to-focus, drag-select, scroll, titlebar drag-move (slow + flick), Alt+F1, fuzzel + arrows, touch tap, Ctrl+Alt+Backspace. 68 doctest cases green, ASan/UBSan clean (our code), idle RSS ≈73 MiB.
Diffstat (limited to 'packages/ext-layer-shell/ext-layer-shell.md')
-rw-r--r--packages/ext-layer-shell/ext-layer-shell.md83
1 files changed, 83 insertions, 0 deletions
diff --git a/packages/ext-layer-shell/ext-layer-shell.md b/packages/ext-layer-shell/ext-layer-shell.md
new file mode 100644
index 0000000..e96b054
--- /dev/null
+++ b/packages/ext-layer-shell/ext-layer-shell.md
@@ -0,0 +1,83 @@
+# ext-layer-shell
+
+wlr-layer-shell-unstable-v1 (protocol **version 5**, the wlroots-0.20 cap) for
+**external** clients: panels, launchers, wallpapers, on-screen keyboards, and
+the crash-isolation escape hatch from `notes/plan.md` §2. unbox's own RMLUi ui
+substrate does **not** go through layer-shell — this protocol exists so foreign
+processes can paint the desktop's edges.
+
+Tier `core`, manifest id `layer-shell`, no dependencies. `activate(Host&)`
+creates the `wlr_layer_shell_v1` global on `host.display()`.
+
+## Why it exists
+The kernel names no concrete protocol. Layer-shell is shell *policy* (which
+edge a panel reserves, which z-band it lands in), so it is an extension. The
+extension-creates-the-global split keeps the kernel featureless.
+
+## Side-effect graph
+- **Creates:** the `wlr_layer_shell_v1` global (one, at activation).
+- **Subscribes (kernel events):** `on_output_added` / `on_output_removed` —
+ to track the output set (assign one to outputless surfaces; re-arrange and
+ evict on output loss). Plus a one-shot enumeration of already-existing outputs
+ (`host.output_layout()->outputs`) at activate, since outputs predate
+ activation (see Gotchas).
+- **Binds (wlroots signals, via RAII `Listener`):** shell `new_surface`; per
+ surface its `wlr_surface.commit`, layer-surface `destroy`, and `new_popup`.
+- **Drives:** `wlr_scene_layer_surface_v1_configure` on every commit and output
+ change, attaching each surface's scene node under the kernel `SceneLayer`
+ band matching its protocol layer (background/bottom/top/overlay map 1:1;
+ `normal` is toplevels-only and never used here).
+- **Emits hooks:** none yet (see *Deferred*).
+
+## Surface → scene-tree association (typed kernel contract)
+For each layer surface we register its `wlr_surface` → our `wlr_scene_tree` via
+`Host::host_surface()`, holding the move-only `SurfaceRegistration` as a member
+of the `LayerSurface` (it unregisters on destruction). ext-xdg-shell resolves a
+popup's parent surface to our tree via `Host::scene_tree_for()`, so xdg popups
+parented to a layer surface attach correctly. This is the kernel-owned **typed**
+replacement for the old `wlr_surface.data` convention (now dead) — cross-unit
+surface→tree coupling routes through this contract, never through `.data`.
+
+## Pure core
+`include/unbox/ext-layer-shell/arrangement.hpp` — `Box`, `SurfaceState`,
+`exclusive_edge()`, `apply_exclusive()`. Zero wlroots types; the independent,
+doctest-hard mirror of the usable-area bookkeeping that
+`wlr_scene_layer_surface_v1_configure` performs. It is what tiling (slice 7)
+will read for per-output usable area. The glue keeps a per-output `Box` updated
+from the helper's `usable_area` out-param using this model's coordinate
+convention.
+
+## What was deferred (intentional)
+- **`on_demand` keyboard interactivity:** only `exclusive` (focus on map) and
+ `none` (leave alone) are honored. `on_demand` needs slice 5's input routing
+ (click-to-focus a layer surface) and is a documented TODO.
+- **A typed usable-area service / `usable-area-changed` Event:** not exported.
+ The per-output `Box` is computed and held internally; publishing it is left
+ to the consumer that actually needs it (tiling) so the contract is shaped by
+ a real caller, not guessed. Noted as a deliberate deferral.
+- **Popup glue beyond the registration:** `new_popup` is bound but does no
+ extra work; wlroots' scene helper wires popup nodes once a consumer resolves
+ the parent via `Host::scene_tree_for()` against our `host_surface()`
+ registration.
+
+## Gotchas
+- **Seed outputs at activate, do not rely on events alone.** `Server::create()`
+ starts the backend, so outputs exist BEFORE extensions activate; their
+ `on_output_added` already fired. We enumerate `host.output_layout()->outputs`
+ in `activate()` to catch them — events-only tracking left `outputs_` empty and
+ silently broke every output-less client (the fuzzel "no configure" bug). The
+ underlying Host contract gap (late subscribers miss state) is a standing
+ change-request in `reports/ext-layer-shell.md`.
+- **Never destroy the scene node in the layer-surface destroy handler.**
+ `wlr_scene_layer_surface_v1` installs its own internal destroy listener that
+ frees the scene tree; calling `wlr_scene_node_destroy` ourselves is a
+ use-after-free (signal-emit order between the two listeners is unspecified).
+ Our destroy handler only reclaims the usable area and erases the
+ `LayerSurface`.
+- An output-less surface arriving when **no** output exists yet is **parked**
+ (`pending_`, destroy-listener only) and placed once an output appears — not
+ closed. We only close on a hard failure (`wlr_scene_layer_surface_v1_create`
+ returning null).
+- The destroy handler's **last** action is `owner.erase(this)`, which deletes
+ the `LayerSurface`; copy any needed value (output, owner ref) into locals
+ first — nothing may touch members afterwards (listener-lifetime).