wayland: implement correct stacking model with scene-graph level trees
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.
This commit is contained in:
parent
0e6f2f60e8
commit
245d0077b5
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(grep -n 'wFrameWindowCreate' /home/lkn/src/WindowMaker-0.96.0/src/*.c)",
|
||||
"Bash(typeb /home/lkn/src/WindowMaker-0.96.0/src/window.c /home/lkn/src/WindowMaker-0.96.0/src/winspector.c)",
|
||||
"Bash(grep -n \"wm_hints->input\\\\|wm_hints->icon_x\\\\|wm_hints->icon_y\" /home/lkn/src/WindowMaker-0.96.0/src/*.c)",
|
||||
"Bash(grep -n \"^\\\\s*return\\\\s*0;\\\\s*$\\\\|^\\\\s*return\\\\s*NULL;\\\\s*$\\\\|^\\\\s*return\\\\s*1;\\\\s*$\\\\|^\\\\s*return;\\\\s*$\" /home/lkn/src/WindowMaker-0.96.0/src/backend/wayland/wl_backend.c)",
|
||||
"Bash(grep *)",
|
||||
"Bash(make -C src/backend/wayland wl_backend.lo)",
|
||||
"Bash(make -C /home/lkn/src/WindowMaker-0.96.0/src)",
|
||||
"Bash(pkg-config --exists \"x11\")",
|
||||
"Bash(pkg-config --modversion \"x11\")",
|
||||
"Bash(pkg-config --modversion wlroots-0.19 wlroots-0.20)",
|
||||
"Bash(pkg-config --variable=pcfiledir wlroots-0.19)",
|
||||
"Read(//usr/local/**)",
|
||||
"Read(//usr/**)",
|
||||
"Bash(pkg-config --exists libgif)",
|
||||
"Bash(pkg-config --modversion libgif)",
|
||||
"Bash(make -C /home/lkn/src/WindowMaker-0.96.0/wrlib)",
|
||||
"Bash(pkg-config --libs wlroots)",
|
||||
"Bash(pkg-config --libs wlroots-0.19)",
|
||||
"Bash(pkg-config --modversion wlroots-0.19)",
|
||||
"Bash(sed -n '560,570p' /usr/include/wlroots-0.19/wlr/types/wlr_compositor.h)",
|
||||
"Bash(sed -n '344,350p' /usr/include/wlroots-0.19/wlr/types/wlr_xdg_shell.h)",
|
||||
"Bash(sed -n '122,128p' /usr/include/wlroots-0.19/wlr/types/wlr_layer_shell_v1.h)",
|
||||
"Bash(sed -n '59,65p' /usr/include/wlroots-0.19/wlr/types/wlr_presentation_time.h)",
|
||||
"Bash(sed -n '76,92p' /usr/include/wlroots-0.19/wlr/types/wlr_linux_dmabuf_v1.h)",
|
||||
"Bash(tar -tzf /var/cache/distfiles/wlroots-0.19.2.tar.gz)",
|
||||
"Bash(tar -xOzf /var/cache/distfiles/wlroots-0.19.2.tar.gz \"wlroots-0.19.2/render/allocator/allocator.c\")",
|
||||
"Bash(tar -xOzf /var/cache/distfiles/wlroots-0.19.2.tar.gz \"wlroots-0.19.2/types/output/output.c\")",
|
||||
"Bash(nm -D /usr/lib64/wlroots-0.19/libwlroots-0.19.so)",
|
||||
"Bash(tar xJf /var/cache/distfiles/libX11-1.8.13.tar.xz -C /tmp libX11-1.8.13/src/XlibInt.c)",
|
||||
"Read(//tmp/libX11-1.8.13/src/**)",
|
||||
"Bash(tar xJf /var/cache/distfiles/libX11-1.8.13.tar.xz -C /tmp libX11-1.8.13/src/ErrHndlr.c)",
|
||||
"Bash(nm -D /usr/lib64/libX11.so.6)",
|
||||
"Bash(objdump -d /usr/lib64/libX11.so.6)",
|
||||
"Bash(readelf -d /usr/lib64/libX11.so.6)",
|
||||
"Bash(readelf -s /usr/lib64/libX11.so.6)",
|
||||
"Bash(xargs -I{} basename {})",
|
||||
"Bash(pkg-config --exists pangoft2)",
|
||||
"Bash(pkg-config --cflags --libs pangoft2)",
|
||||
"Bash(pkg-config --exists pixman-1)",
|
||||
"Bash(awk 'NR>=1700 && NR<=1900 && /^[a-zA-Z]/ {print NR\": \"$0}' /home/lkn/src/WindowMaker-0.96.0/src/menu.c)",
|
||||
"Bash(awk -F: '$2>1400 && $2<1420 || $2>1730 && $2<1745 || $2>2075 && $2<2090 || $2>2328 && $2<2340 || $2>2408 && $2<2420 {print}')",
|
||||
"Bash(awk *)",
|
||||
"Bash(sed -n '2325,2345p' /home/lkn/src/WindowMaker-0.96.0/src/moveres.c)",
|
||||
"Bash(sed -n '2408,2420p' /home/lkn/src/WindowMaker-0.96.0/src/moveres.c)",
|
||||
"Bash(sed -n '930,934p' /home/lkn/src/WindowMaker-0.96.0/src/menu.c)",
|
||||
"Bash(sed -n '1631,1635p' /home/lkn/src/WindowMaker-0.96.0/src/menu.c)",
|
||||
"Bash(sed -n '1875,1879p' /home/lkn/src/WindowMaker-0.96.0/src/menu.c)",
|
||||
"Bash(sed -n '2233,2237p' /home/lkn/src/WindowMaker-0.96.0/src/menu.c)",
|
||||
"Bash(sed -n '1408,1413p' /home/lkn/src/WindowMaker-0.96.0/src/moveres.c)",
|
||||
"Bash(sed -n '1735,1742p' /home/lkn/src/WindowMaker-0.96.0/src/moveres.c)",
|
||||
"Bash(sed -n '2081,2086p' /home/lkn/src/WindowMaker-0.96.0/src/moveres.c)",
|
||||
"Bash(sed -n '2332,2336p' /home/lkn/src/WindowMaker-0.96.0/src/moveres.c)",
|
||||
"Bash(sed -n '2413,2417p' /home/lkn/src/WindowMaker-0.96.0/src/moveres.c)",
|
||||
"Bash(make -C WINGs)",
|
||||
"Bash(make -C src)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ Priority: HIGH — needed for daily-driver use.
|
|||
|
||||
| # | Task | Key Files | Complexity |
|
||||
|---|------|-----------|------------|
|
||||
| A1 | Stacking (raise/lower via scene graph) | stacking.c, wlr_scene_node_raise/lower | Low |
|
||||
| A1 | ~~Stacking (raise/lower via scene graph)~~ | stacking.c, wlr_scene_node_raise/lower | ✅ Done |
|
||||
| A2 | ~~Window maximize~~ | actions.c, usable area calc | ✅ Done |
|
||||
| A3 | ~~Window shade (titlebar collapse)~~ | actions.c, frame buffer resize | ✅ Done |
|
||||
| A4 | Workspace switching (create/destroy/switch) | workspace.c, scene tree reparenting | Medium |
|
||||
|
|
|
|||
|
|
@ -369,6 +369,16 @@ typedef struct WMBackend {
|
|||
int (*stacking_query)(WNativeWindow root,
|
||||
WNativeWindow **out, unsigned int *count);
|
||||
|
||||
/*
|
||||
* Notify the backend that a frame's WM stacking level has changed.
|
||||
* The backend may use this to place the frame in the correct scene-graph
|
||||
* layer so that level ordering is structurally enforced without requiring
|
||||
* a full CommitStacking reorder.
|
||||
* level is one of the WM*Level constants from WindowMaker.h.
|
||||
* No-op on backends that do not have per-level scene trees (e.g. X11).
|
||||
*/
|
||||
void (*stacking_set_level)(WNativeWindow win, int level);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Event queue operations (Phase 3) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
|
|||
|
|
@ -491,6 +491,39 @@ struct wl_compositor_state {
|
|||
int icon_w, icon_h;
|
||||
} dock_apps[WM_DOCK_MAX_APPS];
|
||||
int dock_app_count;
|
||||
/* Stacking level trees — one wlr_scene_tree per layer, ordered
|
||||
* bottom-to-top (index 0 rendered first = furthest back).
|
||||
*
|
||||
* Every scene node that participates in the compositor output is
|
||||
* assigned to exactly one bucket so that creation order within the
|
||||
* root tree never matters and there are no Z-order surprises.
|
||||
*
|
||||
* Named constants (WL_LAYER_*) map to bucket indices:
|
||||
*
|
||||
* 0 WL_LAYER_BACKGROUND background rect / wallpaper
|
||||
* 1 WL_LAYER_DESKTOP WMDesktopLevel (level < -1)
|
||||
* 2 WL_LAYER_SUNKEN WMSunkenLevel (level == -1)
|
||||
* 3 WL_LAYER_NORMAL WMNormalLevel (0 <= level < 3)
|
||||
* 4 WL_LAYER_FLOATING WMFloatingLevel (3 <= level < 20)
|
||||
* 5 WL_LAYER_MENU WMMainMenuWindowLevel (20), OR-redirect
|
||||
* 6 WL_LAYER_DOCK WMStatusWindowLevel / WMDockWindowLevel (21-49)
|
||||
* also the dock panel wm_shm_surface
|
||||
* 7 WL_LAYER_FULLSCREEN WMFullscreenLevel (50-99)
|
||||
* 8 WL_LAYER_OVERLAY WMModalPanel / WMPopUp (>=100)
|
||||
* also the XOR animation overlay surface
|
||||
*/
|
||||
#define WL_LAYER_BACKGROUND 0
|
||||
#define WL_LAYER_DESKTOP 1
|
||||
#define WL_LAYER_SUNKEN 2
|
||||
#define WL_LAYER_NORMAL 3
|
||||
#define WL_LAYER_FLOATING 4
|
||||
#define WL_LAYER_MENU 5
|
||||
#define WL_LAYER_DOCK 6
|
||||
#define WL_LAYER_FULLSCREEN 7
|
||||
#define WL_LAYER_OVERLAY 8
|
||||
#define WL_NUM_LEVEL_BUCKETS 9
|
||||
struct wlr_scene_tree *level_trees[WL_NUM_LEVEL_BUCKETS];
|
||||
|
||||
/* Phase 81: full-screen overlay surface for XOR animation drawing */
|
||||
struct wm_shm_surface *overlay_surf; /* NULL until first draw */
|
||||
/* Focus sink: small off-screen InputOnly X window that receives focus
|
||||
|
|
@ -513,6 +546,11 @@ struct wl_compositor_state {
|
|||
|
||||
static struct wl_compositor_state wl_state;
|
||||
|
||||
/* Forward declarations for helpers used before their definition. */
|
||||
static int level_to_bucket(int level);
|
||||
static struct wlr_scene_tree *level_tree_for(int level);
|
||||
static struct wl_frame_buf *frame_buf_find(WNativeWindow id);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Phase A: WINGs event backend wiring helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
@ -993,14 +1031,36 @@ handle_toplevel_map(struct wl_listener *listener, void *data)
|
|||
* (auto_focus=1 set in wl_screen_create_internals); the
|
||||
* direct wl_focus_set() call is no longer needed here. */
|
||||
|
||||
/* Phase 18: add XDG surface to the scene graph. */
|
||||
/* Phase 18: add XDG surface to the scene graph.
|
||||
*
|
||||
* ORDERING: wManageWindow (above) calls wl_client_reparent
|
||||
* which sets view->frame_id but cannot reparent the scene
|
||||
* node because it does not exist yet. We create it here
|
||||
* and immediately do the deferred reparent if frame_id is
|
||||
* already set, so the client surface ends up inside
|
||||
* fb->tree and moves with it during stacking operations.
|
||||
* Without this the client surface would sit as a flat
|
||||
* sibling of fb->tree in WL_LAYER_NORMAL and CommitStacking
|
||||
* raises would leave it behind the frame decorations of
|
||||
* subsequently-opened windows. */
|
||||
if (wl_state.scene && !view->scene_node) {
|
||||
struct wlr_scene_tree *_vp = wl_state.level_trees[WL_LAYER_NORMAL]
|
||||
? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree;
|
||||
view->scene_node = wlr_scene_subsurface_tree_create(
|
||||
&wl_state.scene->tree, view->xdg_surface->surface);
|
||||
if (view->scene_node)
|
||||
_vp, view->xdg_surface->surface);
|
||||
if (view->scene_node) {
|
||||
/* Deferred reparent: move client surface under its
|
||||
* frame grouping tree now that the node exists. */
|
||||
if (view->frame_id) {
|
||||
struct wl_frame_buf *_pfb = frame_buf_find(view->frame_id);
|
||||
if (_pfb && _pfb->tree)
|
||||
wlr_scene_node_reparent(&view->scene_node->node,
|
||||
_pfb->tree);
|
||||
}
|
||||
wlr_scene_node_set_position(&view->scene_node->node,
|
||||
view->x, view->y);
|
||||
}
|
||||
}
|
||||
|
||||
/* Phase 13: warp virtual pointer to the centre of the
|
||||
* newly managed window so pointer focus is immediately
|
||||
|
|
@ -1176,8 +1236,9 @@ handle_or_map(struct wl_listener *listener, void *data)
|
|||
return;
|
||||
|
||||
if (wl_state.scene && !ov->scene_node && xw->surface) {
|
||||
ov->scene_node = wlr_scene_subsurface_tree_create(
|
||||
&wl_state.scene->tree, xw->surface);
|
||||
struct wlr_scene_tree *_op = wl_state.level_trees[WL_LAYER_MENU]
|
||||
? wl_state.level_trees[WL_LAYER_MENU] : &wl_state.scene->tree;
|
||||
ov->scene_node = wlr_scene_subsurface_tree_create(_op, xw->surface);
|
||||
if (ov->scene_node)
|
||||
wlr_scene_node_set_position(&ov->scene_node->node, xw->x, xw->y);
|
||||
}
|
||||
|
|
@ -1259,8 +1320,9 @@ handle_xwayland_map(struct wl_listener *listener, void *data)
|
|||
* rather than xw->x,y which may not yet reflect the configure we
|
||||
* sent; the set_geometry listener keeps them in sync thereafter. */
|
||||
if (wl_state.scene && !view->scene_node && xw->surface) {
|
||||
view->scene_node = wlr_scene_subsurface_tree_create(
|
||||
&wl_state.scene->tree, xw->surface);
|
||||
struct wlr_scene_tree *_vp = wl_state.level_trees[WL_LAYER_NORMAL]
|
||||
? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree;
|
||||
view->scene_node = wlr_scene_subsurface_tree_create(_vp, xw->surface);
|
||||
if (view->scene_node)
|
||||
wlr_scene_node_set_position(&view->scene_node->node, view->x, view->y);
|
||||
} else if (!xw->surface) {
|
||||
|
|
@ -1460,8 +1522,9 @@ handle_xwayland_new_surface(struct wl_listener *listener, void *data)
|
|||
/* If already associated (surface exists), add to scene now. */
|
||||
if (xw->surface) {
|
||||
if (wl_state.scene) {
|
||||
ov->scene_node = wlr_scene_subsurface_tree_create(
|
||||
&wl_state.scene->tree, xw->surface);
|
||||
struct wlr_scene_tree *_op = wl_state.level_trees[WL_LAYER_MENU]
|
||||
? wl_state.level_trees[WL_LAYER_MENU] : &wl_state.scene->tree;
|
||||
ov->scene_node = wlr_scene_subsurface_tree_create(_op, xw->surface);
|
||||
if (ov->scene_node)
|
||||
wlr_scene_node_set_position(&ov->scene_node->node,
|
||||
xw->x, xw->y);
|
||||
|
|
@ -1964,7 +2027,7 @@ handle_backend_new_output(struct wl_listener *listener, void *data)
|
|||
* surfaces. Default WindowMaker desktop colour: rgb:50/50/75. */
|
||||
{
|
||||
float bg[4] = { 0x50/255.0f, 0x50/255.0f, 0x75/255.0f, 1.0f };
|
||||
wlr_scene_rect_create(&wl_state.scene->tree,
|
||||
wlr_scene_rect_create(wl_state.level_trees[WL_LAYER_BACKGROUND],
|
||||
(int)output->width, (int)output->height, bg);
|
||||
wwarning("wl_backend: root background created (%dx%d)",
|
||||
(int)output->width, (int)output->height);
|
||||
|
|
@ -2221,9 +2284,17 @@ wl_display_open(const char *name)
|
|||
extern void wl_view_commit_backing_impl(unsigned long id);
|
||||
W_ViewCommitBackingFn = wl_view_commit_backing_impl;
|
||||
}
|
||||
if (wl_state.scene)
|
||||
if (wl_state.scene) {
|
||||
wwarning("wl_backend: scene graph created");
|
||||
else
|
||||
/* Create the 9 level-bucket trees in bottom-to-top order
|
||||
* (WL_LAYER_BACKGROUND first, WL_LAYER_OVERLAY last).
|
||||
* Every subsequent scene node is parented to one of these
|
||||
* trees, never directly to scene->tree, so Z-ordering is
|
||||
* determined entirely by bucket index + intra-bucket order. */
|
||||
int _li;
|
||||
for (_li = 0; _li < WL_NUM_LEVEL_BUCKETS; _li++)
|
||||
wl_state.level_trees[_li] = wlr_scene_tree_create(&wl_state.scene->tree);
|
||||
} else
|
||||
wwarning("wl_backend: wlr_scene_create failed (no compositing)");
|
||||
|
||||
/* Phase 20: wire new_input BEFORE starting the backend so that real
|
||||
|
|
@ -3067,8 +3138,9 @@ wl_event_loop_run(void)
|
|||
struct wl_or_view *ov;
|
||||
wl_list_for_each(ov, &wl_state.or_list, link) {
|
||||
if (!ov->scene_node && ov->xw_surface->surface && wl_state.scene) {
|
||||
ov->scene_node = wlr_scene_subsurface_tree_create(
|
||||
&wl_state.scene->tree, ov->xw_surface->surface);
|
||||
struct wlr_scene_tree *_op = wl_state.level_trees[WL_LAYER_MENU]
|
||||
? wl_state.level_trees[WL_LAYER_MENU] : &wl_state.scene->tree;
|
||||
ov->scene_node = wlr_scene_subsurface_tree_create(_op, ov->xw_surface->surface);
|
||||
if (ov->scene_node)
|
||||
wlr_scene_node_set_position(&ov->scene_node->node,
|
||||
ov->xw_surface->x, ov->xw_surface->y);
|
||||
|
|
@ -3456,7 +3528,14 @@ wm_dock_create(WScreen *scr)
|
|||
}
|
||||
|
||||
wm_dock_render(s, icon_sz, n_slots);
|
||||
wm_shm_surface_commit(s, &wl_state.scene->tree, sw - dw, 0);
|
||||
/* Dock panel belongs in WL_LAYER_DOCK so it renders at the same
|
||||
* depth as the dock icon frame_bufs (which wl_stacking_set_level
|
||||
* places there for WMStatusWindowLevel=21). */
|
||||
{
|
||||
struct wlr_scene_tree *_dp = wl_state.level_trees[WL_LAYER_DOCK]
|
||||
? wl_state.level_trees[WL_LAYER_DOCK] : &wl_state.scene->tree;
|
||||
wm_shm_surface_commit(s, _dp, sw - dw, 0);
|
||||
}
|
||||
|
||||
wl_state.dock_surface = s;
|
||||
wl_state.dock_width = dw;
|
||||
|
|
@ -3761,55 +3840,74 @@ frame_buf_find(WNativeWindow id)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* Hit-test: find the topmost frame_buf containing (px, py). */
|
||||
/* Hit-test: find the frame_buf under (px, py) that should receive the event.
|
||||
*
|
||||
* Returns the most-specific (innermost) frame_buf whose rendered pixels
|
||||
* are topmost at (px, py), so the WM event layer receives the correct
|
||||
* sub-window ID (titlebar, resizebar, button) needed to trigger move/resize.
|
||||
*
|
||||
* Scene-graph structure (level-tree model):
|
||||
*
|
||||
* scene->tree
|
||||
* level_trees[N] <- level bucket
|
||||
* fb->tree <- top-level frame grouping tree
|
||||
* fb->scene_buf <- full-frame decoration pixels
|
||||
* child_fb->scene_buf <- titlebar / resizebar sub-frames
|
||||
* v->scene_node <- client surface (returns NULL)
|
||||
*
|
||||
* Strategy:
|
||||
* 1. wlr_scene_node_at gives the topmost rendered node.
|
||||
* 2. First check if that exact node is a child frame_buf's scene_buf
|
||||
* (titlebar, resizebar, button) — if so, return it directly.
|
||||
* 3. Walk up to the fb->tree level (parent is a level_tree bucket).
|
||||
* 4. Match that window-root node to a top-level frame_buf's tree.
|
||||
* 5. If it was a client surface (v->scene_node), return NULL. */
|
||||
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) {
|
||||
if (!fb->scene_buf || !fb->scene_buf->node.enabled)
|
||||
continue;
|
||||
if (px >= fb->x && px < fb->x + fb->width &&
|
||||
py >= fb->y && py < fb->y + fb->height) {
|
||||
/* Prefer the smallest (most specific) frame_buf */
|
||||
if (!hit || (fb->width * fb->height < hit->width * hit->height))
|
||||
hit = fb;
|
||||
}
|
||||
}
|
||||
if (!hit) return NULL;
|
||||
if (!wl_state.scene) 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 */
|
||||
&wl_state.scene->tree.node, (double)px, (double)py, &sx, &sy);
|
||||
if (!node) return NULL;
|
||||
|
||||
struct wl_frame_buf *fb;
|
||||
|
||||
/* Step 2: exact match — is the topmost node a child frame_buf's
|
||||
* scene_buf (no tree wrapper)? This covers titlebars, resizebars,
|
||||
* and button sub-frames, which are what the WM needs for move/resize. */
|
||||
wl_list_for_each(fb, &wl_state.frame_list, link) {
|
||||
if (!fb->tree && fb->scene_buf && &fb->scene_buf->node == node)
|
||||
return fb;
|
||||
}
|
||||
|
||||
/* Step 3: walk up until the parent is a level_tree bucket.
|
||||
* The node we stop at is the fb->tree of a top-level frame, or
|
||||
* a v->scene_node sitting directly in a level tree (unframed). */
|
||||
struct wlr_scene_node *n = node;
|
||||
while (n->parent && &n->parent->node != &wl_state.scene->tree.node)
|
||||
while (n->parent
|
||||
&& &n->parent->node != &wl_state.scene->tree.node) {
|
||||
int _at_level = 0, _li;
|
||||
for (_li = 0; _li < WL_NUM_LEVEL_BUCKETS; _li++) {
|
||||
if (wl_state.level_trees[_li] &&
|
||||
n->parent == wl_state.level_trees[_li]) {
|
||||
_at_level = 1; break;
|
||||
}
|
||||
}
|
||||
if (_at_level) break;
|
||||
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 */
|
||||
|
||||
/* Step 4: match window-root node to a top-level frame_buf. */
|
||||
wl_list_for_each(fb, &wl_state.frame_list, link) {
|
||||
if (fb->tree && &fb->tree->node == n)
|
||||
return fb;
|
||||
}
|
||||
|
||||
/* Step 5: topmost node was a client surface — not a WM frame. */
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Frame / window lifecycle */
|
||||
|
|
@ -3847,9 +3945,13 @@ wl_frame_create_toplevel(WScreen *scr,
|
|||
fb->width = width;
|
||||
fb->height = height;
|
||||
|
||||
/* Create scene buffer node under a per-window tree. */
|
||||
/* Create scene buffer node under a per-window tree.
|
||||
* Default to WL_LAYER_NORMAL; wl_stacking_set_level() will reparent
|
||||
* to the correct bucket when AddToStackList() fires immediately after. */
|
||||
if (wl_state.scene) {
|
||||
fb->tree = wlr_scene_tree_create(&wl_state.scene->tree);
|
||||
struct wlr_scene_tree *_parent = wl_state.level_trees[WL_LAYER_NORMAL]
|
||||
? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree;
|
||||
fb->tree = wlr_scene_tree_create(_parent);
|
||||
if (fb->tree) {
|
||||
wlr_scene_node_set_position(&fb->tree->node, 0, 0);
|
||||
fb->scene_buf = wlr_scene_buffer_create(fb->tree, &fb->base);
|
||||
|
|
@ -4835,10 +4937,24 @@ handle_layer_shell_new_surface(struct wl_listener *listener, void *data)
|
|||
}
|
||||
lv->layer_surface = ls;
|
||||
|
||||
/* Add to scene graph if available. */
|
||||
/* Add to scene graph in the correct level bucket.
|
||||
* Layer-shell layer enum maps to WL_LAYER_* buckets:
|
||||
* BACKGROUND -> WL_LAYER_BACKGROUND
|
||||
* BOTTOM -> WL_LAYER_DESKTOP
|
||||
* TOP -> WL_LAYER_DOCK
|
||||
* OVERLAY -> WL_LAYER_OVERLAY */
|
||||
if (wl_state.scene) {
|
||||
lv->scene_node = wlr_scene_subsurface_tree_create(
|
||||
&wl_state.scene->tree, ls->surface);
|
||||
int _lb;
|
||||
switch (ls->pending.layer) {
|
||||
case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: _lb = WL_LAYER_BACKGROUND; break;
|
||||
case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: _lb = WL_LAYER_DESKTOP; break;
|
||||
case ZWLR_LAYER_SHELL_V1_LAYER_TOP: _lb = WL_LAYER_DOCK; break;
|
||||
case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: _lb = WL_LAYER_OVERLAY; break;
|
||||
default: _lb = WL_LAYER_NORMAL; break;
|
||||
}
|
||||
struct wlr_scene_tree *_lp = wl_state.level_trees[_lb]
|
||||
? wl_state.level_trees[_lb] : &wl_state.scene->tree;
|
||||
lv->scene_node = wlr_scene_subsurface_tree_create(_lp, ls->surface);
|
||||
}
|
||||
|
||||
/* Compute and send initial configure. */
|
||||
|
|
@ -5419,12 +5535,16 @@ overlay_flush(void)
|
|||
struct wm_shm_surface *s = wl_state.overlay_surf;
|
||||
if (!s || !wl_state.scene) return;
|
||||
/* First call creates the scene_buffer node; subsequent calls reposition. */
|
||||
wm_shm_surface_commit(s, &wl_state.scene->tree, 0, 0);
|
||||
/* Overlay surface lives in WL_LAYER_OVERLAY so it is structurally
|
||||
* above every window and dock node without needing raise_to_top. */
|
||||
{
|
||||
struct wlr_scene_tree *_op = wl_state.level_trees[WL_LAYER_OVERLAY]
|
||||
? wl_state.level_trees[WL_LAYER_OVERLAY] : &wl_state.scene->tree;
|
||||
wm_shm_surface_commit(s, _op, 0, 0);
|
||||
}
|
||||
if (s->scene_buf) {
|
||||
/* Notify wlroots that the pixel data has changed. */
|
||||
wlr_scene_buffer_set_buffer(s->scene_buf, &s->wlr_buf);
|
||||
/* Keep the overlay above all client windows. */
|
||||
wlr_scene_node_raise_to_top(&s->scene_buf->node);
|
||||
}
|
||||
/* Commit the scene immediately so the animation step is visible
|
||||
* before the caller's wusleep() delay. */
|
||||
|
|
@ -5443,6 +5563,50 @@ static void wl_server_ungrab(void) { overlay_flush(); }
|
|||
/* Stacking */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Map a WM window level integer to a level_trees[] bucket index.
|
||||
* Buckets are ordered 0=lowest (WL_LAYER_BACKGROUND) to 8=highest
|
||||
* (WL_LAYER_OVERLAY), matching the bottom-to-top creation order of
|
||||
* level_trees[]. Does not map WL_LAYER_BACKGROUND (no WM level). */
|
||||
static int
|
||||
level_to_bucket(int level)
|
||||
{
|
||||
if (level < -1) return WL_LAYER_DESKTOP;
|
||||
if (level < 0) return WL_LAYER_SUNKEN;
|
||||
if (level < 3) return WL_LAYER_NORMAL;
|
||||
if (level < 20) return WL_LAYER_FLOATING;
|
||||
if (level < 21) return WL_LAYER_MENU;
|
||||
if (level < 50) return WL_LAYER_DOCK;
|
||||
if (level < 100) return WL_LAYER_FULLSCREEN;
|
||||
return WL_LAYER_OVERLAY;
|
||||
}
|
||||
|
||||
/* Return the level_trees[] entry for a given WM level integer.
|
||||
* Falls back to the root scene tree only if the scene is not yet
|
||||
* initialised, which cannot happen during normal window management. */
|
||||
static struct wlr_scene_tree *
|
||||
level_tree_for(int level)
|
||||
{
|
||||
int b = level_to_bucket(level);
|
||||
if (wl_state.level_trees[b])
|
||||
return wl_state.level_trees[b];
|
||||
/* Scene not yet initialised — this window will be invisible. */
|
||||
return &wl_state.scene->tree;
|
||||
}
|
||||
|
||||
static void
|
||||
wl_stacking_set_level(WNativeWindow win, int level)
|
||||
{
|
||||
/* Move the frame's grouping tree into the correct level bucket so
|
||||
* that the scene graph structurally enforces level ordering. All
|
||||
* other nodes (background rect, dock panel, OR views, overlays)
|
||||
* are already placed in their fixed buckets at creation time. */
|
||||
struct wl_frame_buf *fb = frame_buf_find(win);
|
||||
if (!fb || !fb->tree) return;
|
||||
struct wlr_scene_tree *target = level_tree_for(level);
|
||||
if (fb->tree->node.parent != target)
|
||||
wlr_scene_node_reparent(&fb->tree->node, target);
|
||||
}
|
||||
|
||||
static void
|
||||
wl_stacking_raise(WNativeWindow win)
|
||||
{
|
||||
|
|
@ -5475,57 +5639,54 @@ wl_stacking_raise(WNativeWindow win)
|
|||
static void
|
||||
wl_stacking_lower(WNativeWindow win)
|
||||
{
|
||||
/* Frame-buf path: operate on the grouping tree, not the inner buffer
|
||||
* node. Lowering fb->tree moves the whole window (frame pixels +
|
||||
* reparented client surface) together, within its level bucket. */
|
||||
struct wl_frame_buf *fb = frame_buf_find(win);
|
||||
if (fb) {
|
||||
if (fb->tree)
|
||||
wlr_scene_node_lower_to_bottom(&fb->tree->node);
|
||||
else if (fb->scene_buf)
|
||||
wlr_scene_node_lower_to_bottom(&fb->scene_buf->node);
|
||||
return;
|
||||
}
|
||||
/* Unframed toplevel view (no frame_buf wrapper). */
|
||||
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.prev, &v->link);
|
||||
if (v->scene_node)
|
||||
wlr_scene_node_lower_to_bottom(&v->scene_node->node);
|
||||
return;
|
||||
}
|
||||
struct wl_frame_buf *fb = frame_buf_find(win);
|
||||
if (fb && fb->scene_buf) {
|
||||
wlr_scene_node_lower_to_bottom(&fb->scene_buf->node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wl_stacking_restack(WNativeWindow *windows, int count)
|
||||
{
|
||||
/* 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. */
|
||||
/* Reorder scene graph to match the WM stacking order.
|
||||
*
|
||||
* windows[] is ordered top-to-bottom (index 0 = topmost).
|
||||
* Process bottom-to-top: last raise_to_top call wins, so the
|
||||
* window at index 0 ends up on top within its level bucket.
|
||||
*
|
||||
* Each window is represented by its toplevel frame_buf ID. We
|
||||
* raise fb->tree — the grouping tree that owns both the frame
|
||||
* pixel buffer and the reparented client surface — so the entire
|
||||
* window moves as one atomic unit. Child frame_bufs (titlebar,
|
||||
* resizebar) are already children of fb->tree and move with it. */
|
||||
int i;
|
||||
for (i = count - 1; i >= 0; i--) {
|
||||
WNativeWindow win = windows[i];
|
||||
struct wl_frame_buf *fb = frame_buf_find(win);
|
||||
if (fb && fb->scene_buf) {
|
||||
if (fb) {
|
||||
/* Raise the grouping tree: moves frame + client together. */
|
||||
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);
|
||||
/* 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;
|
||||
}
|
||||
/* Fallback: unframed toplevel view (no frame_buf). */
|
||||
struct wl_toplevel_view *v = wl_find_view_by_id(win);
|
||||
if (v) {
|
||||
wl_list_remove(&v->link);
|
||||
|
|
@ -6227,8 +6388,11 @@ wl_client_reparent(WNativeWindow client, WNativeWindow new_parent, int x, int y)
|
|||
fb->height = h;
|
||||
|
||||
if (wl_state.scene) {
|
||||
fb->scene_buf = wlr_scene_buffer_create(&wl_state.scene->tree,
|
||||
&fb->base);
|
||||
/* WINGs dialog box: place in WL_LAYER_FLOATING so it renders
|
||||
* above normal windows but below menus and dock. */
|
||||
struct wlr_scene_tree *_wp = wl_state.level_trees[WL_LAYER_FLOATING]
|
||||
? wl_state.level_trees[WL_LAYER_FLOATING] : &wl_state.scene->tree;
|
||||
fb->scene_buf = wlr_scene_buffer_create(_wp, &fb->base);
|
||||
if (fb->scene_buf) {
|
||||
wlr_scene_node_set_position(&fb->scene_buf->node, fb->x, fb->y);
|
||||
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
|
||||
|
|
@ -6432,7 +6596,9 @@ static WNativeWindow wl_fake_leader_create(WScreen *scr)
|
|||
wlr_buffer_init(&fb->base, &frame_buf_impl, 1, 1);
|
||||
|
||||
if (wl_state.scene) {
|
||||
fb->scene_buf = wlr_scene_buffer_create(&wl_state.scene->tree, &fb->base);
|
||||
struct wlr_scene_tree *_pp = wl_state.level_trees[WL_LAYER_NORMAL]
|
||||
? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree;
|
||||
fb->scene_buf = wlr_scene_buffer_create(_pp, &fb->base);
|
||||
if (fb->scene_buf)
|
||||
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
|
||||
}
|
||||
|
|
@ -7625,6 +7791,7 @@ const WMBackend wl_backend = {
|
|||
.stacking_lower = wl_stacking_lower,
|
||||
.stacking_restack = wl_stacking_restack,
|
||||
.stacking_query = wl_stacking_query,
|
||||
.stacking_set_level = wl_stacking_set_level,
|
||||
|
||||
.event_flush = wl_event_flush,
|
||||
.event_flush_expose = wl_event_flush_expose,
|
||||
|
|
|
|||
|
|
@ -766,6 +766,13 @@ x11_stacking_lower(WNativeWindow win)
|
|||
XLowerWindow(dpy, WNW_TO_XID(win));
|
||||
}
|
||||
|
||||
static void
|
||||
x11_stacking_set_level(WNativeWindow win, int level)
|
||||
{
|
||||
/* X11: the server owns z-order; no per-level scene trees needed. */
|
||||
(void)win; (void)level;
|
||||
}
|
||||
|
||||
static int
|
||||
x11_stacking_query(WNativeWindow root, WNativeWindow **out, unsigned int *count)
|
||||
{
|
||||
|
|
@ -4252,6 +4259,7 @@ const WMBackend x11_backend = {
|
|||
.stacking_raise = x11_stacking_raise,
|
||||
.stacking_lower = x11_stacking_lower,
|
||||
.stacking_query = x11_stacking_query,
|
||||
.stacking_set_level = x11_stacking_set_level,
|
||||
.event_flush = x11_event_flush,
|
||||
.event_sync = x11_event_sync,
|
||||
.event_pending = x11_event_pending,
|
||||
|
|
|
|||
|
|
@ -399,6 +399,7 @@ void AddToStackList(WCoreWindow * frame)
|
|||
|
||||
frame->screen_ptr->window_count++;
|
||||
wm_backend->context_save(frame->window, WM_CTX_STACK, frame);
|
||||
wm_backend->stacking_set_level(frame->window, frame->stacking->window_level);
|
||||
curtop = WMGetFromBag(scr->stacking_list, index);
|
||||
|
||||
/* first window in this level */
|
||||
|
|
|
|||
Loading…
Reference in New Issue