summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/input.cpp
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/kernel/src/input.cpp
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/kernel/src/input.cpp')
-rw-r--r--packages/kernel/src/input.cpp55
1 files changed, 36 insertions, 19 deletions
diff --git a/packages/kernel/src/input.cpp b/packages/kernel/src/input.cpp
index b6c4c0c..336947e 100644
--- a/packages/kernel/src/input.cpp
+++ b/packages/kernel/src/input.cpp
@@ -143,17 +143,13 @@ void Server::Impl::new_touch(wlr_input_device* device) {
// ---- Pointer (via wlr_cursor): move cursor + emit, route nothing ------------
void Server::Impl::emit_pointer_motion(std::uint32_t time_msec) {
- // Slice-3 spike input proof (kernel-internal; NOT a contract): forward
- // surface-local coords over the spike node so its button hovers.
- if (ui_spike != nullptr) {
- if (wlr_scene_node* spike = ui_spike->node()) {
- int nx = 0;
- int ny = 0;
- wlr_scene_node_coords(spike, &nx, &ny);
- ui_spike->on_pointer_motion(cursor->x - nx, cursor->y - ny);
- }
+ // Motion is ALWAYS observed by both the substrate (hover/leave on ui
+ // surfaces) and the bus (extensions hit-test the scene themselves). A ui-
+ // surface node is not a client surface, so a routing extension naturally
+ // finds "no client here" over a ui surface and clears stale client hover.
+ if (substrate != nullptr) {
+ substrate->route_pointer_motion(cursor->x, cursor->y, time_msec);
}
-
const PointerMotionEvent ev{cursor->x, cursor->y, time_msec};
ev_pointer_motion.emit(ev);
}
@@ -173,22 +169,23 @@ void Server::Impl::attach_cursor_handlers() {
const auto* event = static_cast<wlr_pointer_button_event*>(data);
const bool pressed = event->state == WL_POINTER_BUTTON_STATE_PRESSED;
- // Slice-3 spike input proof (kernel-internal): forward clicks over the
- // spike node so its button reacts.
- if (ui_spike != nullptr) {
- if (wlr_scene_node* spike = ui_spike->node()) {
- if (wlr_scene_node_at(spike, cursor->x, cursor->y, nullptr, nullptr) != nullptr) {
- ui_spike->on_pointer_button(pressed);
- }
- }
+ // Consumption order: the substrate gets first refusal. If the click is
+ // over a visible ui surface it consumes it (drives the document) and we
+ // do NOT emit on the bus — no click-through to clients beneath.
+ if (substrate != nullptr &&
+ substrate->route_pointer_button(cursor->x, cursor->y, pressed, event->time_msec)) {
+ return;
}
-
const PointerButtonEvent ev{event->button, pressed, cursor->x, cursor->y,
event->time_msec};
ev_pointer_button.emit(ev);
});
cursor_axis.connect(cursor->events.axis, [this](void* data) {
const auto* event = static_cast<wlr_pointer_axis_event*>(data);
+ if (substrate != nullptr &&
+ substrate->route_pointer_axis(cursor->x, cursor->y, event->delta, event->time_msec)) {
+ return; // consumed by a ui surface
+ }
const PointerAxisEvent ev{event->orientation, event->delta, event->delta_discrete,
event->source, event->time_msec};
ev_pointer_axis.emit(ev);
@@ -203,6 +200,12 @@ void Server::Impl::attach_cursor_handlers() {
double ly = 0;
wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y,
&lx, &ly);
+ // Substrate first refusal (consume-or-pass). A down over a ui surface
+ // is captured by the substrate (tap = click) and not emitted on the bus.
+ if (substrate != nullptr &&
+ substrate->route_touch_down(event->touch_id, lx, ly, event->time_msec)) {
+ return;
+ }
const TouchDownEvent ev{event->touch_id, lx, ly, event->time_msec};
ev_touch_down.emit(ev);
});
@@ -212,16 +215,30 @@ void Server::Impl::attach_cursor_handlers() {
double ly = 0;
wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y,
&lx, &ly);
+ // If this touch id was captured by a ui surface at down, the substrate
+ // keeps it (and consumes the motion); otherwise it passes to the bus.
+ if (substrate != nullptr &&
+ substrate->route_touch_motion(event->touch_id, lx, ly, event->time_msec)) {
+ return;
+ }
const TouchMotionEvent ev{event->touch_id, lx, ly, event->time_msec};
ev_touch_motion.emit(ev);
});
cursor_touch_up.connect(cursor->events.touch_up, [this](void* data) {
const auto* event = static_cast<wlr_touch_up_event*>(data);
+ if (substrate != nullptr && substrate->route_touch_up(event->touch_id, event->time_msec)) {
+ return; // a captured (ui-surface) touch ended
+ }
const TouchUpEvent ev{event->touch_id, event->time_msec};
ev_touch_up.emit(ev);
});
cursor_touch_cancel.connect(cursor->events.touch_cancel, [this](void* data) {
const auto* event = static_cast<wlr_touch_cancel_event*>(data);
+ // A cancel of a substrate-captured touch releases the RML button and is
+ // consumed; otherwise it passes to the bus.
+ if (substrate != nullptr && substrate->route_touch_up(event->touch_id, event->time_msec)) {
+ return;
+ }
const TouchCancelEvent ev{event->touch_id};
ev_touch_cancel.emit(ev);
});