From b23e6708241666fde09c6763165546caddd01e99 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Sat, 24 Aug 2024 23:43:12 +0200 Subject: [PATCH] tree/view: Send estimated configure before mapping Sending 0,0 as configure dimensions indicate that the client is free to pick its own dimensions. When tiling, the client needs to strictly adhere to the tile dimensions. Sway's handling of this has been to send a the appropriate dimensions in a new configure when the surface is mapped, leading to the first buffer most likely being incorrectly sized. Introduce a view_premap which goes through part of the mapping dance, calculating what the container dimensions would be at this time and sending it as a configure. This is only an estimate and inherently racey if the user very quickly changes focused workspace between surface role commit and first attached buffer, but when it works the client has a chance of getting the first buffer right, when it doesn't it is no worse than it would have been otherwise. Fixes: https://github.com/swaywm/sway/issues/2176 --- include/sway/tree/arrange.h | 1 + include/sway/tree/view.h | 15 ++++++ sway/desktop/xdg_shell.c | 15 +++++- sway/tree/arrange.c | 82 ++++++++++++++++++++++++++++++++ sway/tree/view.c | 94 +++++++++++++++++++++++++++++++++++++ 5 files changed, 206 insertions(+), 1 deletion(-) diff --git a/include/sway/tree/arrange.h b/include/sway/tree/arrange.h index 06a2279c1..2e7ed7c63 100644 --- a/include/sway/tree/arrange.h +++ b/include/sway/tree/arrange.h @@ -7,6 +7,7 @@ struct sway_container; struct sway_node; void arrange_container(struct sway_container *container); +void next_sibling_container_geometry(struct sway_container *child, struct sway_container *sibling, bool fullscreen); void arrange_workspace(struct sway_workspace *workspace); diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index 14aad1a18..047eefe1d 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -297,6 +297,21 @@ void view_begin_destroy(struct sway_view *view); void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, bool fullscreen, struct wlr_output *fullscreen_output, bool decoration); +/** + * Prepare the view for its upcoming mapping, sending the intended dimensions + * so that the first frame has a chance of being correct. If CSD preferences or + * floating tendency changes, this may turn out to be inaccurate but no worse + * than skipping the step. + * + * `fullscreen` should be set to true (and optionally `fullscreen_output` + * should be populated) if the view should be made fullscreen immediately. + * + * `decoration` should be set to true if the client prefers CSD. The client's + * preference may be ignored. + */ +void view_premap(struct sway_view *view, struct wlr_surface *wlr_surface, + bool fullscreen, struct wlr_output *fullscreen_output, bool decoration); + void view_unmap(struct sway_view *view); void view_update_size(struct sway_view *view); diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c index 3aed4ec7e..c0bf20f48 100644 --- a/sway/desktop/xdg_shell.c +++ b/sway/desktop/xdg_shell.c @@ -287,10 +287,23 @@ static void handle_commit(struct wl_listener *listener, void *data) { if (view->xdg_decoration != NULL) { set_xdg_decoration_mode(view->xdg_decoration); } - // XXX: https://github.com/swaywm/sway/issues/2176 + + bool csd = false; + if (view->xdg_decoration) { + enum wlr_xdg_toplevel_decoration_v1_mode mode = + view->xdg_decoration->wlr_xdg_decoration->requested_mode; + csd = mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + } else { + struct sway_server_decoration *deco = + decoration_from_surface(xdg_surface->surface); + csd = !deco || deco->wlr_server_decoration->mode == + WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT; + } + wlr_xdg_surface_schedule_configure(xdg_surface); wlr_xdg_toplevel_set_wm_capabilities(view->wlr_xdg_toplevel, XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); + view_premap(&xdg_shell_view->view, xdg_surface->surface, false, NULL, csd); // TODO: wlr_xdg_toplevel_set_bounds() return; } diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c index d4003fe65..7bfd41a06 100644 --- a/sway/tree/arrange.c +++ b/sway/tree/arrange.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -249,6 +250,87 @@ void arrange_container(struct sway_container *container) { node_set_dirty(&container->node); } +void next_sibling_container_geometry(struct sway_container *child, struct sway_container *sibling, bool fullscreen) { + assert(child->view); + struct sway_workspace *workspace = child->pending.workspace; + if (!workspace->output || workspace->width == 0 || workspace->height == 0) { + return; + } + if (workspace->fullscreen && !fullscreen) { + return; + } + + struct sway_output *output = workspace->output; + if (fullscreen) { + child->pending.x = output->lx; + child->pending.y = output->ly; + child->pending.width = output->width; + child->pending.height = output->height; + view_autoconfigure(child->view); + return; + } + + list_t *siblings; + struct wlr_box box; + enum sway_container_layout layout; + + if (sibling && sibling->pending.parent) { + struct sway_container *parent = sibling->pending.parent; + siblings = parent->pending.children; + layout = parent->pending.layout; + workspace_get_box(workspace, &box); + } else { + siblings = workspace->tiling; + layout = workspace->layout; + workspace_get_box(workspace, &box); + } + + // We only want to mutate the specified child, not its siblings. Make a + // shallow cloned list of siblings and containers so that their updated + // geometry can be thrown away. + list_t *children = create_list(); + if (!children) { + return; + } + struct sway_container *cons = calloc(siblings->length-1, sizeof(*cons)); + if (!cons) { + list_free(children); + return; + } + for (int idx = 0, ydx = 0; idx < siblings->length; idx++, ydx++) { + if (siblings->items[idx] == child) { + list_add(children, child); + ydx--; + continue; + } + cons[ydx] = *(struct sway_container *)siblings->items[idx]; + list_add(children, &cons[ydx]); + } + + // Update the geometry + switch (layout) { + case L_HORIZ: + apply_horiz_layout(children, &box); + break; + case L_VERT: + apply_vert_layout(children, &box); + break; + case L_TABBED: + apply_tabbed_layout(children, &box); + break; + case L_STACKED: + apply_stacked_layout(children, &box); + break; + case L_NONE: + apply_horiz_layout(children, &box); + break; + } + + view_autoconfigure(child->view); + list_free(children); + free(cons); +} + void arrange_workspace(struct sway_workspace *workspace) { if (config->reloading) { return; diff --git a/sway/tree/view.c b/sway/tree/view.c index d25a09c2a..d55d48e02 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -714,6 +714,100 @@ static void handle_foreign_destroy( wl_list_remove(&view->foreign_destroy.link); } +void view_premap(struct sway_view *view, struct wlr_surface *wlr_surface, + bool fullscreen, struct wlr_output *fullscreen_output, + bool decoration) { + + // If there is a request to be opened fullscreen on a specific output, try + // to honor that request. Otherwise, fallback to assigns, pid mappings, + // focused workspace, etc + struct sway_workspace *ws = NULL; + if (fullscreen_output && fullscreen_output->data) { + struct sway_output *output = fullscreen_output->data; + ws = output_get_active_workspace(output); + } + if (!ws) { + ws = select_workspace(view); + } + if (!ws || !ws->output) { + // Nothing for us to do if we don't have a workspace on an output + return; + } + + // Once the output is determined, we can notify the client early about + // scale to reduce startup jitter. + float scale = ws->output->wlr_output->scale; + wlr_fractional_scale_v1_notify_scale(wlr_surface, scale); + wlr_surface_set_preferred_buffer_scale(wlr_surface, ceil(scale)); + + if (view->impl->wants_floating && view->impl->wants_floating(view)) { + // Nothing more to do for floating, let it pick its own dimensions + return; + } + + struct sway_seat *seat = input_manager_current_seat(); + struct sway_node *node = seat_get_focus_inactive(seat, &ws->node); + struct sway_container *target_sibling = NULL; + if (node && node->type == N_CONTAINER) { + if (container_is_floating(node->sway_container)) { + // If we're about to launch the view into the floating container, then + // launch it as a tiled view instead. + target_sibling = seat_get_focus_inactive_tiling(seat, ws); + if (target_sibling) { + struct sway_container *con = + seat_get_focus_inactive_view(seat, &target_sibling->node); + if (con) { + target_sibling = con; + } + } + } else { + target_sibling = node->sway_container; + } + } + + // Fill out enough of a dummy container to satisfy configuration + struct sway_container con = { + .view = view, + .pending = (struct sway_container_state) { + .workspace = ws, + .parent = target_sibling ? target_sibling->pending.parent : NULL, + .border = config->border, + .border_thickness = config->border_thickness, + } + }; + view->container = &con; + + // Insert the container into the appropriate children list so that smart + // gaps will work correctly + list_t *siblings; + int sibling_index; + if (target_sibling && target_sibling->pending.parent) { + struct sway_container *parent = target_sibling->pending.parent; + siblings = parent->pending.children; + sibling_index = list_find(siblings, target_sibling) + 1; + } else { + siblings = ws->tiling; + sibling_index = ws->tiling->length; + } + list_insert(siblings, sibling_index, &con); + + view_set_tiled(view, true); + view_update_csd_from_client(view, decoration); + next_sibling_container_geometry(&con, target_sibling, fullscreen); + + // Send the configure event for the calculated dimensions + view_configure(view, + con.pending.content_x, + con.pending.content_y, + con.pending.content_width, + con.pending.content_height); + + sway_assert(siblings->items[sibling_index] == &con, + "container siblings mutated unexpectedly"); + list_del(siblings, sibling_index); + view->container = NULL; +} + void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, bool fullscreen, struct wlr_output *fullscreen_output, bool decoration) {