1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
# ext-xdg-shell — package notes
**Tier:** core. **Manifest:** `{ id: "xdg-shell", tier: core, depends_on: {} }`.
Recreates the kernel's former tinywl-shape window management as an extension,
against the Host ABI alone. The xdg-shell v3 global is created HERE (the
extension-creates-the-global split), not by the kernel.
## Why it exists
The slice-4 kernel boots featureless: it owns input/output/scene/seat glue and
emits a typed catalogue, but names no shell policy. This unit is the minimal
shell that makes a session usable: toplevels appear, focus follows click/tap,
pointers and touch route to clients, and windows move/resize on request.
Compositor keybindings (focus-cycle, terminate) now live in ext-keybindings.
## Side-effect graph
- **Creates:** the `wlr_xdg_shell` v3 global on `host.display()`.
- **Subscribes (kernel catalogue):** `on_pointer_motion` (hit-test → seat
enter/motion, default xcursor over nothing, drives grabs), `on_pointer_button`
(forward `notify_button` + click-to-focus + grab begin/reset),
`on_pointer_axis` (forward `notify_axis`), `on_pointer_frame` (notify_frame),
`on_touch_down/motion/up` (tap-to-focus + down/up/motion notify),
`on_touch_frame` (notify_frame). `key_filter` is NOT subscribed here;
ext-keybindings owns all compositor keybindings.
- **Binds (raw xdg-shell signals, RAII `Listener`):** `new_toplevel`,
`new_popup`, and per-entity map/unmap/commit/destroy/request_move/
request_resize/request_maximize/request_fullscreen.
- **Emits (exported `Event`s, adopt()ed):** `on_toplevel_mapped`,
`on_toplevel_unmapped`, `on_toplevel_focused`, each carrying a `Toplevel`
borrow.
- **Provides (service):** `Service` (typed handle to the three Events).
- **Registers (typed surface→tree contract):** each toplevel's and popup's
`wlr_surface` → its scene tree via `Host::host_surface()` (RAII handle held
in the entity); resolves popup parents via `Host::scene_tree_for()`.
## Surface→scene-tree association (typed kernel contract)
The old cross-unit `wlr_surface.data` / `wlr_xdg_surface.data` convention is
DEAD. Cross-unit surface→tree coupling now routes through the kernel's typed
`Host::host_surface()` / `Host::scene_tree_for()` registry. `new_popup`
resolves its parent (our toplevel/ancestor popup OR a layer surface registered
by ext-layer-shell) uniformly through `scene_tree_for()`. We still set
`wlr_scene_tree.node.data = ToplevelEntry*` PRIVATELY — that is an intra-unit
back-pointer for `toplevel_at`, which the registry contract explicitly permits;
it is never read by another unit.
## Gotchas the headers can't express
- **All pointer forwarding is OURS.** The kernel forwards NOTHING to clients
(corrected host.hpp): this extension calls
`wlr_seat_pointer_notify_enter/motion/button/axis/frame` itself. Grabs
suppress the forward by simply not notifying while a move/resize is in flight.
(Forwarding button + axis was a real bug found hands-on in a nested session —
click-drag selection and wheel scroll in foot were dead without it.)
- **Touch layout-origin-during-grab skew** (slice-2 parity): a touch point's
surface origin is captured at down-time and assumed stationary; for ordinary
(non-grab) touch routing this still skews if a surface moves mid-touch.
Accepted until slice 5. NOTE this does NOT affect a touch-driven move/resize
grab: during a grab we suppress the client touch-motion notify entirely (the
compositor consumes the drag) and drive the window from the raw layout
coords, so the moving-surface origin never fights the grab.
- **Interactive move/resize grab is a pure state machine** (`policy::
GrabMachine`), NOT an ad-hoc cursor-mode flag. The grab is a deterministic
function of (which inputs are down, client-requested-move/resize): it engages
ONLY while an input is held, every held motion of the DRIVING input
moves/resizes (suppressing that input's client notify), and the driving
input's release ends it. This killed the original pointer bug (drag didn't
move while held, then followed unclicked after release — grab lifetime
decoupled from the button; a late `request_move` engaged post-release).
The grab's interaction source is generalized: **pointer button OR a single
touch point.** Touch is preferred when a touch point is down (CSD touch drag
carries no pointer button), and the grab is PINNED to its originating touch
id — a second simultaneous finger neither steers nor ends it; only the
originating point's up/cancel does. Pointer and touch are isolated (pointer
motion never drives a touch grab and vice versa). The glue feeds
press/release/down/up/cancel/request/motion in and executes the returned
action; the geometry + driving-input layout position live in the glue.
- **GLUE REGRESSION (seat implicit-grab balance):** a pointer grab begins
because we forwarded the button PRESS to the client (before its `request_move`
arrived) — which starts the wlr_seat IMPLICIT pointer grab. We must forward
the matching button RELEASE even though the release also ends OUR grab;
otherwise the seat's implicit pointer grab stays open forever and silently
swallows every later touch-down, so after one mouse titlebar-drag no touch
grab ever engages again (deterministic user repro). The driving client
ignores the stray release. (The `GrabMachine` was proven innocent here — its
pure sequence tests pass clean; the bug was the missing seat notify.) Touch
grabs stay balanced because `process_touch_up`/`_cancel` always send the
matching `wlr_seat_touch_notify_up`/`_cancel`.
- Teardown is pure RAII (reverse declaration order); there is no manual
cleanup. The `wlr_xdg_shell` global and scene nodes are display/scene-owned
and outlive nothing of ours improperly.
|