summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/toplevel.cpp
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-12 22:44:16 +0900
committerAdam Malczewski <[email protected]>2026-06-12 22:44:16 +0900
commitc102a1b67a70149b6f9c9b2cfd8b31ceb52c09b7 (patch)
treef6dea2875b939c0f661292d8bfa0d79a96fe67d7 /packages/kernel/src/toplevel.cpp
parent6949c3582ed1e480e70aabfcfa3a11b78007cc12 (diff)
downloadunbox-c102a1b67a70149b6f9c9b2cfd8b31ceb52c09b7.tar.gz
unbox-c102a1b67a70149b6f9c9b2cfd8b31ceb52c09b7.zip
Slice 4: extension host + typed bus; xdg-shell/layer-shell extracted to core extensions
The kernel now names NO concrete feature. It owns: the extension host (install/topological activate, missing-dep/cycle = startup error), the typed Event/Filter bus (error-isolated: a throwing extension is disabled, never the session; RAII Subscriptions), the Host API (per-extension facade: borrows, scene layers, event catalogue, typed services), the public RAII Listener, and a typed surface→scene-tree registry (Host::host_surface/scene_tree_for) that replaced the untyped wlr_surface.data convention both extensions flagged. - ext-xdg-shell (core): toplevel/popup lifecycle, focus-on-map, click/tap-to-focus, pointer/touch routing incl. button+axis (the kernel only moves the cursor and emits — a contract-doc lie caught by user hands-on), interactive move/resize via pure GrabMachine (fixes the request-arrives-after-release race: grab requires request ∧ button-down, release always ends it), Alt+F1 cycle, Ctrl+Alt+Backspace terminate (labwc's default A-Escape=Exit killed the dev session once — never again; see nested-run skill). - ext-layer-shell (core): wlr-layer-shell v1 (proto v5) for external clients; pure doctest-hard arrangement core; fuzzel verified visually nested (fix: seed outputs from output_layout at activate — events-only tracking missed pre-activation outputs; plus a scene-node double-free). - First protocol codegen: vendored wlr-layer-shell XML + wayland-scanner server-header propagated through kernel_dep; wlr.hpp grew a namespace→_namespace keyword fix for the generated header. - Glossary: 'scene layer' (user-approved). New rules earned: parallel-wave-builds, contract-docs. - User hands-on verified: typing, click-to-focus, drag-select, scroll, titlebar drag-move (slow + flick), Alt+F1, fuzzel + arrows, touch tap, Ctrl+Alt+Backspace. 68 doctest cases green, ASan/UBSan clean (our code), idle RSS ≈73 MiB.
Diffstat (limited to 'packages/kernel/src/toplevel.cpp')
-rw-r--r--packages/kernel/src/toplevel.cpp190
1 files changed, 0 insertions, 190 deletions
diff --git a/packages/kernel/src/toplevel.cpp b/packages/kernel/src/toplevel.cpp
deleted file mode 100644
index 150870d..0000000
--- a/packages/kernel/src/toplevel.cpp
+++ /dev/null
@@ -1,190 +0,0 @@
-#include "server_impl.hpp"
-
-#include <algorithm>
-
-namespace unbox::kernel {
-
-// ---- Focus ------------------------------------------------------------------
-
-void Server::Impl::focus_toplevel(Toplevel* toplevel) {
- // Keyboard focus only (pointer focus follows the cursor).
- if (toplevel == nullptr) {
- return;
- }
- wlr_surface* surface = toplevel->xdg_toplevel->base->surface;
- wlr_surface* prev_surface = seat->keyboard_state.focused_surface;
- if (prev_surface == surface) {
- return;
- }
- if (prev_surface != nullptr) {
- // Deactivate the previously focused toplevel (client stops drawing
- // its focused decoration state, e.g. hides the caret).
- wlr_xdg_toplevel* prev = wlr_xdg_toplevel_try_from_wlr_surface(prev_surface);
- if (prev != nullptr) {
- wlr_xdg_toplevel_set_activated(prev, false);
- }
- }
-
- wlr_scene_node_raise_to_top(&toplevel->scene_tree->node);
- auto it = std::find(mapped_toplevels.begin(), mapped_toplevels.end(), toplevel);
- if (it != mapped_toplevels.end()) {
- mapped_toplevels.splice(mapped_toplevels.begin(), mapped_toplevels, it);
- }
- wlr_xdg_toplevel_set_activated(toplevel->xdg_toplevel, true);
-
- // The seat tracks the focused surface and routes key events to it.
- if (wlr_keyboard* keyboard = wlr_seat_get_keyboard(seat)) {
- wlr_seat_keyboard_notify_enter(seat, surface, keyboard->keycodes,
- keyboard->num_keycodes, &keyboard->modifiers);
- }
-}
-
-auto Server::Impl::toplevel_at(double lx, double ly, wlr_surface** surface, double* sx, double* sy)
- -> Toplevel* {
- // Topmost scene node at the given layout coords; we only care about
- // buffer nodes belonging to a surface tree rooted at a Toplevel.
- wlr_scene_node* node = wlr_scene_node_at(&scene->tree.node, lx, ly, sx, sy);
- if (node == nullptr || node->type != WLR_SCENE_NODE_BUFFER) {
- return nullptr;
- }
- wlr_scene_buffer* scene_buffer = wlr_scene_buffer_from_node(node);
- wlr_scene_surface* scene_surface = wlr_scene_surface_try_from_buffer(scene_buffer);
- if (scene_surface == nullptr) {
- return nullptr;
- }
- *surface = scene_surface->surface;
-
- // Walk up to the tree whose data field we set: the Toplevel root.
- wlr_scene_tree* tree = node->parent;
- while (tree != nullptr && tree->node.data == nullptr) {
- tree = tree->node.parent;
- }
- if (tree == nullptr) {
- return nullptr;
- }
- return static_cast<Toplevel*>(tree->node.data);
-}
-
-// ---- Interactive move/resize grabs -------------------------------------------
-
-void Server::Impl::reset_cursor_mode() {
- cursor_mode = CursorMode::Passthrough;
- grabbed_toplevel = nullptr;
-}
-
-void Server::Impl::begin_interactive(Toplevel* toplevel, CursorMode mode, std::uint32_t edges) {
- // The compositor consumes pointer events itself during a grab instead
- // of forwarding them. (tinywl note kept: a fuller compositor would
- // verify this against a recent button-press serial.)
- grabbed_toplevel = toplevel;
- cursor_mode = mode;
-
- if (mode == CursorMode::Move) {
- grab_x = cursor->x - toplevel->scene_tree->node.x;
- grab_y = cursor->y - toplevel->scene_tree->node.y;
- } else {
- wlr_box* geo_box = &toplevel->xdg_toplevel->base->geometry;
- const double border_x = (toplevel->scene_tree->node.x + geo_box->x) +
- ((edges & WLR_EDGE_RIGHT) != 0 ? geo_box->width : 0);
- const double border_y = (toplevel->scene_tree->node.y + geo_box->y) +
- ((edges & WLR_EDGE_BOTTOM) != 0 ? geo_box->height : 0);
- grab_x = cursor->x - border_x;
- grab_y = cursor->y - border_y;
-
- grab_geobox = *geo_box;
- grab_geobox.x += toplevel->scene_tree->node.x;
- grab_geobox.y += toplevel->scene_tree->node.y;
- resize_edges = edges;
- }
-}
-
-// ---- xdg-shell toplevels ------------------------------------------------------
-
-void Server::Impl::handle_new_toplevel(wlr_xdg_toplevel* xdg_toplevel) {
- auto owned = std::make_unique<Toplevel>();
- Toplevel* toplevel = owned.get();
- toplevel->server = this;
- toplevel->xdg_toplevel = xdg_toplevel;
- toplevel->scene_tree = wlr_scene_xdg_surface_create(&scene->tree, xdg_toplevel->base);
- toplevel->scene_tree->node.data = toplevel;
- // Popups look this up to find their parent's scene tree.
- xdg_toplevel->base->data = toplevel->scene_tree;
- toplevels.emplace(xdg_toplevel, std::move(owned));
-
- toplevel->map.connect(xdg_toplevel->base->surface->events.map, [this, toplevel](void*) {
- mapped_toplevels.push_front(toplevel);
- focus_toplevel(toplevel);
- wlr_log(WLR_INFO, "toplevel mapped: %s",
- toplevel->xdg_toplevel->title != nullptr ? toplevel->xdg_toplevel->title : "?");
- });
- toplevel->unmap.connect(xdg_toplevel->base->surface->events.unmap, [this, toplevel](void*) {
- if (toplevel == grabbed_toplevel) {
- reset_cursor_mode();
- }
- mapped_toplevels.remove(toplevel);
- });
- toplevel->commit.connect(xdg_toplevel->base->surface->events.commit, [toplevel](void*) {
- if (toplevel->xdg_toplevel->base->initial_commit) {
- // Reply to the initial commit with a 0x0 configure: the client
- // picks its own dimensions.
- wlr_xdg_toplevel_set_size(toplevel->xdg_toplevel, 0, 0);
- }
- });
- toplevel->destroy.connect(xdg_toplevel->events.destroy, [this, toplevel](void*) {
- // Last action: destroys `toplevel` (and these listeners with it).
- toplevels.erase(toplevel->xdg_toplevel);
- });
-
- toplevel->request_move.connect(xdg_toplevel->events.request_move, [this, toplevel](void*) {
- begin_interactive(toplevel, CursorMode::Move, 0);
- });
- toplevel->request_resize.connect(
- xdg_toplevel->events.request_resize, [this, toplevel](void* data) {
- const auto* event = static_cast<wlr_xdg_toplevel_resize_event*>(data);
- begin_interactive(toplevel, CursorMode::Resize, event->edges);
- });
- toplevel->request_maximize.connect(
- xdg_toplevel->events.request_maximize, [toplevel](void*) {
- // Unsupported, but xdg-shell demands a configure reply.
- if (toplevel->xdg_toplevel->base->initialized) {
- wlr_xdg_surface_schedule_configure(toplevel->xdg_toplevel->base);
- }
- });
- toplevel->request_fullscreen.connect(
- xdg_toplevel->events.request_fullscreen, [toplevel](void*) {
- if (toplevel->xdg_toplevel->base->initialized) {
- wlr_xdg_surface_schedule_configure(toplevel->xdg_toplevel->base);
- }
- });
-}
-
-// ---- xdg-shell popups ----------------------------------------------------------
-
-void Server::Impl::handle_new_popup(wlr_xdg_popup* xdg_popup) {
- auto owned = std::make_unique<Popup>();
- Popup* popup = owned.get();
- popup->xdg_popup = xdg_popup;
- popups.emplace(xdg_popup, std::move(owned));
-
- // Parent's scene tree was stashed in base->data when it was created
- // (toplevel or ancestor popup).
- wlr_xdg_surface* parent = wlr_xdg_surface_try_from_wlr_surface(xdg_popup->parent);
- if (parent != nullptr && parent->data != nullptr) {
- auto* parent_tree = static_cast<wlr_scene_tree*>(parent->data);
- xdg_popup->base->data = wlr_scene_xdg_surface_create(parent_tree, xdg_popup->base);
- }
-
- popup->commit.connect(xdg_popup->base->surface->events.commit, [popup](void*) {
- if (popup->xdg_popup->base->initial_commit) {
- // A fuller compositor would also unconstrain the popup to keep
- // it on-screen here.
- wlr_xdg_surface_schedule_configure(popup->xdg_popup->base);
- }
- });
- popup->destroy.connect(xdg_popup->events.destroy, [this, popup](void*) {
- // Last action: destroys `popup` (and these listeners with it).
- popups.erase(popup->xdg_popup);
- });
-}
-
-} // namespace unbox::kernel