From a961c86d4b47a19a60bfe843f86a8f4ca1658990 Mon Sep 17 00:00:00 2001 From: Window Maker Date: Fri, 15 May 2026 09:14:18 +0200 Subject: [PATCH] wayland: fix click-through and focus-toggle on overlapping windows view_surface_at iterated toplevel_list in reverse using geometry alone to find the view under the cursor. toplevel_list order is maintained separately from scene Z-order: handle_pointer_button updated it on click but stacking operations (wl_stacking_restack, wl_stacking_raise/lower) did not. After any stacking operation the list order and visual order diverged, so successive clicks at the same position returned different views, causing focus to toggle between overlapping windows. Fix: use wlr_scene_node_at as the authority on what is visually topmost, then walk up to the fb->tree / window-root level (stopping when the parent is a level_trees[] bucket), and scan toplevel_list only for the ID-to-surface mapping of the matched node. This is the same approach already used by frame_buf_at after the level-tree rewrite. The scene graph is the single source of truth for Z-order. --- src/backend/wayland/wl_backend.c | 79 +++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 85971f0..fc3f031 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -5131,37 +5131,62 @@ static struct wlr_surface * view_surface_at(int px, int py, double *sx_out, double *sy_out, struct wl_toplevel_view **view_out) { - struct wl_toplevel_view *v; - wl_list_for_each_reverse(v, &wl_state.toplevel_list, link) { - double sx = px - (double)v->x; - double sy = py - (double)v->y; + /* Use wlr_scene_node_at as the authority on what is visually + * topmost at (px,py). The old geometry-scan loop over + * toplevel_list caused toggling focus on overlapping windows + * because toplevel_list order diverged from scene Z-order. */ + if (!wl_state.scene) return NULL; - /* Phase 17: XWayland window — simple bounds check on the surface. */ - if (v->xw_surface) { - if (!v->xw_surface->surface || !v->xw_surface->surface->mapped) - continue; - if (sx >= 0 && sy >= 0 && - sx < (double)v->xw_surface->width && - sy < (double)v->xw_surface->height) { - if (sx_out) *sx_out = sx; - if (sy_out) *sy_out = sy; - if (view_out) *view_out = v; - return v->xw_surface->surface; - } - continue; + double _sx, _sy; + struct wlr_scene_node *node = wlr_scene_node_at( + &wl_state.scene->tree.node, (double)px, (double)py, &_sx, &_sy); + if (!node) return NULL; + + /* Walk up until parent is a level_tree bucket: that node is + * fb->tree (framed) or v->scene_node (unframed). */ + struct wlr_scene_node *n = node; + while (n->parent && &n->parent->node != &wl_state.scene->tree.node) { + int _bucket = 0, _li; + for (_li = 0; _li < WL_NUM_LEVEL_BUCKETS; _li++) { + if (wl_state.level_trees[_li] && n->parent == wl_state.level_trees[_li]) + { _bucket = 1; break; } } + if (_bucket) break; + n = &n->parent->node; + } - /* XDG toplevel path. */ - if (!v->xdg_surface || !v->xdg_surface->surface || !v->xdg_surface->surface->mapped) - continue; - double sub_sx, sub_sy; - struct wlr_surface *sub = wlr_xdg_surface_surface_at( - v->xdg_surface, sx, sy, &sub_sx, &sub_sy); - if (sub) { - if (sx_out) *sx_out = sub_sx; - if (sy_out) *sy_out = sub_sy; + struct wl_toplevel_view *v; + wl_list_for_each(v, &wl_state.toplevel_list, link) { + /* Framed window: resolve via the fb->tree node. */ + int match = 0; + if (v->frame_id) { + struct wl_frame_buf *_fb = frame_buf_find(v->frame_id); + match = (_fb && _fb->tree && &_fb->tree->node == n); + } else { + match = (v->scene_node && &v->scene_node->node == n); + } + if (!match) continue; + + if (v->xw_surface && v->xw_surface->surface) { + double sx = px - (double)v->x; + double sy = py - (double)v->y; + if (sx_out) *sx_out = sx; + if (sy_out) *sy_out = sy; if (view_out) *view_out = v; - return sub; + return v->xw_surface->surface; + } + if (v->xdg_surface) { + double sx = px - (double)v->x; + double sy = py - (double)v->y; + double sub_sx, sub_sy; + struct wlr_surface *sub = wlr_xdg_surface_surface_at( + v->xdg_surface, sx, sy, &sub_sx, &sub_sy); + if (sub) { + if (sx_out) *sx_out = sub_sx; + if (sy_out) *sy_out = sub_sy; + if (view_out) *view_out = v; + return sub; + } } } return NULL;