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.
This commit is contained in:
Window Maker 2026-05-15 09:14:18 +02:00
parent 245d0077b5
commit a961c86d4b
1 changed files with 52 additions and 27 deletions

View File

@ -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;