From b21daabf777deb19df9bb931b6d3c5dba181283a Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Tue, 3 Feb 2026 19:18:53 +0900 Subject: [PATCH] view: decouple always-on-top windows from the omnipresent state Before this commit, always-on-{top,bottom} windows were always visible on all workspaces (omnipresent) because the they were not in per-workspace trees like normal windows. This commit fixes this by introducing per-layer trees in `struct workspace`. I also added `enum view_layer` and `view->layer` for simplicity. --- include/config/types.h | 5 +--- include/labwc.h | 4 --- include/view.h | 10 +++++--- include/workspaces.h | 1 + src/debug.c | 52 ++++++++++++++++++++++++------------- src/desktop.c | 4 +-- src/server.c | 6 +---- src/view.c | 58 +++++++++++------------------------------- src/workspaces.c | 19 +++++++------- src/xdg.c | 3 ++- src/xwayland.c | 3 ++- 11 files changed, 75 insertions(+), 90 deletions(-) diff --git a/include/config/types.h b/include/config/types.h index 4561194e..3b1fa5e8 100644 --- a/include/config/types.h +++ b/include/config/types.h @@ -62,10 +62,7 @@ enum lab_view_criteria { /* No filter -> all focusable views */ LAB_VIEW_CRITERIA_NONE = 0, - /* - * Includes always-on-top views, e.g. - * what is visible on the current workspace - */ + /* Includes omnipresent (visible on all desktops) views */ LAB_VIEW_CRITERIA_CURRENT_WORKSPACE = 1 << 0, /* Positive criteria */ diff --git a/include/labwc.h b/include/labwc.h index 5e887ad8..c5dd9a44 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -231,10 +231,6 @@ struct server { * them to this dedicated tree */ struct wlr_scene_tree *xdg_popup_tree; - - /* Tree for all non-layer xdg/xwayland-shell surfaces with always-on-top/below */ - struct wlr_scene_tree *view_tree_always_on_top; - struct wlr_scene_tree *view_tree_always_on_bottom; #if HAVE_XWAYLAND /* Tree for unmanaged xsurfaces without initialized view (usually popups) */ struct wlr_scene_tree *unmanaged_tree; diff --git a/include/view.h b/include/view.h index b1c7b65d..7dcee3fb 100644 --- a/include/view.h +++ b/include/view.h @@ -82,6 +82,12 @@ enum view_wants_focus { VIEW_WANTS_FOCUS_UNLIKELY, }; +enum view_layer { + VIEW_LAYER_NORMAL = 0, + VIEW_LAYER_ALWAYS_ON_TOP, + VIEW_LAYER_ALWAYS_ON_BOTTOM, +}; + struct view; struct wlr_surface; struct foreign_toplevel; @@ -183,6 +189,7 @@ struct view { bool fullscreen; bool tearing_hint; enum lab_tristate force_tearing; + enum view_layer layer; bool visible_on_all_workspaces; enum lab_edge tiled; enum lab_edge edges_visible; @@ -527,9 +534,6 @@ void view_toggle_maximize(struct view *view, enum view_axis axis); bool view_wants_decorations(struct view *view); void view_toggle_decorations(struct view *view); -bool view_is_always_on_top(struct view *view); -bool view_is_always_on_bottom(struct view *view); -bool view_is_omnipresent(struct view *view); void view_toggle_always_on_top(struct view *view); void view_toggle_always_on_bottom(struct view *view); void view_toggle_visible_on_all_workspaces(struct view *view); diff --git a/include/workspaces.h b/include/workspaces.h index cc9a2fcf..c12a9c45 100644 --- a/include/workspaces.h +++ b/include/workspaces.h @@ -16,6 +16,7 @@ struct workspace { char *name; struct wlr_scene_tree *tree; + struct wlr_scene_tree *view_trees[3]; struct lab_cosmic_workspace *cosmic_workspace; struct { diff --git a/src/debug.c b/src/debug.c index 7a39824f..14516f21 100644 --- a/src/debug.c +++ b/src/debug.c @@ -88,9 +88,25 @@ get_view_part(struct view *view, struct wlr_scene_node *node) return ssd_debug_get_node_name(view->ssd, node); } +static struct workspace * +get_workspace_from_node(struct server *server, struct wlr_scene_node *node) +{ + struct workspace *workspace; + wl_list_for_each(workspace, &server->workspaces.all, link) { + if (&workspace->tree->node == node) { + return workspace; + } + } + return NULL; +} + static const char * get_special(struct server *server, struct wlr_scene_node *node) { + struct wlr_scene_tree *grand_parent = + node->parent ? node->parent->node.parent : NULL; + struct wlr_scene_tree *grand_grand_parent = + grand_parent ? grand_parent->node.parent : NULL; if (node == &server->scene->tree.node) { return "server->scene"; } @@ -100,21 +116,28 @@ get_special(struct server *server, struct wlr_scene_node *node) if (node == &server->view_tree->node) { return "server->view_tree"; } - if (node == &server->view_tree_always_on_bottom->node) { - return "server->always_on_bottom"; - } - if (node == &server->view_tree_always_on_top->node) { - return "server->always_on_top"; - } if (node->parent == server->view_tree) { - struct workspace *workspace; - wl_list_for_each(workspace, &server->workspaces.all, link) { - if (&workspace->tree->node == node) { - return workspace->name; - } + struct workspace *workspace = get_workspace_from_node(server, node); + if (workspace) { + return workspace->name; } return "unknown workspace"; } + if (grand_parent == server->view_tree) { + struct workspace *workspace = + get_workspace_from_node(server, &node->parent->node); + if (workspace) { + struct wlr_scene_tree **trees = workspace->view_trees; + if (node == &trees[VIEW_LAYER_NORMAL]->node) { + return "normal"; + } else if (node == &trees[VIEW_LAYER_ALWAYS_ON_TOP]->node) { + return "always_on_top"; + } else if (node == &trees[VIEW_LAYER_ALWAYS_ON_BOTTOM]->node) { + return "always_on_bottom"; + } + } + return "unknown tree"; + } if (node->parent == &server->scene->tree) { struct output *output; wl_list_for_each(output, &server->outputs, link) { @@ -160,12 +183,7 @@ get_special(struct server *server, struct wlr_scene_node *node) return "server->unmanaged_tree"; } #endif - struct wlr_scene_tree *grand_parent = - node->parent ? node->parent->node.parent : NULL; - if (grand_parent == server->view_tree && node->data) { - last_view = node_view_from_node(node); - } - if (node->parent == server->view_tree_always_on_top && node->data) { + if (grand_grand_parent == server->view_tree && node->data) { last_view = node_view_from_node(node); } const char *view_part = get_view_part(last_view, node); diff --git a/src/desktop.c b/src/desktop.c index 6a64ab13..e5a33a78 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -96,9 +96,9 @@ desktop_focus_view(struct view *view, bool raise) /* * Switch workspace if necessary to make the view visible - * (unnecessary for "always on {top,bottom}" views). + * (unnecessary for omnipresent views). */ - if (!view_is_always_on_top(view) && !view_is_always_on_bottom(view)) { + if (!view->visible_on_all_workspaces) { workspaces_switch_to(view->workspace, /*update_focus*/ false); } diff --git a/src/server.c b/src/server.c index a71c76eb..580d2db2 100644 --- a/src/server.c +++ b/src/server.c @@ -576,16 +576,12 @@ server_init(struct server *server) * | output->layer_tree[2] | top layer surfaces (e.g. waybar) * | server->unmanaged_tree | unmanaged X11 surfaces (e.g. dmenu) * | server->xdg_popup_tree | xdg popups on xdg windows - * | server->view_tree_always_on_top | always-on-top xdg/X11 windows - * | server->view_tree | normal xdg/X11 windows (e.g. firefox) - * | server->view_tree_always_on_bottom | always-on-bottom xdg/X11 windows + * | server->view_tree | xdg/X11 windows (e.g. firefox) * | output->layer_tree[1] | bottom layer surfaces * | output->layer_tree[0] | background layer surfaces (e.g. swaybg) */ - server->view_tree_always_on_bottom = wlr_scene_tree_create(&server->scene->tree); server->view_tree = wlr_scene_tree_create(&server->scene->tree); - server->view_tree_always_on_top = wlr_scene_tree_create(&server->scene->tree); server->xdg_popup_tree = wlr_scene_tree_create(&server->scene->tree); #if HAVE_XWAYLAND server->unmanaged_tree = wlr_scene_tree_create(&server->scene->tree); diff --git a/src/view.c b/src/view.c index 5271404b..f5e89d18 100644 --- a/src/view.c +++ b/src/view.c @@ -267,13 +267,7 @@ matches_criteria(struct view *view, enum lab_view_criteria criteria) return false; } if (criteria & LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) { - /* - * Always-on-top views are always on the current desktop and are - * special in that they live in a different tree. - */ - struct server *server = view->server; - if (view->scene_tree->node.parent != server->workspaces.current->tree - && !view_is_always_on_top(view)) { + if (view->workspace != view->server->workspaces.current) { return false; } } @@ -283,7 +277,7 @@ matches_criteria(struct view *view, enum lab_view_criteria criteria) } } if (criteria & LAB_VIEW_CRITERIA_ALWAYS_ON_TOP) { - if (!view_is_always_on_top(view)) { + if (view->layer != VIEW_LAYER_ALWAYS_ON_TOP) { return false; } } @@ -293,7 +287,7 @@ matches_criteria(struct view *view, enum lab_view_criteria criteria) } } if (criteria & LAB_VIEW_CRITERIA_NO_ALWAYS_ON_TOP) { - if (view_is_always_on_top(view)) { + if (view->layer == VIEW_LAYER_ALWAYS_ON_TOP) { return false; } } @@ -303,11 +297,7 @@ matches_criteria(struct view *view, enum lab_view_criteria criteria) } } if (criteria & LAB_VIEW_CRITERIA_NO_OMNIPRESENT) { - /* - * TODO: Once always-on-top views use a per-workspace - * sub-tree we can remove the check from this condition. - */ - if (view->visible_on_all_workspaces || view_is_always_on_top(view)) { + if (view->visible_on_all_workspaces) { return false; } } @@ -1549,48 +1539,30 @@ view_toggle_decorations(struct view *view) } } -bool -view_is_always_on_top(struct view *view) -{ - assert(view); - return view->scene_tree->node.parent == - view->server->view_tree_always_on_top; -} - void view_toggle_always_on_top(struct view *view) { assert(view); - if (view_is_always_on_top(view)) { - view->workspace = view->server->workspaces.current; - wlr_scene_node_reparent(&view->scene_tree->node, - view->workspace->tree); + if (view->layer == VIEW_LAYER_ALWAYS_ON_TOP) { + view->layer = VIEW_LAYER_NORMAL; } else { - wlr_scene_node_reparent(&view->scene_tree->node, - view->server->view_tree_always_on_top); + view->layer = VIEW_LAYER_ALWAYS_ON_TOP; } -} - -bool -view_is_always_on_bottom(struct view *view) -{ - assert(view); - return view->scene_tree->node.parent == - view->server->view_tree_always_on_bottom; + wlr_scene_node_reparent(&view->scene_tree->node, + view->workspace->view_trees[view->layer]); } void view_toggle_always_on_bottom(struct view *view) { assert(view); - if (view_is_always_on_bottom(view)) { - view->workspace = view->server->workspaces.current; - wlr_scene_node_reparent(&view->scene_tree->node, - view->workspace->tree); + if (view->layer == VIEW_LAYER_ALWAYS_ON_BOTTOM) { + view->layer = VIEW_LAYER_NORMAL; } else { - wlr_scene_node_reparent(&view->scene_tree->node, - view->server->view_tree_always_on_bottom); + view->layer = VIEW_LAYER_ALWAYS_ON_BOTTOM; } + wlr_scene_node_reparent(&view->scene_tree->node, + view->workspace->view_trees[view->layer]); } void @@ -1609,7 +1581,7 @@ view_move_to_workspace(struct view *view, struct workspace *workspace) if (view->workspace != workspace) { view->workspace = workspace; wlr_scene_node_reparent(&view->scene_tree->node, - workspace->tree); + workspace->view_trees[view->layer]); } } diff --git a/src/workspaces.c b/src/workspaces.c index 9b4ab819..795c6076 100644 --- a/src/workspaces.c +++ b/src/workspaces.c @@ -235,6 +235,12 @@ add_workspace(struct server *server, const char *name) workspace->server = server; workspace->name = xstrdup(name); workspace->tree = wlr_scene_tree_create(server->view_tree); + workspace->view_trees[VIEW_LAYER_ALWAYS_ON_BOTTOM] = + wlr_scene_tree_create(workspace->tree); + workspace->view_trees[VIEW_LAYER_NORMAL] = + wlr_scene_tree_create(workspace->tree); + workspace->view_trees[VIEW_LAYER_ALWAYS_ON_TOP] = + wlr_scene_tree_create(workspace->tree); wl_list_append(&server->workspaces.all, &workspace->link); wlr_scene_node_set_enabled(&workspace->tree->node, false); @@ -483,24 +489,17 @@ workspaces_switch_to(struct workspace *target, bool update_focus) server->workspaces.current = target; struct view *grabbed_view = server->grabbed_view; - if (grabbed_view && !view_is_always_on_top(grabbed_view)) { + if (grabbed_view) { view_move_to_workspace(grabbed_view, target); } /* * Make sure we are focusing what the user sees. Only refocus if - * the focus is not already on an omnipresent or always-on-top view. - * - * TODO: Decouple always-on-top views from the omnipresent state. - * One option for that would be to create a new scene tree - * as child of every workspace tree and then reparent a-o-t - * windows to that one. Combined with adjusting the condition - * below that should take care of the issue. + * the focus is not already on an omnipresent view. */ if (update_focus) { struct view *active_view = server->active_view; - if (!active_view || (!active_view->visible_on_all_workspaces - && !view_is_always_on_top(active_view))) { + if (!(active_view && active_view->visible_on_all_workspaces)) { desktop_focus_topmost_view(server); } } diff --git a/src/xdg.c b/src/xdg.c index b239e8da..21e34b14 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -1019,7 +1019,8 @@ handle_new_xdg_toplevel(struct wl_listener *listener, void *data) } view->workspace = server->workspaces.current; - view->scene_tree = wlr_scene_tree_create(view->workspace->tree); + view->scene_tree = wlr_scene_tree_create( + view->workspace->view_trees[VIEW_LAYER_NORMAL]); wlr_scene_node_set_enabled(&view->scene_tree->node, false); struct wlr_scene_tree *tree = wlr_scene_xdg_surface_create( diff --git a/src/xwayland.c b/src/xwayland.c index 5984a31b..da8fc64c 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -1010,7 +1010,8 @@ xwayland_view_create(struct server *server, xsurface->data = view; view->workspace = server->workspaces.current; - view->scene_tree = wlr_scene_tree_create(view->workspace->tree); + view->scene_tree = wlr_scene_tree_create( + view->workspace->view_trees[VIEW_LAYER_NORMAL]); node_descriptor_create(&view->scene_tree->node, LAB_NODE_VIEW, view, /*data*/ NULL);