Three interlocking problems were present in the Wayland stacking
implementation.
1. Wrong scene-graph nodes targeted by stacking operations
Each managed window groups its frame pixels and client surface under a
single wlr_scene_tree (fb->tree):
fb->tree
fb->scene_buf (frame decoration pixels)
child_frame_bufs (titlebar / resizebar sub-frames)
v->scene_node (client surface, reparented in)
wl_stacking_restack was raising fb->scene_buf -- a child inside fb->tree
-- instead of fb->tree itself, a no-op for inter-window ordering. Three
redundant loops then tried to separately raise the client surface and child
frames, also to no effect. wl_stacking_lower had the same bug.
Fix: raise/lower fb->tree in both functions so the entire window assembly
moves as one atomic unit.
2. Flat scene graph -- no structural level ordering
All nodes were direct children of scene->tree, so creation order and
raise_to_top calls determined Z-order. A normal window could accidentally
render above a floating window after any CommitStacking call.
Fix: introduce 9 wlr_scene_tree level buckets created at scene init time
in bottom-to-top order:
0 WL_LAYER_BACKGROUND background rect / wallpaper
1 WL_LAYER_DESKTOP WMDesktopLevel
2 WL_LAYER_SUNKEN WMSunkenLevel
3 WL_LAYER_NORMAL WMNormalLevel (default for new frames)
4 WL_LAYER_FLOATING WMFloatingLevel / WINGs dialogs
5 WL_LAYER_MENU WMMainMenuWindowLevel / OR-redirect windows
6 WL_LAYER_DOCK WMStatusWindowLevel / dock panel surface
7 WL_LAYER_FULLSCREEN WMFullscreenLevel
8 WL_LAYER_OVERLAY WMModalPanel / WMPopUp / XOR overlay
Every scene node is assigned to the correct bucket at creation.
A new stacking_set_level vtable slot called from AddToStackList moves
fb->tree to the right bucket whenever a window is added or its level
changes via ChangeStackingLevel. X11 backend implements it as a no-op.
3. XDG client surface not reparented into fb->tree
For XDG toplevels the map handler called wManageWindow before creating
view->scene_node, so the reparent inside wl_client_reparent was a no-op.
The client surface then lived as a flat sibling of fb->tree in
WL_LAYER_NORMAL, never moving with its frame during stacking operations.
Fix: after creating view->scene_node, check if frame_id is already set
and do the deferred reparent immediately.
4. Hit-testing broken by level-tree hierarchy
frame_buf_at walked up from the hit scene node to the first direct child
of scene->tree. With level buckets that stopped at the level_tree node
instead of fb->tree, making every window in the same bucket appear to be
the same window. Client surfaces were not blocking clicks on frames
beneath them, and titlebar sub-frames were not distinguished from the
top-level frame, breaking move and resize initiation.
Fix: rewrite frame_buf_at with a two-pass approach:
Pass 1 -- exact match: check if the topmost node is a child frame_buf
scene_buf such as titlebar, resizebar, or button; return it directly.
Pass 2 -- walk up stopping when parent is a level_tree bucket, then
match that window-root node to a top-level frame_buf tree.
Return NULL if the hit was on a client surface.
ROADMAP: mark A1 done.