diff --git a/include/labwc.h b/include/labwc.h index a19f8243..9a38b677 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -486,6 +486,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); diff --git a/include/view.h b/include/view.h index b4f90e6a..637b00df 100644 --- a/include/view.h +++ b/include/view.h @@ -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); /** diff --git a/src/interactive.c b/src/interactive.c index 813289aa..ae5a6972 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -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); diff --git a/src/view.c b/src/view.c index 1646decd..5ddceead 100644 --- a/src/view.c +++ b/src/view.c @@ -815,8 +815,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; @@ -839,18 +839,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 @@ -947,8 +943,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); } @@ -1246,6 +1245,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); diff --git a/src/xdg.c b/src/xdg.c index 86ee06c3..6f10cfa6 100644 --- a/src/xdg.c +++ b/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) { @@ -84,6 +104,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 @@ -107,8 +141,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) { @@ -320,7 +355,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 @@ -563,11 +606,12 @@ xdg_toplevel_view_map(struct view *view) view->surface = xdg_surface->surface; 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 { @@ -575,8 +619,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)) { @@ -588,22 +631,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 @@ -811,6 +843,31 @@ xdg_surface_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 diff --git a/src/xwayland.c b/src/xwayland.c index d4eff83d..b27e741b 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -554,6 +554,21 @@ handle_set_strut_partial(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) @@ -691,6 +706,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