summaryrefslogtreecommitdiffhomepage
path: root/packages/ext-layer-shell/src
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-13 00:17:33 +0900
committerAdam Malczewski <[email protected]>2026-06-13 00:17:33 +0900
commit803fd2687a5f6ead0644f9c952bed6e3e4ef7ed9 (patch)
tree68d727df9c0f08a7a08c2c464f95d8c82fb8789e /packages/ext-layer-shell/src
parentc102a1b67a70149b6f9c9b2cfd8b31ceb52c09b7 (diff)
downloadunbox-803fd2687a5f6ead0644f9c952bed6e3e4ef7ed9.tar.gz
unbox-803fd2687a5f6ead0644f9c952bed6e3e4ef7ed9.zip
Slice 5: real ui substrate + unified input routing + touch-mode; spike retired
The ui substrate is now the extension-facing contract (unbox/kernel/ui.hpp): Host::ui() -> UiSubstrate::create_surface(spec) -> UiSurface with typed scalar bindings (int/double/bool/string getters), data-event callbacks (error-isolated per extension), dirty(), geometry/visibility — RMLUi and GL stay kernel-private. Production sync: glFinish replaced by EGL_KHR_fence_sync + 2-deep wlr_swapchain. ui_spike retired (orientation guard + dirty-cycle coverage live on as substrate tests). Input: ONE kernel routing path feeds pointer AND touch into ui surfaces with consume-or-pass semantics and implicit-grab ownership (the consumer of a press owns the matching release; per touch point too) — fixes drag-release-over-ui sticking. touch-mode: state machine + debounce + on_touch_mode_changed notification, NO visual scaling (user decision after hardware hands-on; dp-ratio stays 1.0; see plan §2). ext-xdg-shell: GrabMachine generalized to pointer-OR-touch interaction source (touch titlebar drag works; originating-point pinning); fixed the seat implicit-grab leak (suppressed release after forwarded press swallowed all later touch-downs — pointer/touch alternation doctested); factory renamed create(). ext-layer-shell: on_demand keyboard interactivity via scene hit resolution. host-bin: --ui-demo extension (temporary acceptance demo on the public contract, dies in slice 6). User hands-on verified: same surface by mouse and finger, tap counter, touch-mode neutrality, no click-through, drag alternation, fuzzel on_demand. 113 doctest cases green, ASan/UBSan clean (our code), idle RSS ≈78 MiB. Harness: UX-feel hands-on lesson (ORCHESTRATOR §2.6), nested-run pkill/setsid notes, touch-mode glossary redefinition.
Diffstat (limited to 'packages/ext-layer-shell/src')
-rw-r--r--packages/ext-layer-shell/src/ext_layer_shell.cpp100
1 files changed, 90 insertions, 10 deletions
diff --git a/packages/ext-layer-shell/src/ext_layer_shell.cpp b/packages/ext-layer-shell/src/ext_layer_shell.cpp
index 89a0012..5670c52 100644
--- a/packages/ext-layer-shell/src/ext_layer_shell.cpp
+++ b/packages/ext-layer-shell/src/ext_layer_shell.cpp
@@ -39,6 +39,21 @@ auto band_for_layer(enum zwlr_layer_shell_v1_layer layer) -> kernel::SceneLayer
return kernel::SceneLayer::top; // unreachable: protocol validates the enum
}
+// Give `surface` the seat keyboard focus, forwarding the currently-pressed
+// keycodes/modifiers if a keyboard is present (wlroots defers to any active
+// grab). Used for both `exclusive` (focus on map) and `on_demand` (focus on
+// click/tap) interactivity.
+void focus_keyboard(Host& host, wlr_surface* surface) {
+ wlr_seat* seat = host.seat();
+ wlr_keyboard* kbd = wlr_seat_get_keyboard(seat);
+ if (kbd != nullptr) {
+ wlr_seat_keyboard_notify_enter(seat, surface, kbd->keycodes,
+ kbd->num_keycodes, &kbd->modifiers);
+ } else {
+ wlr_seat_keyboard_notify_enter(seat, surface, nullptr, 0, nullptr);
+ }
+}
+
class LayerShellExt;
// One live layer surface: its scene node and the wlroots signal bindings.
@@ -114,6 +129,27 @@ public:
wl_list_for_each(lo, &host.output_layout()->outputs, link) {
track_output(lo->output);
}
+
+ // on_demand keyboard interactivity (zwlr v4): focus a layer surface when
+ // the user clicks OR taps it. We take focus only on interaction with OUR
+ // surface and never steal it back — clicking/tapping elsewhere lets focus
+ // move away normally (some other extension's hit handler, or a focus
+ // clear, owns that). These are fire-and-forget Events with N subscribers,
+ // so coexisting with ext-xdg-shell's toplevel-focusing handler on the
+ // same hit stream is fine — the hits are disjoint (its toplevels vs our
+ // layer surfaces). The kernel has already consumed any hit over its own
+ // UI surfaces before we see the event; layer surfaces are client surfaces
+ // and unaffected.
+ pointer_button_ = host.subscribe(
+ host.on_pointer_button(), [this](const kernel::PointerButtonEvent& e) {
+ if (e.pressed) {
+ focus_on_demand_at(e.lx, e.ly);
+ }
+ });
+ touch_down_ = host.subscribe(
+ host.on_touch_down(), [this](const kernel::TouchDownEvent& e) {
+ focus_on_demand_at(e.lx, e.ly);
+ });
}
[[nodiscard]] auto host() -> Host& { return *host_; }
@@ -255,6 +291,54 @@ private:
}
}
+ // Resolve the topmost scene surface at layout point (lx,ly); if it is one of
+ // OUR tracked, mapped layer surfaces requesting on_demand keyboard
+ // interactivity, give it keyboard focus. Called on pointer-button-press and
+ // touch-down. We never clear focus here: taking focus on a hit to our
+ // surface is the whole on_demand contract; moving focus AWAY is someone
+ // else's hit (or a focus clear), never our job.
+ void focus_on_demand_at(double lx, double ly) {
+ double nx = 0;
+ double ny = 0;
+ wlr_scene_node* node =
+ wlr_scene_node_at(&host_->scene()->tree.node, lx, ly, &nx, &ny);
+ if (node == nullptr || node->type != WLR_SCENE_NODE_BUFFER) {
+ return;
+ }
+ wlr_scene_buffer* buffer = wlr_scene_buffer_from_node(node);
+ wlr_scene_surface* scene_surface =
+ wlr_scene_surface_try_from_buffer(buffer);
+ if (scene_surface == nullptr) {
+ return;
+ }
+ // Map the hit wlr_surface back to its layer surface, then confirm it is
+ // OURS (tracked) and on_demand. The scene hit may land on a sub-surface
+ // or popup of the layer surface; try_from_wlr_surface only resolves the
+ // role surface itself, so also accept a hit whose layer surface we own.
+ wlr_layer_surface_v1* layer =
+ wlr_layer_surface_v1_try_from_wlr_surface(scene_surface->surface);
+ if (layer == nullptr || !owns(layer)) {
+ return;
+ }
+ if (!layer->surface->mapped) {
+ return;
+ }
+ if (layer->current.keyboard_interactive !=
+ ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) {
+ return;
+ }
+ focus_keyboard(*host_, layer->surface);
+ }
+
+ [[nodiscard]] auto owns(wlr_layer_surface_v1* layer) const -> bool {
+ for (const auto& ls : surfaces_) {
+ if (ls->wlr() == layer) {
+ return true;
+ }
+ }
+ return false;
+ }
+
// Per-output usable area (our pure-core mirror). N == #outputs (tiny); a
// flat pointer-keyed vector keeps the public header wlroots-free.
struct UsableEntry {
@@ -286,6 +370,8 @@ private:
Listener new_surface_;
kernel::Subscription output_added_;
kernel::Subscription output_removed_;
+ kernel::Subscription pointer_button_; // on_demand focus on click
+ kernel::Subscription touch_down_; // on_demand focus on tap
// A layer surface that arrived before any output existed. Held with only a
// destroy listener until an output appears (adopt_pending_surfaces), then
@@ -341,8 +427,9 @@ LayerSurface::LayerSurface(LayerShellExt& owner, wlr_layer_surface_v1* surface,
new_popup_.connect(surface_->events.new_popup, [](void*) {});
}
-// Minimal v1 keyboard interactivity: focus an `exclusive` surface once mapped;
-// leave `none` alone. `on_demand` is deferred to slice 5's input routing.
+// Keyboard interactivity on commit: focus an `exclusive` surface once mapped.
+// `none` and `on_demand` are left alone here — `on_demand` takes focus only on
+// a pointer/touch hit (LayerShellExt::focus_on_demand_at), never on commit.
void LayerSurface::update_keyboard_focus() {
if (!surface_->surface->mapped) {
return;
@@ -351,14 +438,7 @@ void LayerSurface::update_keyboard_focus() {
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) {
return;
}
- wlr_seat* seat = owner_.host().seat();
- wlr_keyboard* kbd = wlr_seat_get_keyboard(seat);
- if (kbd != nullptr) {
- wlr_seat_keyboard_notify_enter(seat, surface_->surface, kbd->keycodes,
- kbd->num_keycodes, &kbd->modifiers);
- } else {
- wlr_seat_keyboard_notify_enter(seat, surface_->surface, nullptr, 0, nullptr);
- }
+ focus_keyboard(owner_.host(), surface_->surface);
}
} // namespace