diff options
| author | Adam Malczewski <[email protected]> | 2026-06-15 14:17:19 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-15 14:17:19 +0900 |
| commit | f0f372ef6ca9cf77ca774583192e83a4cde88d32 (patch) | |
| tree | 871a330407a0f0fca0f32b50edcdea805fece79a /packages/kernel/src/ui_substrate.cpp | |
| parent | ed1e3d60fe209af06911c640a145936f71adc238 (diff) | |
| download | unbox-f0f372ef6ca9cf77ca774583192e83a4cde88d32.tar.gz unbox-f0f372ef6ca9cf77ca774583192e83a4cde88d32.zip | |
rml-compositing W3: click/tap-to-focus (kernel on_pressed + window-field)
Completes the window-manager input story.
kernel (additive): SurfaceElement::on_pressed(std::function<void()>) — the
substrate invokes it (error-isolated to the owner) when a pointer button PRESS
or touch DOWN is routed to that surface element (root or any subsurface/popup
child fires the root element's handler), in addition to the existing client
input-back forwarding. Not fired on motion/release/miss. It is the
click/tap-to-focus SIGNAL; focus policy stays the wm's.
ext-window-field: on map, sets the element's on_pressed -> that toplevel's
Toplevel::focus() (keyboard focus + on_toplevel_focused -> RCSS raise/highlight),
guarded on the window still being tracked. So a click/tap on a background window
now focuses it (the gap noted in Wave 3).
kernel + ext-window-field + ext-xdg-shell suites green; build-asan green, no new
unbox::-framed leak/UB (the stored handler dies with the element).
Diffstat (limited to 'packages/kernel/src/ui_substrate.cpp')
| -rw-r--r-- | packages/kernel/src/ui_substrate.cpp | 57 |
1 files changed, 56 insertions, 1 deletions
diff --git a/packages/kernel/src/ui_substrate.cpp b/packages/kernel/src/ui_substrate.cpp index 0a4b508..fd42a44 100644 --- a/packages/kernel/src/ui_substrate.cpp +++ b/packages/kernel/src/ui_substrate.cpp @@ -659,6 +659,14 @@ struct SurfaceNode { struct SurfaceElementState { Substrate::Impl* owner = nullptr; int id = 0; + // The OWNING extension (for error isolation of on_pressed). A throwing + // on_pressed handler disables THIS extension only, via owner->disable(who). + ExtensionId who{}; + + // The click/tap-to-focus SIGNAL handler (ui.hpp SurfaceElement::on_pressed): + // invoked, error-isolated, when a pointer-button PRESS or a touch DOWN is + // routed to this element (root OR a child node). Default none (no-op). + std::function<void()> on_pressed_cb; // The ROOT node. `root.surface` is the wl_surface passed to // create_surface_element — a BORROW the caller outlives (ui.hpp). `root.uri` @@ -874,6 +882,12 @@ struct Substrate::Impl { std::uint32_t button, std::uint32_t time_msec) -> bool; auto forward_touch_to_client(Surface& surf, double lx, double ly, std::int32_t id, bool down, std::uint32_t time_msec) -> bool; + // Fire `el`'s on_pressed handler (the click/tap-to-focus SIGNAL), if set. + // Called ONLY for a pointer-button PRESS / touch DOWN routed to a surface + // element (root OR child) — never on motion/release/miss. Error-isolated to + // the owning extension (a throw => disable(el.who)), like the other ui + // callbacks; never takes down the session. + void fire_pressed(SurfaceElementState& el); // Keyboard-focus PRIMITIVE (mechanism only — focus POLICY is a later wave). // Give `el`'s ROOT client surface seat keyboard focus + send the enter with // the active keyboard's pressed keys + modifiers, so the kernel's existing @@ -1973,6 +1987,15 @@ auto Substrate::Impl::forward_pointer_to_client(Surface& surf, double lx, double wlr_seat_pointer_notify_clear_focus(seat); return false; } + // Click/tap-to-focus SIGNAL (ui.hpp SurfaceElement::on_pressed): a PRESS that + // lands on this element (root OR child) notifies the OWNER, in addition to the + // client forwarding below. `el` is the owning element (root), so a child-node + // press fires the ROOT element's handler. Fired only for button_down (not + // motion/release) and only on a hit — and BEFORE the coord projection, so the + // focus signal fires even for an edge-on element where no client coord exists. + if (kind == PointerKind::button_down && el != nullptr) { + fire_pressed(*el); + } // Project the screen point onto the node img's OWN (possibly 3D-transformed) // plane FIRST (Element::Project: the spike fix; no-op when untransformed), // then box->surface-local scale. The point fed to the context was in @@ -2023,6 +2046,13 @@ auto Substrate::Impl::forward_touch_to_client(Surface& surf, double lx, double l if (node == nullptr || node->surface == nullptr) { return false; } + // Click/tap-to-focus SIGNAL (mirror forward_pointer_to_client): a touch DOWN + // that lands on this element (root OR child) notifies the OWNER too. `el` is + // the root element, so a child-node down fires the ROOT element's handler. + // Only on a DOWN (not a motion) and only on a hit, before coord projection. + if (down && el != nullptr) { + fire_pressed(*el); + } Rml::Element* img = find_img_by_src(surf.document, node->uri); if (img == nullptr) { return false; @@ -2047,6 +2077,23 @@ auto Substrate::Impl::forward_touch_to_client(Surface& surf, double lx, double l return true; } +void Substrate::Impl::fire_pressed(SurfaceElementState& el) { + // The click/tap-to-focus SIGNAL. Error-isolated exactly like the other + // substrate callbacks (bind_event / bind_drag): a throwing handler disables + // the OWNING extension only (owner->disable(who)), never the session. A + // missing handler (default) is a no-op. + if (!el.on_pressed_cb) { + return; + } + try { + el.on_pressed_cb(); + } catch (...) { + if (disable) { + disable(el.who); + } + } +} + void Substrate::Impl::focus_keyboard(SurfaceElementState& el) { // Mechanism only (focus POLICY is Wave 3). Mirror src/input.cpp / // ext-xdg-shell discipline: pick the seat's current keyboard, set it on the @@ -2211,7 +2258,8 @@ auto Substrate::create_preview(wlr_scene_tree* source) -> std::unique_ptr<Previe auto Substrate::preview_import_is_dmabuf() const -> bool { return impl_->last_preview_dmabuf; } -auto Substrate::create_surface_element(wlr_surface* client) -> std::unique_ptr<SurfaceElement> { +auto Substrate::create_surface_element(ExtensionId who, wlr_surface* client) + -> std::unique_ptr<SurfaceElement> { impl_->last_surface_element_dmabuf = false; if (!impl_->available() || client == nullptr) { return nullptr; // no GL path or no surface: graceful degrade (ui.hpp) @@ -2220,6 +2268,7 @@ auto Substrate::create_surface_element(wlr_surface* client) -> std::unique_ptr<S impl_->surface_elements.emplace_back(); SurfaceElementState& s = impl_->surface_elements.back(); s.owner = impl_.get(); + s.who = who; // for on_pressed error isolation s.id = ++impl_->next_surface_id; s.root.surface = client; s.root.uri = surface_element_uri(s.id); // == source_uri() @@ -3116,4 +3165,10 @@ auto SurfaceElementHandle::height() const -> int { return state_->root.height; } void SurfaceElementHandle::focus_keyboard() { substrate_->impl_->focus_keyboard(*state_); } +void SurfaceElementHandle::on_pressed(std::function<void()> handler) { + // One handler per element; re-setting replaces it (the stored std::function + // lives in the SurfaceElementState and dies with the element). + state_->on_pressed_cb = std::move(handler); +} + } // namespace unbox::kernel |
