wl_backend: stacking_raise raises frame + client + children together

When raising a frame_buf, also raise its children (titlebar,
resizebar) and the client surface inside it. When raising a
client surface, also raise its containing frame. This ensures
click-on-titlebar raises the entire window assembly.
This commit is contained in:
Levi Neely 2026-05-14 19:28:36 +02:00
parent 30f8fe51c8
commit 509a59ec81
2 changed files with 128 additions and 31 deletions

View File

@ -287,6 +287,7 @@ struct wl_frame_buf {
struct wlr_buffer base; /* wlr_buffer (must be first) */
pixman_image_t *image; /* ARGB32 pixel data */
struct wlr_scene_buffer *scene_buf; /* scene graph node */
struct wlr_scene_tree *tree; /* grouping tree (top-level frames only) */
WNativeWindow id; /* synthetic ID (>= 0x10000000) */
WNativeWindow parent_id; /* parent frame_buf ID (0 = root) */
int x, y; /* position in output coords */
@ -3766,6 +3767,8 @@ frame_buf_find(WNativeWindow id)
static struct wl_frame_buf *
frame_buf_at(int px, int py)
{
/* Find the topmost frame_buf at this position, but only if no client
* surface from a DIFFERENT window is above it in the scene graph. */
struct wl_frame_buf *hit = NULL;
struct wl_frame_buf *fb;
wl_list_for_each(fb, &wl_state.frame_list, link) {
@ -3778,6 +3781,35 @@ frame_buf_at(int px, int py)
hit = fb;
}
}
if (!hit) return NULL;
/* Check if a client surface from a DIFFERENT window is above us */
if (wl_state.scene) {
double sx, sy;
struct wlr_scene_node *node = wlr_scene_node_at(
&wl_state.scene->tree.node, px, py, &sx, &sy);
if (node) {
/* Find which tree this node belongs to */
struct wlr_scene_node *n = node;
while (n->parent && &n->parent->node != &wl_state.scene->tree.node)
n = &n->parent->node;
/* Find which tree our hit frame_buf belongs to */
struct wlr_scene_node *h = &hit->scene_buf->node;
while (h->parent && &h->parent->node != &wl_state.scene->tree.node)
h = &h->parent->node;
/* Same tree = same window, allow the hit */
if (n == h)
return hit;
/* Different tree: check if topmost is a frame_buf */
struct wl_frame_buf *fb2;
wl_list_for_each(fb2, &wl_state.frame_list, link) {
if (fb2->scene_buf && &fb2->scene_buf->node == node)
return fb2;
}
/* Topmost node is a client surface from another window */
return NULL;
}
}
return hit;
}
@ -3817,13 +3849,17 @@ wl_frame_create_toplevel(WScreen *scr,
fb->width = width;
fb->height = height;
/* Create scene buffer node. */
/* Create scene buffer node under a per-window tree. */
if (wl_state.scene) {
fb->scene_buf = wlr_scene_buffer_create(&wl_state.scene->tree,
&fb->base);
if (fb->scene_buf) {
wlr_scene_node_set_position(&fb->scene_buf->node, x, y);
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
fb->tree = wlr_scene_tree_create(&wl_state.scene->tree);
if (fb->tree) {
wlr_scene_node_set_position(&fb->tree->node, 0, 0);
fb->scene_buf = wlr_scene_buffer_create(fb->tree, &fb->base);
if (fb->scene_buf) {
wlr_scene_node_set_position(&fb->scene_buf->node, x, y);
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
}
wlr_scene_node_set_enabled(&fb->tree->node, true);
}
}
@ -3870,8 +3906,9 @@ wl_frame_create_child(WNativeWindow parent_win, WScreen *scr,
fb->height = height;
if (wl_state.scene) {
fb->scene_buf = wlr_scene_buffer_create(&wl_state.scene->tree,
&fb->base);
struct wlr_scene_tree *ptree = (parent && parent->tree)
? parent->tree : &wl_state.scene->tree;
fb->scene_buf = wlr_scene_buffer_create(ptree, &fb->base);
if (fb->scene_buf) {
wlr_scene_node_set_position(&fb->scene_buf->node, abs_x, abs_y);
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
@ -4839,9 +4876,6 @@ handle_pointer_button(struct wl_listener *listener, void *data)
* pointer and focus + raise it via the WM policy layer.
* This mirrors what the X11 backend does via ButtonPress on frame windows. */
if (ev->state == WLR_BUTTON_PRESSED) {
/* If pointer is over WM chrome (menu, titlebar), don't raise clients */
if (frame_buf_at(wl_state.pointer_x, wl_state.pointer_y))
goto deliver;
double sx, sy;
struct wl_toplevel_view *view = NULL;
view_surface_at(wl_state.pointer_x, wl_state.pointer_y,
@ -4853,21 +4887,24 @@ handle_pointer_button(struct wl_listener *listener, void *data)
WWindow *wwin = (WWindow *)desc->parent;
WScreen *scr = wScreenWithNumber(0);
wSetFocusTo(scr, wwin);
/* Raise the window in scene Z-order by moving its scene node
* to the top of the scene root's child list. */
if (view->scene_node)
/* Raise entire window assembly via tree */
if (view->frame_id) {
struct wl_frame_buf *fb = frame_buf_find(view->frame_id);
if (fb && fb->tree)
wlr_scene_node_raise_to_top(&fb->tree->node);
} else if (view->scene_node) {
wlr_scene_node_raise_to_top(&view->scene_node->node);
}
/* Also raise it in our toplevel list so view_surface_at()
* returns it first on subsequent hits. */
wl_list_remove(&view->link);
wl_list_insert(wl_state.toplevel_list.prev, &view->link);
wl_list_insert(&wl_state.toplevel_list, &view->link);
wwarning("wl_backend: click focus id=0x%lx", view->id);
}
}
}
/* Phase A: enqueue native WMEvent for WINGs */
deliver:;
WMScreen *wmscr = wl_get_wmscreen();
if (wmscr) {
WMEvent wev;
@ -5382,13 +5419,24 @@ wl_stacking_raise(WNativeWindow win)
if (v) {
wl_list_remove(&v->link);
wl_list_insert(&wl_state.toplevel_list, &v->link);
if (v->frame_id) {
struct wl_frame_buf *fb = frame_buf_find(v->frame_id);
if (fb && fb->tree) {
wlr_scene_node_raise_to_top(&fb->tree->node);
return;
}
}
if (v->scene_node)
wlr_scene_node_raise_to_top(&v->scene_node->node);
return;
}
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb && fb->scene_buf) {
wlr_scene_node_raise_to_top(&fb->scene_buf->node);
if (fb) {
if (fb->tree) {
wlr_scene_node_raise_to_top(&fb->tree->node);
} else if (fb->scene_buf) {
wlr_scene_node_raise_to_top(&fb->scene_buf->node);
}
return;
}
}
@ -5414,13 +5462,46 @@ wl_stacking_lower(WNativeWindow win)
static void
wl_stacking_restack(WNativeWindow *windows, int count)
{
/* Phase 58: reorder toplevel_list to match the given Z-order array. */
/* Reorder scene graph to match the WM's stacking order.
* Array is top-to-bottom. Process bottom-to-top so raise_to_top
* puts the first element on top last. */
int i;
for (i = count - 1; i >= 0; i--) {
struct wl_toplevel_view *v = wl_find_view_by_id(windows[i]);
if (!v) continue;
wl_list_remove(&v->link);
wl_list_insert(&wl_state.toplevel_list, &v->link);
WNativeWindow win = windows[i];
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb && fb->scene_buf) {
wlr_scene_node_raise_to_top(&fb->scene_buf->node);
/* Raise client surface inside this frame */
struct wl_toplevel_view *cv;
wl_list_for_each(cv, &wl_state.toplevel_list, link) {
if (cv->frame_id == win && cv->scene_node) {
wlr_scene_node_raise_to_top(&cv->scene_node->node);
break;
}
}
/* Also try via WM context */
WObjDescriptor *desc = wm_backend->context_find(win, WM_CTX_CLIENT_WIN);
if (desc && desc->parent_type == WCLASS_WINDOW) {
WWindow *wwin = (WWindow *)desc->parent;
struct wl_toplevel_view *v = wl_find_view_by_id(wwin->client_win);
if (v && v->scene_node)
wlr_scene_node_raise_to_top(&v->scene_node->node);
}
/* Raise children (titlebar, resizebar) on top */
struct wl_frame_buf *child;
wl_list_for_each(child, &wl_state.frame_list, link) {
if (child->parent_id == win && child->scene_buf)
wlr_scene_node_raise_to_top(&child->scene_buf->node);
}
continue;
}
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v) {
wl_list_remove(&v->link);
wl_list_insert(&wl_state.toplevel_list, &v->link);
if (v->scene_node)
wlr_scene_node_raise_to_top(&v->scene_node->node);
}
}
}
@ -5673,20 +5754,20 @@ wl_window_map(WNativeWindow win)
return; /* parent not visible yet */
}
wlr_scene_node_set_enabled(&fb->scene_buf->node, true);
wlr_scene_node_raise_to_top(&fb->scene_buf->node);
/* Raise the tree (raises entire window assembly together) */
if (fb->tree)
wlr_scene_node_raise_to_top(&fb->tree->node);
else if (!fb->parent_id)
wlr_scene_node_raise_to_top(&fb->scene_buf->node);
/* Also enable children (and grandchildren) */
struct wl_frame_buf *child;
wl_list_for_each(child, &wl_state.frame_list, link) {
if (child->parent_id == win && child->scene_buf) {
wlr_scene_node_set_enabled(&child->scene_buf->node, true);
wlr_scene_node_raise_to_top(&child->scene_buf->node);
/* Enable grandchildren */
struct wl_frame_buf *gc;
wl_list_for_each(gc, &wl_state.frame_list, link) {
if (gc->parent_id == child->id && gc->scene_buf) {
if (gc->parent_id == child->id && gc->scene_buf)
wlr_scene_node_set_enabled(&gc->scene_buf->node, true);
wlr_scene_node_raise_to_top(&gc->scene_buf->node);
}
}
}
}
@ -5700,13 +5781,23 @@ wl_window_map_raised(WNativeWindow win)
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v && v->scene_node) {
wlr_scene_node_set_enabled(&v->scene_node->node, true);
if (v->frame_id) {
struct wl_frame_buf *fb = frame_buf_find(v->frame_id);
if (fb && fb->tree) {
wlr_scene_node_raise_to_top(&fb->tree->node);
return;
}
}
wlr_scene_node_raise_to_top(&v->scene_node->node);
return;
}
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb && fb->scene_buf) {
wlr_scene_node_set_enabled(&fb->scene_buf->node, true);
wlr_scene_node_raise_to_top(&fb->scene_buf->node);
if (fb->tree)
wlr_scene_node_raise_to_top(&fb->tree->node);
else
wlr_scene_node_raise_to_top(&fb->scene_buf->node);
return;
}
}
@ -6014,8 +6105,12 @@ wl_client_reparent(WNativeWindow client, WNativeWindow new_parent, int x, int y)
if (parent_fb) {
v->x = parent_fb->x + x;
v->y = parent_fb->y + y;
if (v->scene_node)
if (v->scene_node) {
/* Move client's scene tree under the frame's tree */
if (parent_fb->tree)
wlr_scene_node_reparent(&v->scene_node->node, parent_fb->tree);
wlr_scene_node_set_position(&v->scene_node->node, v->x, v->y);
}
}
return;
}

View File

@ -206,6 +206,8 @@ void wRaiseFrame(WCoreWindow * frame)
/* already on top */
if (frame->stacking->above == NULL) {
/* Still notify backend to sync scene graph */
wm_backend->stacking_raise(frame->window);
return;
}