From c229f6edc0fb5a2a3d6ef83a71e7474e241125a5 Mon Sep 17 00:00:00 2001 From: "Andrew J. Hesford" Date: Sun, 31 Dec 2023 21:39:06 -0500 Subject: [PATCH] view: try to honor original geometry with layout changes Whenever the output layout changes, each view's original geometry will be captured as last_layout_geometry (if it has not already been captured by a previous layout change), which will remain valid unless the user modifies the view's geometry (i.e., by tiling, maximizing, moving, resizing or full-screening). On subsequent output layout changes, views with valid last_layout_geometry will be back to their original position if possible, or else to the closest possible output. --- include/view.h | 8 +++ src/interactive.c | 8 +++ src/view.c | 159 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 156 insertions(+), 19 deletions(-) diff --git a/include/view.h b/include/view.h index bef7afea..bfb84bd1 100644 --- a/include/view.h +++ b/include/view.h @@ -180,6 +180,13 @@ struct view { * maximized/fullscreen/tiled. */ struct wlr_box natural_geometry; + /* + * Whenever an output layout change triggers a view relocation, the + * last pending position (or natural geometry) will be saved so the + * view may be restored to its original location on a subsequent layout + * change. + */ + struct wlr_box last_layout_geometry; /* used by xdg-shell views */ uint32_t pending_configure_serial; @@ -420,6 +427,7 @@ bool view_is_floating(struct view *view); void view_move_to_workspace(struct view *view, struct workspace *workspace); void view_set_decorations(struct view *view, bool decorations); void view_toggle_fullscreen(struct view *view); +void view_invalidate_last_layout_geometry(struct view *view); void view_adjust_for_layout_change(struct view *view); void view_move_to_edge(struct view *view, enum view_edge direction, bool snap_to_windows); void view_grow_to_edge(struct view *view, enum view_edge direction); diff --git a/src/interactive.c b/src/interactive.c index 95eb4b78..f92ab8c1 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -70,6 +70,7 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges) } else { /* Store natural geometry at start of move */ view_store_natural_geometry(view); + view_invalidate_last_layout_geometry(view); } /* Prevent region snapping when just moving via A-Left mousebind */ @@ -86,6 +87,13 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges) */ return; } + + /* + * Resizing overrides any attempt to restore window + * geometries altered by layout changes. + */ + view_invalidate_last_layout_geometry(view); + /* * If tiled or maximized in only one direction, reset * tiled/maximized state but keep the same geometry as diff --git a/src/view.c b/src/view.c index 0fcfdd2e..72f23812 100644 --- a/src/view.c +++ b/src/view.c @@ -252,14 +252,27 @@ view_get_edge_snap_box(struct view *view, struct output *output, return dst; } -static void -view_discover_output(struct view *view) +static bool +view_discover_output(struct view *view, struct wlr_box *geometry) { assert(view); assert(!view->fullscreen); - view->output = output_nearest_to(view->server, - view->current.x + view->current.width / 2, - view->current.y + view->current.height / 2); + + if (!geometry) { + geometry = &view->current; + } + + struct output *output = + output_nearest_to(view->server, + geometry->x + geometry->width / 2, + geometry->y + geometry->height / 2); + + if (output && output != view->output) { + view->output = output; + return true; + } + + return false; } static void @@ -343,7 +356,7 @@ view_moved(struct view *view) * output when they enter that state. */ if (view_is_floating(view)) { - view_discover_output(view); + view_discover_output(view, NULL); } ssd_update_geometry(view->ssd); cursor_update_focus(view->server); @@ -614,11 +627,11 @@ view_adjust_floating_geometry(struct view *view, struct wlr_box *geometry) bool adjusted = false; /* - * First check whether the view is onscreen. For now, "onscreen" - * is defined as even one pixel of the client area being visible. + * First check whether the view is the target screen, meaning that at + * least one client pixel is on the screen. */ if (wlr_output_layout_intersects(view->server->output_layout, - NULL, geometry)) { + view->output->wlr_output, geometry)) { /* * If onscreen, then make sure the titlebar is also * visible (and not overlapping any panels/docks) @@ -988,6 +1001,7 @@ view_maximize(struct view *view, enum view_axis axis, interactive_cancel(view); if (store_natural_geometry && view_is_floating(view)) { view_store_natural_geometry(view); + view_invalidate_last_layout_geometry(view); } } set_maximized(view, axis); @@ -1200,6 +1214,7 @@ view_set_fullscreen(struct view *view, bool fullscreen) */ interactive_cancel(view); view_store_natural_geometry(view); + view_invalidate_last_layout_geometry(view); } set_fullscreen(view, fullscreen); @@ -1211,37 +1226,141 @@ view_set_fullscreen(struct view *view, bool fullscreen) set_adaptive_sync_fullscreen(view); } +static bool +last_layout_geometry_is_valid(struct view *view) +{ + return view->last_layout_geometry.width > 0 + && view->last_layout_geometry.height > 0; +} + +static void +update_last_layout_geometry(struct view *view) +{ + /* + * Only update an invalid last-layout geometry to prevent a series of + * successive layout changes from continually replacing the "preferred" + * location with whatever location the view currently holds. The + * "preferred" location should be whatever state was set by user + * interaction, not automatic responses to layout changes. + */ + if (last_layout_geometry_is_valid(view)) { + return; + } + + if (view_is_floating(view)) { + view->last_layout_geometry = view->pending; + } else { + view->last_layout_geometry = view->natural_geometry; + } +} + +static bool +apply_last_layout_geometry(struct view *view, bool force_update) +{ + /* Only apply a valid last-layout geometry */ + if (!last_layout_geometry_is_valid(view)) { + return false; + } + + /* + * Unless forced, the last-layout geometry is only applied + * when the relevant view geometry is distinct. + */ + if (!force_update) { + struct wlr_box *relevant = view_is_floating(view) ? + &view->pending : &view->natural_geometry; + + if (wlr_box_equal(relevant, &view->last_layout_geometry)) { + return false; + } + } + + view->natural_geometry = view->last_layout_geometry; + view_adjust_floating_geometry(view, &view->natural_geometry); + return true; +} + +void +view_invalidate_last_layout_geometry(struct view *view) +{ + assert(view); + view->last_layout_geometry.width = 0; + view->last_layout_geometry.height = 0; +} + void view_adjust_for_layout_change(struct view *view) { assert(view); - /* Exit fullscreen if output is lost */ bool was_fullscreen = view->fullscreen; - if (was_fullscreen && !output_is_usable(view->output)) { - set_fullscreen(view, false); + bool is_floating = view_is_floating(view); + + if (!output_is_usable(view->output)) { + /* A view losing an output should have a last-layout geometry */ + update_last_layout_geometry(view); + + /* Exit fullscreen and re-assess floating status */ + if (was_fullscreen) { + set_fullscreen(view, false); + is_floating = view_is_floating(view); + } + } + + /* Restore any full-screen window to natural geometry */ + bool use_natural = was_fullscreen; + + /* Capture a pointer to the last-layout geometry (only if valid) */ + struct wlr_box *last_geometry = NULL; + if (last_layout_geometry_is_valid(view)) { + last_geometry = &view->last_layout_geometry; } /* - * Floating views are always assigned to the nearest output. - * Maximized/tiled views remain on the same output if possible. + * Check if an output change is required: + * - Floating views are always mapped to the nearest output + * - Any view without a usable output needs to be repositioned + * - Any view with a valid last-layout geometry might be better + * positioned on another output */ - bool is_floating = view_is_floating(view); - if (is_floating || !output_is_usable(view->output)) { - view_discover_output(view); + if (is_floating || last_geometry || !output_is_usable(view->output)) { + /* Move the view to an appropriate output, if needed */ + bool output_changed = view_discover_output(view, last_geometry); + + /* + * Try to apply the last-layout to the natural geometry + * (adjusting to ensure that it fits on the screen). This is + * forced if the output has changed, but will be done + * opportunistically even on the same output if the last-layout + * geometry is different from the view's governing geometry. + */ + if (apply_last_layout_geometry(view, output_changed)) { + use_natural = true; + } + + /* + * Whether or not the view has moved, the layout has changed. + * Ensure that the view now has a valid last-layout geometry. + */ + update_last_layout_geometry(view); } if (!is_floating) { view_apply_special_geometry(view); - } else if (was_fullscreen) { + } else if (use_natural) { + /* + * Move the window to its natural location, either because it + * was fullscreen or we are trying to restore a prior layout. + */ view_apply_natural_geometry(view); } else { - /* reposition view if it's offscreen */ + /* Otherwise, just ensure the view is on screen. */ struct wlr_box geometry = view->pending; if (view_adjust_floating_geometry(view, &geometry)) { view_move_resize(view, geometry); } } + if (view->toplevel.handle) { foreign_toplevel_update_outputs(view); } @@ -1542,6 +1661,7 @@ view_snap_to_edge(struct view *view, enum view_edge edge, } else if (store_natural_geometry) { /* store current geometry as new natural_geometry */ view_store_natural_geometry(view); + view_invalidate_last_layout_geometry(view); } view_set_untiled(view); view_set_output(view, output); @@ -1571,6 +1691,7 @@ view_snap_to_region(struct view *view, struct region *region, } else if (store_natural_geometry) { /* store current geometry as new natural_geometry */ view_store_natural_geometry(view); + view_invalidate_last_layout_geometry(view); } view_set_untiled(view); view->tiled_region = region;