diff --git a/include/waybox/server.h b/include/waybox/server.h index fafeefe..88521cf 100644 --- a/include/waybox/server.h +++ b/include/waybox/server.h @@ -45,6 +45,10 @@ struct wb_server { struct wlr_xdg_output_manager_v1 *output_manager; struct wlr_renderer *renderer; struct wlr_scene *scene; + /* Container for all xdg toplevels, kept at a fixed z between the lower + * (background, bottom) and upper (top, overlay) layer-shell trees so panels + * like Waybar correctly render above ordinary windows. */ + struct wlr_scene_tree *toplevel_tree; struct wlr_scene_output_layout *scene_layout; struct wlr_session *session; struct wlr_subcompositor *subcompositor; diff --git a/waybox/decoration.cpp b/waybox/decoration.cpp index 34aa355..770b1e6 100644 --- a/waybox/decoration.cpp +++ b/waybox/decoration.cpp @@ -13,14 +13,22 @@ * negotiation honours DecorMode::Full and client SSD requests. */ static constexpr bool kSsdAvailable = true; -/* Whether this toplevel should currently wear a server-side frame: the - * negotiated decoration is server-side and it is not fullscreen. */ +/* Whether this toplevel should currently wear a server-side frame. + * + * SSD requires the client to participate in the xdg-decoration protocol: a + * client that never created a decoration object (notably GTK, which is + * CSD-only and always draws its own titlebar + shadow) cannot be told to drop + * its decorations, so imposing an SSD frame on it produces conflicting double + * decorations. We therefore only wear a frame when the client bound the + * decoration protocol for this toplevel; for such clients we then honour + * DecorMode and the client's requested mode. */ static bool toplevel_wants_ssd(struct wb_toplevel *toplevel) { if (toplevel->xdg_toplevel->current.fullscreen) return false; - bool client_wants_ssd = toplevel->decoration != nullptr && - toplevel->decoration->requested_mode == - WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; + if (toplevel->decoration == nullptr) + return false; /* CSD-only client (e.g. GTK): leave its decorations be */ + bool client_wants_ssd = toplevel->decoration->requested_mode == + WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; return wb::negotiate_decoration(toplevel->decorations, client_wants_ssd, kSsdAvailable) == wb::NegotiatedDecoration::ServerSide; } diff --git a/waybox/output.cpp b/waybox/output.cpp index e95663f..8528751 100644 --- a/waybox/output.cpp +++ b/waybox/output.cpp @@ -233,6 +233,20 @@ void new_output_notify(struct wb_server *server, void *data) { &wlr_scene_tree_create(&server->scene->tree)->node; } + /* Stack this output's layer trees around the shared toplevel container so + * panels render correctly: background/bottom below ordinary windows; the + * fullscreen, top and overlay layers (where panels like Waybar live) above + * them. New toplevels are children of toplevel_tree, so they never lift + * above the top/overlay layers. */ + struct wlr_scene_node *tl = &server->toplevel_tree->node; + wlr_scene_node_place_below(&output->layers.shell_background->node, tl); + wlr_scene_node_place_below(&output->layers.shell_bottom->node, tl); + wlr_scene_node_place_above(&output->layers.shell_top->node, tl); + wlr_scene_node_place_above(&output->layers.shell_fullscreen->node, + &output->layers.shell_top->node); + wlr_scene_node_place_above(&output->layers.shell_overlay->node, + &output->layers.shell_fullscreen->node); + wl_list_insert(&server->outputs, &output->link); /* Initialise the usable area to the full output; arrange_layers() will diff --git a/waybox/server.cpp b/waybox/server.cpp index 8e67ee2..0eedc4a 100644 --- a/waybox/server.cpp +++ b/waybox/server.cpp @@ -129,6 +129,11 @@ bool wb_start_server(struct wb_server* server) { server->scene = wlr_scene_create(); server->scene_layout = wlr_scene_attach_output_layout(server->scene, server->output_layout); + /* The toplevel container. Created up-front so per-output layer trees (made + * as outputs come up) can be stacked relative to it; toplevels are added as + * its children, so adding/raising a window never lifts it above the panel + * layers. */ + server->toplevel_tree = wlr_scene_tree_create(&server->scene->tree); const char *socket = wl_display_add_socket_auto(server->wl_display); if (!socket) { diff --git a/waybox/xdg_shell.cpp b/waybox/xdg_shell.cpp index 71a79db..3417dff 100644 --- a/waybox/xdg_shell.cpp +++ b/waybox/xdg_shell.cpp @@ -724,11 +724,21 @@ void set_toplevel_fullscreen(struct wb_toplevel *toplevel, bool fullscreen) { struct wlr_output *wlr_output = get_active_output(toplevel); struct wb_output *output = static_cast( wlr_output ? wlr_output->data : NULL); - if (output != NULL) + if (output != NULL) { toplevel->geometry = output->geometry; /* layout box: pos + size */ + /* Lift the window into the fullscreen layer so it covers panels + * (the top layer), which ordinary toplevels sit below. */ + if (toplevel->scene_tree != NULL) + wlr_scene_node_reparent(&toplevel->scene_tree->node, + output->layers.shell_fullscreen); + } raise_toplevel(toplevel); } else { toplevel->geometry = toplevel->restore_fullscreen; + /* Return to the ordinary toplevel container (below the panel layers). */ + if (toplevel->scene_tree != NULL) + wlr_scene_node_reparent(&toplevel->scene_tree->node, + toplevel->server->toplevel_tree); } wlr_xdg_toplevel_set_fullscreen(toplevel->xdg_toplevel, fullscreen); @@ -951,7 +961,7 @@ static void handle_new_xdg_toplevel(struct wb_server *server, void *data) { * decoration nodes). For now the surface sits at the container origin: with * no decorations the insets are zero, so this is identical to attaching the * surface directly. */ - toplevel->scene_tree = wlr_scene_tree_create(&toplevel->server->scene->tree); + toplevel->scene_tree = wlr_scene_tree_create(toplevel->server->toplevel_tree); toplevel->surface_tree = wlr_scene_xdg_surface_create( toplevel->scene_tree, xdg_toplevel->base); /* Tag both the container and the surface tree so a click on a decoration