summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/server.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/server.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/server.cpp')
-rw-r--r--packages/kernel/src/server.cpp93
1 files changed, 69 insertions, 24 deletions
diff --git a/packages/kernel/src/server.cpp b/packages/kernel/src/server.cpp
index e3308de..d02cf78 100644
--- a/packages/kernel/src/server.cpp
+++ b/packages/kernel/src/server.cpp
@@ -69,12 +69,53 @@ void Server::terminate() {
wl_display_terminate(impl_->display);
}
-auto Server::ui_spike_frame_count() const -> int {
- return impl_->ui_spike != nullptr ? impl_->ui_spike->frame_count() : 0;
+auto Server::ui_frame_count() const -> int {
+ return impl_->substrate != nullptr ? impl_->substrate->frame_count() : 0;
}
-auto Server::ui_spike_orientation() const -> int {
- return impl_->ui_spike != nullptr ? impl_->ui_spike->check_orientation() : 0;
+auto Server::ui_orientation() const -> int {
+ return impl_->substrate != nullptr ? impl_->substrate->orientation() : 0;
+}
+
+auto Server::ui_fence_sync_active() const -> bool {
+ return impl_->substrate != nullptr && impl_->substrate->fence_sync_active();
+}
+
+void Server::ui_set_touch_override(UiTouchOverride ov) {
+ if (impl_->substrate == nullptr) {
+ return;
+ }
+ UiSubstrate::TouchModeOverride mapped = UiSubstrate::TouchModeOverride::automatic;
+ if (ov == UiTouchOverride::force_off) {
+ mapped = UiSubstrate::TouchModeOverride::force_off;
+ } else if (ov == UiTouchOverride::force_on) {
+ mapped = UiSubstrate::TouchModeOverride::force_on;
+ }
+ impl_->substrate->set_touch_mode_override(mapped);
+}
+
+// ---- PerExtensionUi (per-extension ui-substrate facade) --------------------
+
+auto PerExtensionUi::create_surface(const UiSurfaceSpec& spec) -> std::unique_ptr<UiSurface> {
+ if (server_->substrate == nullptr) {
+ return nullptr;
+ }
+ wlr_scene_tree* parent = server_->scene_layers[static_cast<std::size_t>(spec.layer)];
+ return server_->substrate->create_surface(id_, parent, spec);
+}
+
+auto PerExtensionUi::available() const -> bool {
+ return server_->substrate != nullptr && server_->substrate->available();
+}
+
+auto PerExtensionUi::touch_mode() const -> bool {
+ return server_->substrate != nullptr && server_->substrate->touch_mode();
+}
+
+void PerExtensionUi::set_touch_mode_override(TouchModeOverride ov) {
+ if (server_->substrate != nullptr) {
+ server_->substrate->set_touch_mode_override(ov);
+ }
}
// ---- Impl lifecycle --------------------------------------------------------
@@ -169,9 +210,10 @@ void Server::Impl::init() {
throw std::runtime_error("failed to start the wlr_backend");
}
- if (options.ui_spike) {
- start_ui_spike();
- }
+ // The ui substrate is always built; it reports available()==false on a
+ // backend with no GL path (headless pixman) and create_surface yields
+ // nullptr there, so extensions degrade gracefully. Never throws.
+ start_substrate();
if (!options.startup_cmd.empty()) {
if (fork() == 0) {
@@ -273,20 +315,22 @@ void Server::Impl::activate_extensions() {
}
}
-void Server::Impl::start_ui_spike() {
- if (!wlr_renderer_is_gles2(renderer)) {
- wlr_log(WLR_INFO, "ui-spike: renderer is not gles2; spike disabled");
- return;
- }
- wlr_egl* egl = wlr_gles2_renderer_get_egl(renderer);
- if (egl == nullptr) {
- wlr_log(WLR_ERROR, "ui-spike: gles2 renderer has no wlr_egl");
- return;
+void Server::Impl::start_substrate() {
+ // The substrate needs the wlr renderer's EGLDisplay for its sibling GLES
+ // 3.2 context. Only the gles2 renderer exposes one; under pixman (headless
+ // CI) there is no GL path, so the substrate builds but reports unavailable.
+ EGLDisplay display_egl = EGL_NO_DISPLAY;
+ if (wlr_renderer_is_gles2(renderer)) {
+ if (wlr_egl* egl = wlr_gles2_renderer_get_egl(renderer)) {
+ display_egl = wlr_egl_get_display(egl);
+ }
+ } else {
+ wlr_log(WLR_INFO, "ui-substrate: renderer is not gles2; substrate unavailable");
}
- EGLDisplay display_egl = wlr_egl_get_display(egl);
- // The spike sits in the overlay band so it composites above everything.
- ui_spike = UiSpike::create(scene_layers[static_cast<std::size_t>(SceneLayer::overlay)],
- display_egl, allocator, renderer);
+ // A data-event/getter throw disables the owning extension via the same
+ // isolation path the bus uses (Server::Impl is the DisableSink).
+ substrate = Substrate::create(display_egl, allocator, renderer,
+ [this](ExtensionId who) { disable(who); });
}
void Server::Impl::shutdown() {
@@ -301,8 +345,9 @@ void Server::Impl::shutdown() {
}
extensions.clear();
- // Slice-3 spike: tear down before scene/renderer/allocator die.
- ui_spike.reset();
+ // The ui substrate owns scene nodes + GL objects on a sibling context and
+ // borrows scene/renderer/allocator: tear it down before they die.
+ substrate.reset();
if (display != nullptr) {
wl_display_destroy_clients(display);
@@ -377,8 +422,8 @@ void Server::Impl::handle_new_output(wlr_output* wlr_output) {
outputs.push_back(std::move(owned));
output->frame.connect(wlr_output->events.frame, [this, output](void*) {
- if (ui_spike != nullptr) {
- ui_spike->tick();
+ if (substrate != nullptr) {
+ substrate->tick_all();
}
wlr_scene_output* scene_output = wlr_scene_get_scene_output(scene, output->output);
wlr_scene_output_commit(scene_output, nullptr);