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
This commit is contained in:
Kenny Levinsen 2024-08-24 23:43:12 +02:00
parent 7288f77bbe
commit b23e670824
5 changed files with 206 additions and 1 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -1,3 +1,4 @@
#include <assert.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
@ -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;

View File

@ -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) {