diff options
| author | Adam Malczewski <[email protected]> | 2026-06-15 07:08:23 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-15 07:08:23 +0900 |
| commit | c38f328c8b38546a19fc4cd955e01e70b42d624d (patch) | |
| tree | 6c34654598b7aabd6f1dfbac0c2b36bb0e206100 /packages/kernel/src/server.cpp | |
| parent | 74d75fdd8b8654446338ae03bf2a133512408ec6 (diff) | |
| download | unbox-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.cpp | 83 |
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 |
