From 03781c4e6a61a073e03b98c289af7f9c4ebd39e5 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Mon, 12 Jan 2026 18:11:11 +0900 Subject: [PATCH] view: keep and restore the output-relative position on layout changes Before this patch, we always tried to preserve the global positions of floating windows across layout changes, which meant that windows could jump to different outputs if the output coordinates changed. Instead, this patch adds the output name and output-relative position in `view->last_placement` to keep them across layout changes, like KDE and GNOME do. This also allows us to remove `view->lost_output_due_to_layout_change`, which was required to keep the output of fullscreen/maximized windows, since we now always try to keep `view->output` whether or not the window is floating. --- include/view.h | 14 ++++++---- src/view.c | 75 +++++++++++++++++++++++++++++++------------------- 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/include/view.h b/include/view.h index a7c1dff0..6299e2e6 100644 --- a/include/view.h +++ b/include/view.h @@ -214,17 +214,21 @@ struct view { */ struct wlr_box natural_geometry; /* - * Whenever an output layout change triggers a view relocation, the - * last pending position will be saved so the view may be restored - * to its original location on a subsequent layout change. + * last_placement represents the last view position set by the user + * before layout changes. output_name and relative_geo are used to + * keep or restore the view position relative to the output and + * layout_geo is used to keep the global position when the output is + * lost. */ struct { + char *output_name; + /* view geometry in output-relative coordinates */ + struct wlr_box relative_geo; + /* view geometry in layout coordinates */ struct wlr_box layout_geo; } last_placement; /* Set temporarily when moving view due to layout change */ bool adjusting_for_layout_change; - /* True if original output was disconnected or disabled */ - bool lost_output_due_to_layout_change; /* used by xdg-shell views */ uint32_t pending_configure_serial; diff --git a/src/view.c b/src/view.c index d4220b90..ee7c52a5 100644 --- a/src/view.c +++ b/src/view.c @@ -581,6 +581,8 @@ view_moved(struct view *view) } } +static void clear_last_placement(struct view *view); + void view_move_resize(struct view *view, struct wlr_box geo) { @@ -600,8 +602,7 @@ view_move_resize(struct view *view, struct wlr_box geo) * Not sure if it might have other side-effects though. */ if (!view->adjusting_for_layout_change) { - view->last_placement.layout_geo = (struct wlr_box){0}; - view->lost_output_due_to_layout_change = false; + clear_last_placement(view); } } @@ -1744,7 +1745,11 @@ save_last_placement(struct view *view) wlr_log(WLR_ERROR, "cannot save last placement in unusable output"); return; } + xstrdup_replace(view->last_placement.output_name, output->wlr_output->name); view->last_placement.layout_geo = view->pending; + view->last_placement.relative_geo = view->pending; + view->last_placement.relative_geo.x -= output->scene_output->x; + view->last_placement.relative_geo.y -= output->scene_output->y; } void @@ -1756,50 +1761,63 @@ views_save_last_placement(struct server *server) } } +static void +clear_last_placement(struct view *view) +{ + assert(view); + zfree(view->last_placement.output_name); + view->last_placement.relative_geo = (struct wlr_box){0}; + view->last_placement.layout_geo = (struct wlr_box){0}; +} + void view_adjust_for_layout_change(struct view *view) { assert(view); - - bool is_floating = view_is_floating(view); - view->adjusting_for_layout_change = true; - if (wlr_box_empty(&view->last_placement.layout_geo)) { /* * views_save_last_placement() should be called before layout * changes. Not using assert() just in case. */ wlr_log(WLR_ERROR, "view has no last placement info"); - goto out; - } - /* - * Check if an output change is required: - * - Floating views are always mapped to the nearest output - * - Any fullscreen/tiled/maximized view which lost its output - * due to a layout change should be moved back if the output - * is reconnected. - * - * If so, try to find the "nearest" output (in terms of layout - * coordinates) to the saved geometry. Other strategies might be - * possible here -- for example, trying to match the same physical - * output the view was on previously -- but this is simplest. - */ - if (is_floating || view->lost_output_due_to_layout_change) { - view_discover_output(view, &view->last_placement.layout_geo); + return; } - if (!is_floating) { + view->adjusting_for_layout_change = true; + + struct wlr_box new_geo; + struct output *output = output_from_name(view->server, + view->last_placement.output_name); + if (output_is_usable(output)) { + /* + * When the previous output (which might have been reconnected + * or relocated) is available, keep the relative position on it. + */ + new_geo = view->last_placement.relative_geo; + new_geo.x += output->scene_output->x; + new_geo.y += output->scene_output->y; + view->output = output; + } else { + /* + * Otherwise, evacuate the view to another output. Use the last + * layout geometry so that the view position is kept when the + * user reconnects the previous output in a different connector + * or the reconnected output somehow gets a different name. + */ + view_discover_output(view, &view->last_placement.layout_geo); + new_geo = view->last_placement.layout_geo; + } + + if (!view_is_floating(view)) { view_apply_special_geometry(view); } else { - /* Restore saved geometry, ensuring view is on-screen */ - struct wlr_box geometry = view->last_placement.layout_geo; - adjust_floating_geometry(view, &geometry, + /* Ensure view is on-screen */ + adjust_floating_geometry(view, &new_geo, /* midpoint_visibility */ true); - view_move_resize(view, geometry); + view_move_resize(view, new_geo); } view_update_outputs(view); -out: view->adjusting_for_layout_change = false; } @@ -2532,6 +2550,7 @@ view_destroy(struct view *view) undecorate(view); + clear_last_placement(view); view_set_icon(view, NULL, NULL); menu_on_view_destroy(view);