summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/kernel.md
diff options
context:
space:
mode:
Diffstat (limited to 'packages/kernel/kernel.md')
-rw-r--r--packages/kernel/kernel.md99
1 files changed, 69 insertions, 30 deletions
diff --git a/packages/kernel/kernel.md b/packages/kernel/kernel.md
index 24a37c9..6ed9b5e 100644
--- a/packages/kernel/kernel.md
+++ b/packages/kernel/kernel.md
@@ -1,45 +1,84 @@
# kernel — package notes
-Slice-2 state: a working tinywl port (+ touch, which tinywl lacks) wholly
-inside the kernel: backend/output/scene glue, xdg-shell toplevels + popups,
-click/tap-to-focus, Alt-drag-free interactive move/resize (client-requested
-only), keyboard/pointer/touch via one wlr_cursor path. Slice-2 keybindings:
-Alt+Escape = terminate, Alt+F1 = cycle. Slice 4 splits shell policy out
-into extensions.
+Slice-4 state: the kernel **names no concrete feature** and boots
+featureless. It owns generic plumbing (compositor/subcompositor/data-device,
+output+scene glue, cursor + xcursor-mgr + seat, the kernel-private ui spike)
+plus the **extension host + typed bus**. ALL shell policy (xdg-shell
+toplevels/popups, focus, alt-cycle, terminate, interactive move/resize,
+keybindings) was EXTRACTED — `src/toplevel.cpp` is deleted; ext-xdg-shell /
+ext-layer-shell recreate it from the contract alone.
-Slice-3 state: THE SPIKE landed on **Plan A** (RMLUi -> dmabuf-backed
-wlr_buffer -> wlr_scene_buffer), with Plan B (FBO + glReadPixels into a
-data-ptr wlr_buffer) as a verified runtime fallback. All bridge state is
-private in `src/ui_spike.{hpp,cpp}` + the adapted GLES3 renderer
-`src/rmlui_renderer_gl3.{h,cpp}`. Public surface delta: `Options::ui_spike`
-+ `Server::ui_spike_frame_count()` (both TEMPORARY, replaced by the real ui
-substrate in slice 4+). Driven from the output frame handler; renders only
-when `ui_spike != nullptr`. Host-bin does NOT yet wire the Option.
+Public contract (the ABI): `hooks.hpp` (typed `Event<Args...>` /
+`Filter<T>` + RAII `Subscription`), `extension.hpp` (`Tier`, `Manifest`,
+`Extension`), `host.hpp` (`Host` facade: borrows + event catalogue + scene
+layers + services + typed surface→tree association), `listener.hpp` (the RAII
+`wl_listener` wrapper, now public), `surface_registry.hpp` (`SurfaceRegistration`
+RAII handle + the pure `detail::PointerAssoc` core), `server.hpp` (`install` +
+`activate_extensions`).
+
+Side-effect graph (who emits / who routes):
+- The kernel EMITS typed Events for its glue (output add/remove; pointer
+ motion/button/axis/frame; touch down/motion/up/cancel/frame) and applies
+ `key_filter` to every key. It moves the cursor, runs seat-capability and
+ seat-protocol glue (request_set_cursor/selection, focus_change default
+ cursor), and forwards a key to the focused client ONLY if no filter link
+ set `handled`. It routes NOTHING else to client surfaces and makes NO
+ focus decision — extensions do that via the bus + the seat borrow.
+- `Server::install()` transfers ownership; `activate_extensions()` (called
+ by `run()`, or earlier by host-bin/tests) topo-sorts by `Manifest
+ depends_on` (ties: tier then install order), then calls each `activate`.
+ Missing dep / cycle / duplicate id = `std::runtime_error` at startup. An
+ `activate()` throw is FATAL (propagates) — a core ext that can't start is
+ a broken session, not an isolated one. RUNTIME callback throws ARE
+ isolated (see below).
+- Scene z-bands live in `Impl::scene_layers[]` (SceneLayer order, created
+ over `scene->tree` background→overlay so stacking is correct). The ui
+ spike now sits in the `overlay` band. Extensions attach via
+ `Host::scene_layer()`.
Gotchas the headers can't express:
+- **Error isolation = deferred purge.** A hook callback that throws is
+ caught at the bus boundary; `Server::Impl` (a `detail::DisableSink`) marks
+ the owning extension disabled and `purge()`s its subscriptions from EVERY
+ registered hook (`all_hooks`). Purge during a live dispatch only
+ tombstones (`dead=true`); physical erase happens when that hook's dispatch
+ depth returns to 0 (`compact_if_idle`). So disabling an extension from
+ inside its own callback, and an ext subscribed to multiple hooks, are both
+ safe. Hooks are PINNED (Subscriptions hold a raw `HookBase*`): never move
+ an `Event`/`Filter`; hold them as stable members.
+- **Extensions are destroyed FIRST in `shutdown()`**, reverse of install
+ order, so their RAII members (Subscriptions, Listeners, scene nodes)
+ release while the wlr objects they borrow are still alive. Then the spike,
+ then clients, then server-level Listeners, then wlr objects.
- **`wlr.hpp` blanks `static` around the wlr includes.** wlroots headers
use C99 array-parameter syntax (`float color[static 4]`), invalid in
C++. With `static` blanked, `static inline` helpers become `inline`
- (ODR-merged, safe). Cost: a function-local `static` inside a header
- inline would silently lose persistence — none exist in our include set;
- re-audit when ADDING includes to the wrapper.
+ (ODR-merged, safe). Re-audit when ADDING includes to the wrapper.
- **RMLUi is kernel-private.** `rmlui_dep` is deliberately absent from
- `kernel_dep` propagation (see meson.build): extensions contribute RML
- documents + data bindings via the ui substrate, never RMLUi API calls.
- Do not "fix" a missing-RMLUi-header error downstream by propagating it.
-- **Shutdown order is load-bearing** (`Impl::shutdown()`): destroy clients
- → disconnect ALL server-level Listeners → scene/cursor/allocator/
- renderer/backend/display. A Listener outliving the wlr object owning its
+ `kernel_dep` propagation: extensions contribute RML documents + data
+ bindings via the (future) ui substrate, never RMLUi API calls. Do not
+ "fix" a missing-RMLUi-header error downstream by propagating it.
+- **Server-level Listener disconnect order is load-bearing**
+ (`Impl::shutdown()`): a Listener outliving the wlr object owning its
signal is a use-after-free (`wl_list_remove` touches neighbor links).
- Entity-level Listeners are exempt: their destroy events fire (and erase
- the entities) during `wl_display_destroy_clients` / backend destroy.
+ Entity-level Listeners (Output/Keyboard/TouchDevice) are exempt: their
+ destroy events fire during `wl_display_destroy_clients` / backend destroy.
- **A Listener handler may destroy its own Listener** (the destroy-event
- pattern) but the erase/delete must be the handler's LAST action — see
- listener.hpp. The slice-4 bus formalizes this.
-- **Touch points record their down-surface's layout origin** to derive
- surface-local motion coords; a surface moving mid-touch (interactive
- grab) skews them. Acceptable until slice 5's input routing.
+ pattern) but the erase/delete must be the handler's LAST action.
+- **No cross-unit `wlr_surface.data`.** The surface→scene-tree association is
+ a typed kernel contract (`Host::host_surface`/`scene_tree_for`, backed by
+ `Server::Impl::surface_assoc`). The map is kernel-owned but the VALUE tree is
+ an extension's; the returned tree is a borrow valid only while the hosting
+ extension's `SurfaceRegistration` lives. Re-hosting a surface supersedes the
+ old handle (token defense), so a stale handle never tears down the new
+ mapping. Private intra-unit `.data` use is still fine; cross-unit must route
+ through the contract.
+- **Pointer button & axis are NOT forwarded by the kernel** (it only moves the
+ cursor and emits `ev_pointer_button`/`ev_pointer_axis`). The pointer-routing
+ extension forwards them via `wlr_seat_pointer_notify_button/_axis`, same as
+ enter/motion/frame — not notifying during a grab is the suppression mechanism.
+ (The old "kernel forwards button/axis" doc comment was a verified lie; fixed.)
- Everything runs on the single `wl_event_loop` thread.
Slice-3 spike gotchas (EGL/dmabuf — read before touching `ui_spike.cpp`):