desktop: keep windows within output on scale change

Fixes: #3195
This commit is contained in:
Johan Malm 2025-11-16 19:08:21 +00:00
parent 8ced055cb9
commit e4dca57c96
4 changed files with 150 additions and 0 deletions

31
include/desktop-state.h Normal file
View file

@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_DESKTOP_STATE_H
#define LABWC_DESKTOP_STATE_H
#include <wayland-util.h>
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 */

105
src/desktop-state.c Normal file
View file

@ -0,0 +1,105 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _POSIX_C_SOURCE 200809L
#include "desktop-state.h"
#include <assert.h>
#include <wlr/types/wlr_output.h>
#include <wlr/util/log.h>
#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);
}
}
}
}

View file

@ -3,6 +3,7 @@ labwc_sources = files(
'buffer.c',
'debug.c',
'desktop.c',
'desktop-state.c',
'dnd.c',
'edges.c',
'idle.c',

View file

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