summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/server.cpp
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-15 07:08:23 +0900
committerAdam Malczewski <[email protected]>2026-06-15 07:08:23 +0900
commitc38f328c8b38546a19fc4cd955e01e70b42d624d (patch)
tree6c34654598b7aabd6f1dfbac0c2b36bb0e206100 /packages/kernel/src/server.cpp
parent74d75fdd8b8654446338ae03bf2a133512408ec6 (diff)
downloadunbox-c38f328c8b38546a19fc4cd955e01e70b42d624d.tar.gz
unbox-c38f328c8b38546a19fc4cd955e01e70b42d624d.zip
kernel(rml-compositing W1b): surface trees + input-back + keyboard-focus
Extends the live SurfaceElement to the whole surface TREE and routes input back to clients. Surface trees: - create_surface_element(root) now manages subsurfaces + xdg popups as per-subsurface child elements (unbox-surface://N.K), each its own live seq-gated texture at its tree offset; the substrate re-walks the live tree each dirty tick (wlr_surface_for_each_surface + popup walk), reconciling by wl_surface identity and dropping a node the instant it leaves the tree. - Frame-callback duty now walks the WHOLE tree per composited frame. - Parent-relative child placement (place_child_box, pure core): child <img> positioned relative to the root img's resolved box, so a moving/resized parent drags its children; popups unclipped. Caller tracks only the root. Input-back (automatic; the wm wires no seat calls): - pure core src/input_core.hpp (port of the spike's spike_input_core): project_to_screen/unproject_to_local + place_child_box, doctested (criterion-3 round-trip < 0.01px; affine exact). - a pointer/touch pick landing on a surface-element node maps the point through the node img's real RCSS transform via Element::Project, then box->surface- local, and forwards to that client via wl_seat at surface-local px. Normal RML picks still fire bind_event/bind_drag unchanged. Cursor stays a wlr plane. - SurfaceElement::focus_keyboard() (new public method): seat keyboard-focus MECHANISM only (focus POLICY is the window-field wave); cleared on destroy. Tests: pure-core doctests + a headless test driving a REAL in-process client (toplevel root + subsurface + xdg popup): tree composes as >=3 child <img>, whole-tree frame-done, pointer enter/motion/button + touch at expected surface- local coords (incl. a rotateY(35) transformed-element case proving Project), keyboard enter+key. kernel suite 72c/375a green; build-asan 0 records (the seat/per-node-import/listener lifetimes clean). Spike untouched.
Diffstat (limited to 'packages/kernel/src/server.cpp')
-rw-r--r--packages/kernel/src/server.cpp83
1 files changed, 82 insertions, 1 deletions
diff --git a/packages/kernel/src/server.cpp b/packages/kernel/src/server.cpp
index b79337c..8b67c8a 100644
--- a/packages/kernel/src/server.cpp
+++ b/packages/kernel/src/server.cpp
@@ -1,5 +1,7 @@
#include "server_impl.hpp"
+#include <xkbcommon/xkbcommon.h> // virtual-keyboard test seam keymap
+
#include <ctime>
#include <stdexcept>
#include <unistd.h>
@@ -152,6 +154,78 @@ auto Server::ui_reload_surface() -> bool {
return impl_->substrate != nullptr && impl_->substrate->reload_first_surface();
}
+void Server::ui_route_pointer_motion_for_test(double lx, double ly, unsigned int time_msec) {
+ if (impl_->substrate != nullptr) {
+ impl_->substrate->route_pointer_motion(lx, ly, time_msec);
+ }
+}
+
+void Server::ui_route_pointer_button_for_test(double lx, double ly, bool pressed,
+ unsigned int time_msec) {
+ if (impl_->substrate != nullptr) {
+ // A button needs a hover first (the cursor is already at (lx,ly) on a real
+ // seat); feed a motion so the substrate's pick is current, then the button.
+ impl_->substrate->route_pointer_motion(lx, ly, time_msec);
+ (void)impl_->substrate->route_pointer_button(lx, ly, pressed, time_msec);
+ }
+}
+
+void Server::ui_route_touch_down_for_test(int id, double lx, double ly, unsigned int time_msec) {
+ if (impl_->substrate != nullptr) {
+ (void)impl_->substrate->route_touch_down(id, lx, ly, time_msec);
+ }
+}
+
+void Server::ui_route_touch_up_for_test(int id, unsigned int time_msec) {
+ if (impl_->substrate != nullptr) {
+ (void)impl_->substrate->route_touch_up(id, time_msec);
+ }
+}
+
+namespace {
+// A no-op wlr_keyboard impl for the virtual test keyboard (it never produces LED
+// updates; the seat only needs a keyboard object + keymap to ship an enter).
+const wlr_keyboard_impl kTestKeyboardImpl = {
+ .name = "unbox-test-keyboard",
+ .led_update = nullptr,
+};
+} // namespace
+
+void Server::ui_add_test_keyboard() {
+ if (impl_->test_keyboard != nullptr || impl_->seat == nullptr) {
+ return;
+ }
+ auto* kb = new wlr_keyboard();
+ wlr_keyboard_init(kb, &kTestKeyboardImpl, "unbox-test-keyboard");
+ xkb_context* ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ xkb_keymap* keymap = xkb_keymap_new_from_names(ctx, nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS);
+ wlr_keyboard_set_keymap(kb, keymap);
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(ctx);
+ impl_->test_keyboard = kb;
+ // Advertise the keyboard capability + set it on the seat so an enter ships
+ // the keymap (mirrors input.cpp::new_keyboard's seat wiring).
+ wlr_seat_set_capabilities(impl_->seat, WL_SEAT_CAPABILITY_POINTER |
+ WL_SEAT_CAPABILITY_TOUCH |
+ WL_SEAT_CAPABILITY_KEYBOARD);
+ wlr_seat_set_keyboard(impl_->seat, kb);
+}
+
+void Server::ui_send_key_for_test(unsigned int keycode, bool pressed) {
+ if (impl_->seat == nullptr) {
+ return;
+ }
+ // The post-filter equivalent of input.cpp's key path: the seat forwards the
+ // key to whatever surface holds keyboard focus (set by focus_keyboard()).
+ timespec now{};
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ const std::uint32_t t = static_cast<std::uint32_t>(now.tv_sec) * 1000U +
+ static_cast<std::uint32_t>(now.tv_nsec) / 1000000U;
+ wlr_seat_keyboard_notify_key(impl_->seat, t, keycode,
+ pressed ? WL_KEYBOARD_KEY_STATE_PRESSED
+ : WL_KEYBOARD_KEY_STATE_RELEASED);
+}
+
void Server::ui_set_touch_override(UiTouchOverride ov) {
if (impl_->substrate == nullptr) {
return;
@@ -446,7 +520,7 @@ void Server::Impl::start_substrate() {
// isolation path the bus uses (Server::Impl is the DisableSink). The
// substrate uses the kernel's ONE shared FileWatcher for (UNBOX_DEV-gated)
// asset hot-reload — the same watcher Host::watch_file uses for config.
- substrate = Substrate::create(display_egl, allocator, renderer, file_watcher(),
+ substrate = Substrate::create(display_egl, allocator, renderer, seat, file_watcher(),
[this](ExtensionId who) { disable(who); },
[this] { schedule_driver_frame(); });
}
@@ -502,6 +576,13 @@ void Server::Impl::shutdown() {
test_new_surface.disconnect();
test_surface_commits.clear();
test_last_client_surface = nullptr;
+ // The virtual test keyboard (if added) is finished + freed before the seat /
+ // display die (a wlr_keyboard outliving the seat it was set on is UB).
+ if (test_keyboard != nullptr) {
+ wlr_keyboard_finish(test_keyboard);
+ delete test_keyboard;
+ test_keyboard = nullptr;
+ }
// The ui substrate owns scene nodes + GL objects on a sibling context and
// borrows scene/renderer/allocator: tear it down before they die. (Its asset