diff --git a/include/desktop-state.h b/include/desktop-state.h new file mode 100644 index 00000000..a96a8125 --- /dev/null +++ b/include/desktop-state.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_DESKTOP_STATE_H +#define LABWC_DESKTOP_STATE_H +#include + +struct server; +struct output; + +struct desktop_output { + struct output *output; + double scale; + struct wl_array views; + struct wl_list link; +}; + +/* Helpers to remember windows and scales for each output */ +void desktop_state_create(struct server *server, struct wl_list *desktop_outputs); +void desktop_state_destroy(struct wl_list *desktop_outputs); + +/** + * desktop_state_adjust_on_output_scale_change() - adjust views on scale change + * @server: server + * @desktop_outputs: object list containing scale and array of views + * + * Put windows back on the pre-scale-change output and also adjust position/size + * to fit the usable area. + */ +void desktop_state_adjust_on_output_scale_change(struct server *server, + struct wl_list *desktop_outputs); + +#endif /* LABWC_DESKTOP_STATE_H */ diff --git a/src/desktop-state.c b/src/desktop-state.c new file mode 100644 index 00000000..c15f4d38 --- /dev/null +++ b/src/desktop-state.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only +#define _POSIX_C_SOURCE 200809L +#include "desktop-state.h" +#include +#include +#include +#include "common/array.h" +#include "common/border.h" +#include "common/mem.h" +#include "labwc.h" +#include "output.h" +#include "ssd.h" +#include "view.h" + +void +desktop_state_create(struct server *server, struct wl_list *desktop_outputs) +{ + struct output *output; + wl_list_for_each(output, &server->outputs, link) { + struct desktop_output *desktop_output = znew(*desktop_output); + desktop_output->output = output; + desktop_output->scale = output->wlr_output->scale; + + /* + * On output scale change, windows can move from one output to + * another, so we have to remember where they were. + */ + struct view *view; + wl_array_init(&desktop_output->views); + wl_list_for_each(view, &server->views, link) { + if (view->output == output) { + array_add(&desktop_output->views, view); + } + } + + wl_list_insert(desktop_outputs, &desktop_output->link); + } +} + +void desktop_state_destroy(struct wl_list *desktop_outputs) +{ + struct desktop_output *desktop_output, *next; + wl_list_for_each_safe(desktop_output, next, desktop_outputs, link) { + wl_list_remove(&desktop_output->link); + wl_array_release(&desktop_output->views); + zfree(desktop_output); + } +} + +static bool +get_scale(double *scale, struct output *output, struct wl_list *desktop_outputs) +{ + struct desktop_output *desktop_output; + wl_list_for_each(desktop_output, desktop_outputs, link) { + if (desktop_output->output == output) { + *scale = desktop_output->scale; + return true; + } + } + assert(false && "output list mismatch"); + return false; +} + +static void +fit_window_within_usable_area(struct view *view) +{ + struct wlr_box usable = output_usable_area_in_layout_coords(view->output); + struct border margin = ssd_get_margin(view->ssd); + + /* + * This is quite a simple approach. + * + * We prioritise keeping windows big because with a scale increase, it + * is likely that users will want that. As such, we first move the + * window to the top/left corner and then shrink it to the usable area + * if needed. + */ + view_move(view, usable.x + margin.left, usable.y + margin.top); + view_constrain_size_to_that_of_usable_area(view); +} + +void +desktop_state_adjust_on_output_scale_change(struct server *server, + struct wl_list *desktop_outputs) +{ + struct desktop_output *desktop_output; + wl_list_for_each(desktop_output, desktop_outputs, link) { + struct view **view; + wl_array_for_each(view, &desktop_output->views) { + struct output *output = desktop_output->output; + double old_scale = 0.0; + if (!get_scale(&old_scale, output, desktop_outputs)) { + return; + } + /* Put window back on the pre-scale-change output */ + if ((*view)->output != output) { + view_set_output(*view, output); + } + /* Adjust window position and size */ + if (old_scale != (*view)->output->wlr_output->scale) { + fit_window_within_usable_area(*view); + } + } + } +} diff --git a/src/meson.build b/src/meson.build index 330b5daf..d951c97d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -3,6 +3,7 @@ labwc_sources = files( 'buffer.c', 'debug.c', 'desktop.c', + 'desktop-state.c', 'dnd.c', 'edges.c', 'idle.c', diff --git a/src/output.c b/src/output.c index f74a5697..736e374c 100644 --- a/src/output.c +++ b/src/output.c @@ -26,6 +26,7 @@ #include "common/mem.h" #include "common/scene-helpers.h" #include "config/rcxml.h" +#include "desktop-state.h" #include "labwc.h" #include "layers.h" #include "node.h" @@ -791,6 +792,14 @@ handle_output_manager_apply(struct wl_listener *listener, void *data) bool config_is_good = verify_output_config_v1(config); + /* + * Save old desktop state, specifically windows and scales for each + * output, so that window positions and sizes can be adjusted later. + */ + struct wl_list desktop_outputs; + wl_list_init(&desktop_outputs); + desktop_state_create(server, &desktop_outputs); + if (config_is_good && output_config_apply(server, config)) { wlr_output_configuration_v1_send_succeeded(config); } else { @@ -803,6 +812,10 @@ handle_output_manager_apply(struct wl_listener *listener, void *data) output->wlr_output->scale); } + /* Adjust windows if output scale(s) changed */ + desktop_state_adjust_on_output_scale_change(server, &desktop_outputs); + desktop_state_destroy(&desktop_outputs); + /* Re-set cursor image in case scale changed */ cursor_update_focus(server); cursor_update_image(&server->seat);