mirror of
https://github.com/labwc/labwc.git
synced 2025-10-29 05:40:24 -04:00
xdg: handle initially maximized xdg-shell views better
Currently, initially maximized (or fullscreen) xdg-shell views exhibit one of two issues: - some (e.g. GTK and Qt apps) paint an initial frame un-maximized (before the "map" event) and only maximize in a later commit - others (e.g. foot) maximize immediately without flicker, but never store a valid natural size, so we end up using a fallback (640x480) Under KWin, neither of these issues occur, so I looked into what labwc is doing wrong. It seems that: - wlroots internally sends an initial configure event with a size of 0x0 to all xdg-shell views. This requests the client to set its own preferred (a.k.a. natural) size. - For an initially maximized/fullscreen view, the initial configure event should contain the maximized/fullscreen size rather than 0x0. In labwc, this means we have to call wlr_xdg_toplevel_set_size() earlier, i.e. from the new_surface event. Tracing with WAYLAND_DEBUG shows that the initial configure event now has the correct geometry, matching KWin behavior. With this change, GTK and Qt apps no longer paint an incorrect un-maximized frame. - However, this means that all xdg-shell views now suffer from the same issue as foot, where we never receive a commit with the un-maximized (natural) geometry. The correct way to get the natural geometry seems to be to wait until we want to un-maximize, and send a configure event of 0x0 at that point. Sending a configure event of 0x0 when un-maximizing is a bit annoying as it breaks some assumptions in labwc code. In particular: - view->natural_geometry may now be unknown (0x0), requiring various wlr_box_empty() checks sprinkled around. I added these in all the obvious places, but there could be some code paths that I missed. - Positioning the newly un-maximized view within view_maximize() no longer works since we don't know the natural size. Instead we have to run the positioning logic from the surface commit handler. This results in some extra complexity, especially for interactive move. See the new do_late_positioning() function in xdg.c. Some TODOs/FIXMEs (non-blocking in my opinion): - The view_wants_decorations() check is now duplicated in both the new_surface and map event handlers. I'm not sure if this is necessary but it seemed like the safest approach for now. More testing would be nice, particularly with various combinations of config and client SSD preferences. - Aside from the interactive move case, the "late positioning" logic always centers the view when un-maximizing, and does not invoke any of the smart placement logic. If we want to invoke smart placement here, I'd appreciate someone with more knowledge of that code to take a look and figure out how to do that correctly.
This commit is contained in:
parent
e26ec472b2
commit
3be8fe25f3
6 changed files with 148 additions and 41 deletions
|
|
@ -484,6 +484,15 @@ void seat_set_pressed(struct seat *seat, struct view *view,
|
|||
void seat_reset_pressed(struct seat *seat);
|
||||
void seat_output_layout_changed(struct seat *seat);
|
||||
|
||||
/**
|
||||
* interactive_anchor_to_cursor() - repositions the view to remain
|
||||
* underneath the cursor when its size changes during interactive move.
|
||||
*
|
||||
* geometry->{width,height} are provided by the caller.
|
||||
* geometry->{x,y} are computed by this function.
|
||||
*/
|
||||
void interactive_anchor_to_cursor(struct view *view, struct wlr_box *geometry);
|
||||
|
||||
void interactive_begin(struct view *view, enum input_mode mode, uint32_t edges);
|
||||
void interactive_finish(struct view *view);
|
||||
void interactive_cancel(struct view *view);
|
||||
|
|
|
|||
|
|
@ -451,6 +451,7 @@ void view_moved(struct view *view);
|
|||
void view_minimize(struct view *view, bool minimized);
|
||||
bool view_compute_centered_position(struct view *view,
|
||||
const struct wlr_box *ref, int w, int h, int *x, int *y);
|
||||
void view_set_fallback_natural_geometry(struct view *view);
|
||||
void view_store_natural_geometry(struct view *view);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -21,6 +21,16 @@ max_move_scale(double pos_cursor, double pos_current,
|
|||
return pos_new;
|
||||
}
|
||||
|
||||
void
|
||||
interactive_anchor_to_cursor(struct view *view, struct wlr_box *geometry)
|
||||
{
|
||||
struct wlr_cursor *cursor = view->server->seat.cursor;
|
||||
geometry->x = max_move_scale(cursor->x, view->current.x,
|
||||
view->current.width, geometry->width);
|
||||
geometry->y = max_move_scale(cursor->y, view->current.y,
|
||||
view->current.height, geometry->height);
|
||||
}
|
||||
|
||||
void
|
||||
interactive_begin(struct view *view, enum input_mode mode, uint32_t edges)
|
||||
{
|
||||
|
|
@ -61,14 +71,17 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges)
|
|||
* width/height.
|
||||
* Don't reset tiled state yet since we may want
|
||||
* to keep it (in the snap-to-maximize case).
|
||||
*
|
||||
* If the natural geometry is unknown (possible
|
||||
* with xdg-shell views), then we set a size of
|
||||
* 0x0 here and determine the correct geometry
|
||||
* later. See do_late_positioning() in xdg.c.
|
||||
*/
|
||||
geometry = view->natural_geometry;
|
||||
geometry.x = max_move_scale(seat->cursor->x,
|
||||
view->current.x, view->current.width,
|
||||
geometry.width);
|
||||
geometry.y = max_move_scale(seat->cursor->y,
|
||||
view->current.y, view->current.height,
|
||||
geometry.height);
|
||||
geometry.width = view->natural_geometry.width;
|
||||
geometry.height = view->natural_geometry.height;
|
||||
if (!wlr_box_empty(&geometry)) {
|
||||
interactive_anchor_to_cursor(view, &geometry);
|
||||
}
|
||||
|
||||
view_set_shade(view, false);
|
||||
view_set_untiled(view);
|
||||
|
|
|
|||
41
src/view.c
41
src/view.c
|
|
@ -816,8 +816,8 @@ adjust_floating_geometry(struct view *view, struct wlr_box *geometry,
|
|||
&geometry->x, &geometry->y);
|
||||
}
|
||||
|
||||
static void
|
||||
set_fallback_geometry(struct view *view)
|
||||
void
|
||||
view_set_fallback_natural_geometry(struct view *view)
|
||||
{
|
||||
view->natural_geometry.width = LAB_FALLBACK_WIDTH;
|
||||
view->natural_geometry.height = LAB_FALLBACK_HEIGHT;
|
||||
|
|
@ -840,18 +840,14 @@ view_store_natural_geometry(struct view *view)
|
|||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If an application was started maximized or fullscreened, its
|
||||
* natural_geometry width/height may still be zero (or very small
|
||||
* values) in which case we set some fallback values. This is the case
|
||||
* with foot and some Qt/Tk applications.
|
||||
/*
|
||||
* Note that for xdg-shell views that start fullscreen or maximized,
|
||||
* we end up storing a natural geometry of 0x0. This is intentional.
|
||||
* When leaving fullscreen or unmaximizing, we pass 0x0 to the
|
||||
* xdg-toplevel configure event, which means the application should
|
||||
* choose its own size.
|
||||
*/
|
||||
if (view->pending.width < LAB_MIN_VIEW_WIDTH
|
||||
|| view->pending.height < LAB_MIN_VIEW_HEIGHT) {
|
||||
set_fallback_geometry(view);
|
||||
} else {
|
||||
view->natural_geometry = view->pending;
|
||||
}
|
||||
view->natural_geometry = view->pending;
|
||||
}
|
||||
|
||||
int
|
||||
|
|
@ -948,8 +944,11 @@ view_apply_natural_geometry(struct view *view)
|
|||
assert(view_is_floating(view));
|
||||
|
||||
struct wlr_box geometry = view->natural_geometry;
|
||||
adjust_floating_geometry(view, &geometry,
|
||||
/* midpoint_visibility */ false);
|
||||
/* Only adjust natural geometry if known (not 0x0) */
|
||||
if (!wlr_box_empty(&geometry)) {
|
||||
adjust_floating_geometry(view, &geometry,
|
||||
/* midpoint_visibility */ false);
|
||||
}
|
||||
view_move_resize(view, geometry);
|
||||
}
|
||||
|
||||
|
|
@ -1247,6 +1246,18 @@ view_maximize(struct view *view, enum view_axis axis,
|
|||
view_invalidate_last_layout_geometry(view);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* When natural geometry is unknown (0x0) for an xdg-shell view,
|
||||
* we normally send a configure event of 0x0 to get the client's
|
||||
* preferred size, but this doesn't work if unmaximizing only
|
||||
* one axis. So in that corner case, set a fallback geometry.
|
||||
*/
|
||||
if ((axis == VIEW_AXIS_HORIZONTAL || axis == VIEW_AXIS_VERTICAL)
|
||||
&& wlr_box_empty(&view->natural_geometry)) {
|
||||
view_set_fallback_natural_geometry(view);
|
||||
}
|
||||
|
||||
set_maximized(view, axis);
|
||||
if (view_is_floating(view)) {
|
||||
view_apply_natural_geometry(view);
|
||||
|
|
|
|||
95
src/xdg.c
95
src/xdg.c
|
|
@ -75,6 +75,26 @@ handle_new_popup(struct wl_listener *listener, void *data)
|
|||
xdg_popup_create(view, wlr_popup);
|
||||
}
|
||||
|
||||
static void
|
||||
do_late_positioning(struct view *view)
|
||||
{
|
||||
struct server *server = view->server;
|
||||
if (server->input_mode == LAB_INPUT_STATE_MOVE
|
||||
&& view == server->grabbed_view) {
|
||||
/* Keep view underneath cursor */
|
||||
interactive_anchor_to_cursor(view, &view->pending);
|
||||
/* Update grab offsets */
|
||||
server->grab_x = server->seat.cursor->x;
|
||||
server->grab_y = server->seat.cursor->y;
|
||||
server->grab_box = view->pending;
|
||||
} else {
|
||||
/* TODO: smart placement? */
|
||||
view_compute_centered_position(view, NULL,
|
||||
view->pending.width, view->pending.height,
|
||||
&view->pending.x, &view->pending.y);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
handle_commit(struct wl_listener *listener, void *data)
|
||||
{
|
||||
|
|
@ -90,6 +110,20 @@ handle_commit(struct wl_listener *listener, void *data)
|
|||
|
||||
struct wlr_box size;
|
||||
wlr_xdg_surface_get_geometry(xdg_surface, &size);
|
||||
bool update_required = false;
|
||||
|
||||
/*
|
||||
* If we didn't know the natural size when leaving fullscreen or
|
||||
* unmaximizing, then the pending size will be 0x0. In this case,
|
||||
* the pending x/y is also unset and we still need to position
|
||||
* the window.
|
||||
*/
|
||||
if (wlr_box_empty(&view->pending)) {
|
||||
view->pending.width = size.width;
|
||||
view->pending.height = size.height;
|
||||
do_late_positioning(view);
|
||||
update_required = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Qt applications occasionally fail to call set_window_geometry
|
||||
|
|
@ -113,8 +147,9 @@ handle_commit(struct wl_listener *listener, void *data)
|
|||
}
|
||||
|
||||
struct wlr_box *current = &view->current;
|
||||
bool update_required = current->width != size.width
|
||||
|| current->height != size.height;
|
||||
if (current->width != size.width || current->height != size.height) {
|
||||
update_required = true;
|
||||
}
|
||||
|
||||
uint32_t serial = view->pending_configure_serial;
|
||||
if (serial > 0 && serial == xdg_surface->current.configure_serial) {
|
||||
|
|
@ -327,7 +362,15 @@ static void
|
|||
xdg_toplevel_view_configure(struct view *view, struct wlr_box geo)
|
||||
{
|
||||
uint32_t serial = 0;
|
||||
view_adjust_size(view, &geo.width, &geo.height);
|
||||
|
||||
/*
|
||||
* Leave a size of 0x0 unchanged; this has special meaning in
|
||||
* an xdg-toplevel configure event and requests the application
|
||||
* to choose its own preferred size.
|
||||
*/
|
||||
if (!wlr_box_empty(&geo)) {
|
||||
view_adjust_size(view, &geo.width, &geo.height);
|
||||
}
|
||||
|
||||
/*
|
||||
* We do not need to send a configure request unless the size
|
||||
|
|
@ -569,11 +612,12 @@ xdg_toplevel_view_map(struct view *view)
|
|||
struct wlr_xdg_surface *xdg_surface = xdg_surface_from_view(view);
|
||||
wlr_scene_node_set_enabled(&view->scene_tree->node, true);
|
||||
if (!view->been_mapped) {
|
||||
struct wlr_xdg_toplevel_requested *requested =
|
||||
&xdg_toplevel_from_view(view)->requested;
|
||||
|
||||
init_foreign_toplevel(view);
|
||||
|
||||
/*
|
||||
* FIXME: is this needed or is the earlier logic in
|
||||
* xdg_surface_new() enough?
|
||||
*/
|
||||
if (view_wants_decorations(view)) {
|
||||
view_set_ssd_mode(view, LAB_SSD_MODE_FULL);
|
||||
} else {
|
||||
|
|
@ -581,8 +625,7 @@ xdg_toplevel_view_map(struct view *view)
|
|||
}
|
||||
|
||||
/*
|
||||
* Set initial "pending" dimensions (may be modified by
|
||||
* view_set_fullscreen/view_maximize() below). "Current"
|
||||
* Set initial "pending" dimensions. "Current"
|
||||
* dimensions remain zero until handle_commit().
|
||||
*/
|
||||
if (wlr_box_empty(&view->pending)) {
|
||||
|
|
@ -594,22 +637,11 @@ xdg_toplevel_view_map(struct view *view)
|
|||
|
||||
/*
|
||||
* Set initial "pending" position for floating views.
|
||||
* Do this before view_set_fullscreen/view_maximize() so
|
||||
* that the position is saved with the natural geometry.
|
||||
*
|
||||
* FIXME: the natural geometry is not saved if either
|
||||
* handle_request_fullscreen/handle_request_maximize()
|
||||
* is called before map (try "foot --maximized").
|
||||
*/
|
||||
if (view_is_floating(view)) {
|
||||
set_initial_position(view);
|
||||
}
|
||||
|
||||
set_fullscreen_from_request(view, requested);
|
||||
view_maximize(view, requested->maximized ?
|
||||
VIEW_AXIS_BOTH : VIEW_AXIS_NONE,
|
||||
/*store_natural_geometry*/ true);
|
||||
|
||||
/*
|
||||
* Set initial "current" position directly before
|
||||
* calling view_moved() to reduce flicker
|
||||
|
|
@ -838,6 +870,31 @@ xdg_toplevel_new(struct wl_listener *listener, void *data)
|
|||
CONNECT_SIGNAL(xdg_surface, xdg_toplevel_view, new_popup);
|
||||
|
||||
wl_list_insert(&server->views, &view->link);
|
||||
|
||||
/* FIXME: is view_wants_decorations() reliable this early? */
|
||||
if (view_wants_decorations(view)) {
|
||||
view_set_ssd_mode(view, LAB_SSD_MODE_FULL);
|
||||
} else {
|
||||
view_set_ssd_mode(view, LAB_SSD_MODE_NONE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle initial fullscreen/maximize requests. This needs to be
|
||||
* done early (before map) in order to send the correct size to
|
||||
* the initial configure event and avoid flicker.
|
||||
*
|
||||
* Note that at this point, wlroots has already scheduled (but
|
||||
* not yet sent) the initial configure event with a size of 0x0.
|
||||
* In normal (non-fullscreen/maximized) cases, the zero size
|
||||
* requests the application to choose its own size.
|
||||
*/
|
||||
if (toplevel->requested.fullscreen) {
|
||||
set_fullscreen_from_request(view, &toplevel->requested);
|
||||
}
|
||||
if (toplevel->requested.maximized) {
|
||||
view_maximize(view, VIEW_AXIS_BOTH,
|
||||
/*store_natural_geometry*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -613,6 +613,21 @@ handle_map_request(struct wl_listener *listener, void *data)
|
|||
*/
|
||||
}
|
||||
|
||||
static void
|
||||
check_natural_geometry(struct view *view)
|
||||
{
|
||||
/*
|
||||
* Some applications (example: Thonny) don't set a reasonable
|
||||
* un-maximized size when started maximized. Try to detect this
|
||||
* and set a fallback size.
|
||||
*/
|
||||
if (!view_is_floating(view)
|
||||
&& (view->natural_geometry.width < LAB_MIN_VIEW_WIDTH
|
||||
|| view->natural_geometry.height < LAB_MIN_VIEW_HEIGHT)) {
|
||||
view_set_fallback_natural_geometry(view);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
set_initial_position(struct view *view,
|
||||
struct wlr_xwayland_surface *xwayland_surface)
|
||||
|
|
@ -731,6 +746,7 @@ xwayland_view_map(struct view *view)
|
|||
}
|
||||
|
||||
if (!view->been_mapped) {
|
||||
check_natural_geometry(view);
|
||||
set_initial_position(view, xwayland_surface);
|
||||
/*
|
||||
* When mapping the view for the first time, visual
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue