window: Implement asynchronous popup moving

This commits adds support on the MetaWindow and constraints engine side
for asynchronously repositioning a window with a placement rule, either
due to environmental changes (e.g. parent moved) or explicitly done so
via `meta_window_update_placement_rule()`.

This is so far unused, as placement rules where this functionality is
triggered are not yet constructed by the xdg-shell implementation, and
no users of `meta_window_update_placement_rule()` exists yet.

To summarize, it works by making it possible to produce placement rules
with the parent rectangle a window should be placed against, while
creating a pending configuration that is not applied until acknowledged
by the client using the xdg-shell configure/ack_configure mechanisms.

An "temporary" constrain result is added to deal with situations
where the client window *must* move immediately even though it has not yet
acknowledged a new configuration that was sent. This happens for example
when the parent window is moved, causing the popup window to change its
relative position e.g. because it ended up partially off-screen. In this
situation, the temporary position corresponds to the result of the
movement of the parent, while the pending (asynchronously configured)
position is the relative one given the new constraining result.

https://gitlab.gnome.org/GNOME/mutter/merge_requests/705
This commit is contained in:
Jonas Ådahl 2020-02-14 10:41:38 +01:00 committed by Carlos Garnacho
parent 6c82feb1b8
commit d08a8de265
8 changed files with 277 additions and 91 deletions

View File

@ -123,6 +123,7 @@ typedef struct
{
MetaRectangle orig;
MetaRectangle current;
MetaRectangle temporary;
int rel_x;
int rel_y;
ActionType action_type;
@ -147,6 +148,8 @@ typedef struct
*/
GList *usable_screen_region;
GList *usable_monitor_region;
MetaMoveResizeFlags flags;
} ConstraintInfo;
static gboolean do_screen_and_monitor_relative_constraints (MetaWindow *window,
@ -284,6 +287,7 @@ meta_window_constrain (MetaWindow *window,
MetaGravity resize_gravity,
const MetaRectangle *orig,
MetaRectangle *new,
MetaRectangle *temporary,
int *rel_x,
int *rel_y)
{
@ -322,6 +326,7 @@ meta_window_constrain (MetaWindow *window,
/* Make sure we use the constrained position */
*new = info.current;
*temporary = info.temporary;
*rel_x = info.rel_x;
*rel_y = info.rel_y;
@ -348,8 +353,10 @@ setup_constraint_info (ConstraintInfo *info,
info->orig = *orig;
info->current = *new;
info->temporary = *orig;
info->rel_x = 0;
info->rel_y = 0;
info->flags = flags;
if (info->current.width < 1)
info->current.width = 1;
@ -500,16 +507,11 @@ place_window_if_needed(MetaWindow *window,
if (window->placement.rule)
{
MetaWindow *parent = meta_window_get_transient_for (window);
MetaRectangle parent_rect;
meta_window_process_placement (window,
window->placement.rule,
&info->rel_x, &info->rel_y);
meta_window_get_frame_rect (parent, &parent_rect);
placed_rect.x = parent_rect.x + info->rel_x;
placed_rect.y = parent_rect.y + info->rel_y;
placed_rect.x = window->placement.rule->parent_rect.x + info->rel_x;
placed_rect.y = window->placement.rule->parent_rect.y + info->rel_y;
}
else
{
@ -825,12 +827,13 @@ constrain_custom_rule (MetaWindow *window,
MetaPlacementRule *placement_rule;
MetaRectangle intersection;
gboolean constraint_satisfied;
MetaRectangle temporary_rect;
MetaRectangle adjusted_unconstrained;
int adjusted_rel_x;
int adjusted_rel_y;
MetaPlacementRule current_rule;
MetaWindow *parent;
MetaRectangle parent_rect;
int parent_x, parent_y;
if (priority > PRIORITY_CUSTOM_RULE)
return TRUE;
@ -839,25 +842,72 @@ constrain_custom_rule (MetaWindow *window,
if (!placement_rule)
return TRUE;
adjusted_unconstrained = info->current;
parent = meta_window_get_transient_for (window);
meta_window_get_frame_rect (parent, &parent_rect);
if (window->placement.state == META_PLACEMENT_STATE_CONSTRAINED_FINISHED)
{
placement_rule->parent_rect.x = parent->rect.x;
placement_rule->parent_rect.y = parent->rect.y;
}
parent_x = placement_rule->parent_rect.x;
parent_y = placement_rule->parent_rect.y;
/*
* Calculate the temporary position, meaning a position that will be
* applied if the new constrained position requires asynchronous
* configuration of the window. This happens for example when the parent
* moves, causing this window to change relative position, meaning it can
* only have its newly constrained position applied when the configuration is
* acknowledged.
*/
switch (window->placement.state)
{
case META_PLACEMENT_STATE_UNCONSTRAINED:
adjusted_rel_x = window->rect.x - parent->rect.x;
adjusted_rel_y = window->rect.y - parent->rect.y;
temporary_rect = info->current;
break;
case META_PLACEMENT_STATE_CONSTRAINED:
adjusted_unconstrained.x =
parent->rect.x + window->placement.current.rel_x;
adjusted_unconstrained.y =
parent->rect.y + window->placement.current.rel_y;
case META_PLACEMENT_STATE_CONSTRAINED_CONFIGURED:
case META_PLACEMENT_STATE_CONSTRAINED_PENDING:
case META_PLACEMENT_STATE_CONSTRAINED_FINISHED:
case META_PLACEMENT_STATE_INVALIDATED:
temporary_rect = (MetaRectangle) {
.x = parent->rect.x + window->placement.current.rel_x,
.y = parent->rect.y + window->placement.current.rel_y,
.width = info->current.width,
.height = info->current.height,
};
break;
}
/*
* Calculate an adjusted current position. Depending on the rule
* configuration and placement state, this may result in window being
* reconstrained.
*/
adjusted_unconstrained = temporary_rect;
if (window->placement.state == META_PLACEMENT_STATE_INVALIDATED ||
window->placement.state == META_PLACEMENT_STATE_UNCONSTRAINED ||
(window->placement.state == META_PLACEMENT_STATE_CONSTRAINED_FINISHED &&
placement_rule->is_reactive))
{
meta_window_process_placement (window, placement_rule,
&adjusted_rel_x,
&adjusted_rel_y);
adjusted_unconstrained.x = parent_x + adjusted_rel_x;
adjusted_unconstrained.y = parent_y + adjusted_rel_y;
}
else if (window->placement.state == META_PLACEMENT_STATE_CONSTRAINED_PENDING)
{
adjusted_rel_x = window->placement.pending.rel_x;
adjusted_rel_y = window->placement.pending.rel_y;
adjusted_unconstrained.x = window->placement.pending.x;
adjusted_unconstrained.y = window->placement.pending.y;
}
else
{
adjusted_rel_x = window->placement.current.rel_x;
adjusted_rel_y = window->placement.current.rel_y;
break;
}
meta_rectangle_intersect (&adjusted_unconstrained, &info->work_area_monitor,
@ -872,30 +922,43 @@ constrain_custom_rule (MetaWindow *window,
if (check_only)
return constraint_satisfied;
current_rule = *placement_rule;
switch (window->placement.state)
{
case META_PLACEMENT_STATE_CONSTRAINED:
info->current = adjusted_unconstrained;
info->rel_x = adjusted_rel_x;
info->rel_y = adjusted_rel_y;
goto done;
info->temporary = temporary_rect;
switch (window->placement.state)
{
case META_PLACEMENT_STATE_CONSTRAINED_FINISHED:
if (!placement_rule->is_reactive)
return TRUE;
break;
case META_PLACEMENT_STATE_CONSTRAINED_PENDING:
case META_PLACEMENT_STATE_CONSTRAINED_CONFIGURED:
return TRUE;
case META_PLACEMENT_STATE_UNCONSTRAINED:
case META_PLACEMENT_STATE_INVALIDATED:
break;
}
if (constraint_satisfied)
goto done;
/*
* Process the placement rule in order either until constraints are
* satisfied, or there are no more rules to process.
*/
current_rule = *placement_rule;
if (info->current.width != intersection.width &&
(current_rule.constraint_adjustment &
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X))
{
try_flip_window_position (window, info, &current_rule,
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X,
parent_rect.x,
parent_rect.y,
parent_x,
parent_y,
&info->current,
&info->rel_x,
&info->rel_y,
@ -907,8 +970,8 @@ constrain_custom_rule (MetaWindow *window,
{
try_flip_window_position (window, info, &current_rule,
META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y,
parent_rect.x,
parent_rect.y,
parent_x,
parent_y,
&info->current,
&info->rel_x,
&info->rel_y,
@ -1010,10 +1073,12 @@ constrain_custom_rule (MetaWindow *window,
}
done:
window->placement.state = META_PLACEMENT_STATE_CONSTRAINED;
window->placement.state = META_PLACEMENT_STATE_CONSTRAINED_PENDING;
window->placement.current.rel_x = info->rel_x;
window->placement.current.rel_y = info->rel_y;
window->placement.pending.rel_x = info->rel_x;
window->placement.pending.rel_y = info->rel_y;
window->placement.pending.x = info->current.x;
window->placement.pending.y = info->current.y;
return TRUE;
}

View File

@ -32,6 +32,7 @@ void meta_window_constrain (MetaWindow *window,
MetaGravity resize_gravity,
const MetaRectangle *orig,
MetaRectangle *new,
MetaRectangle *intermediate,
int *rel_x,
int *rel_y);

View File

@ -85,6 +85,7 @@ typedef enum
META_MOVE_RESIZE_FORCE_MOVE = 1 << 8,
META_MOVE_RESIZE_WAYLAND_STATE_CHANGED = 1 << 9,
META_MOVE_RESIZE_FORCE_UPDATE_MONITOR = 1 << 10,
META_MOVE_RESIZE_PLACEMENT_CHANGED = 1 << 11,
} MetaMoveResizeFlags;
typedef enum
@ -141,12 +142,19 @@ typedef struct _MetaPlacementRule
int offset_y;
int width;
int height;
gboolean is_reactive;
MetaRectangle parent_rect;
} MetaPlacementRule;
typedef enum _MetaPlacementState
{
META_PLACEMENT_STATE_UNCONSTRAINED,
META_PLACEMENT_STATE_CONSTRAINED,
META_PLACEMENT_STATE_CONSTRAINED_PENDING,
META_PLACEMENT_STATE_CONSTRAINED_CONFIGURED,
META_PLACEMENT_STATE_CONSTRAINED_FINISHED,
META_PLACEMENT_STATE_INVALIDATED,
} MetaPlacementState;
typedef enum
@ -538,6 +546,13 @@ struct _MetaWindow
MetaPlacementRule *rule;
MetaPlacementState state;
struct {
int x;
int y;
int rel_x;
int rel_y;
} pending;
struct {
int rel_x;
int rel_y;
@ -569,6 +584,7 @@ struct _MetaWindowClass
MetaGravity gravity,
MetaRectangle unconstrained_rect,
MetaRectangle constrained_rect,
MetaRectangle temporary_rect,
int rel_x,
int rel_y,
MetaMoveResizeFlags flags,

View File

@ -4001,6 +4001,7 @@ meta_window_move_resize_internal (MetaWindow *window,
gboolean did_placement;
MetaRectangle unconstrained_rect;
MetaRectangle constrained_rect;
MetaRectangle temporary_rect;
int rel_x = 0;
int rel_y = 0;
MetaMoveResizeResultFlags result = 0;
@ -4057,6 +4058,7 @@ meta_window_move_resize_internal (MetaWindow *window,
g_assert_not_reached ();
constrained_rect = unconstrained_rect;
temporary_rect = window->rect;
if (flags & (META_MOVE_RESIZE_MOVE_ACTION | META_MOVE_RESIZE_RESIZE_ACTION) &&
!(flags & META_MOVE_RESIZE_WAYLAND_FINISH_MOVE_RESIZE) &&
window->monitor)
@ -4069,13 +4071,14 @@ meta_window_move_resize_internal (MetaWindow *window,
gravity,
&old_rect,
&constrained_rect,
&temporary_rect,
&rel_x,
&rel_y);
}
else if (window->placement.rule)
{
rel_x = window->placement.current.rel_x;
rel_y = window->placement.current.rel_y;
rel_x = window->placement.pending.rel_x;
rel_y = window->placement.pending.rel_y;
}
/* If we did placement, then we need to save the position that the window
@ -4093,6 +4096,7 @@ meta_window_move_resize_internal (MetaWindow *window,
gravity,
unconstrained_rect,
constrained_rect,
temporary_rect,
rel_x,
rel_y,
flags, &result);

View File

@ -138,7 +138,8 @@ G_DEFINE_TYPE_WITH_CODE (MetaWaylandXdgPopup,
popup_surface_iface_init));
static MetaPlacementRule
meta_wayland_xdg_positioner_to_placement (MetaWaylandXdgPositioner *xdg_positioner);
meta_wayland_xdg_positioner_to_placement (MetaWaylandXdgPositioner *xdg_positioner,
MetaWindow *parent_window);
static struct wl_resource *
meta_wayland_xdg_surface_get_wm_base_resource (MetaWaylandXdgSurface *xdg_surface);
@ -1745,6 +1746,7 @@ xdg_surface_constructor_get_popup (struct wl_client *client,
struct wl_resource *xdg_surface_resource = constructor->resource;
MetaWaylandSurface *parent_surface =
surface_from_xdg_surface_resource (parent_resource);
MetaWindow *parent_window;
MetaWaylandXdgPositioner *xdg_positioner;
MetaWaylandXdgPopup *xdg_popup;
MetaWaylandXdgSurface *xdg_surface;
@ -1783,9 +1785,11 @@ xdg_surface_constructor_get_popup (struct wl_client *client,
xdg_surface = META_WAYLAND_XDG_SURFACE (xdg_popup);
meta_wayland_xdg_surface_constructor_finalize (constructor, xdg_surface);
parent_window = meta_wayland_surface_get_window (parent_surface);
xdg_positioner = wl_resource_get_user_data (positioner_resource);
xdg_popup->setup.placement_rule =
meta_wayland_xdg_positioner_to_placement (xdg_positioner);
meta_wayland_xdg_positioner_to_placement (xdg_positioner, parent_window);
xdg_popup->setup.parent_surface = parent_surface;
}
@ -1892,8 +1896,13 @@ positioner_gravity_to_placement_gravity (uint32_t gravity)
}
static MetaPlacementRule
meta_wayland_xdg_positioner_to_placement (MetaWaylandXdgPositioner *xdg_positioner)
meta_wayland_xdg_positioner_to_placement (MetaWaylandXdgPositioner *xdg_positioner,
MetaWindow *parent_window)
{
MetaRectangle parent_rect;
meta_window_get_frame_rect (parent_window, &parent_rect);
return (MetaPlacementRule) {
.anchor_rect = xdg_positioner->anchor_rect,
.gravity = positioner_gravity_to_placement_gravity (xdg_positioner->gravity),
@ -1903,6 +1912,8 @@ meta_wayland_xdg_positioner_to_placement (MetaWaylandXdgPositioner *xdg_position
.offset_y = xdg_positioner->offset_y,
.width = xdg_positioner->width,
.height = xdg_positioner->height,
.parent_rect = parent_rect,
};
}

View File

@ -222,18 +222,23 @@ meta_window_wayland_move_resize_internal (MetaWindow *window,
MetaGravity gravity,
MetaRectangle unconstrained_rect,
MetaRectangle constrained_rect,
MetaRectangle temporary_rect,
int rel_x,
int rel_y,
MetaMoveResizeFlags flags,
MetaMoveResizeResultFlags *result)
{
MetaWindowWayland *wl_window = META_WINDOW_WAYLAND (window);
gboolean can_move_now;
gboolean can_move_now = FALSE;
int configured_x;
int configured_y;
int configured_width;
int configured_height;
int geometry_scale;
int new_x;
int new_y;
int new_buffer_x;
int new_buffer_y;
g_assert (window->frame == NULL);
@ -300,14 +305,61 @@ meta_window_wayland_move_resize_internal (MetaWindow *window,
/* This is a commit of an attach. We should move the window to match the
* new position the client wants. */
can_move_now = TRUE;
if (window->placement.state == META_PLACEMENT_STATE_CONSTRAINED_CONFIGURED)
window->placement.state = META_PLACEMENT_STATE_CONSTRAINED_FINISHED;
}
else
{
/* If the size changed, or the state changed, then we have to wait until
* the client acks our configure before moving the window. */
if (constrained_rect.width != window->rect.width ||
if (window->placement.rule)
{
switch (window->placement.state)
{
case META_PLACEMENT_STATE_UNCONSTRAINED:
case META_PLACEMENT_STATE_CONSTRAINED_CONFIGURED:
case META_PLACEMENT_STATE_INVALIDATED:
can_move_now = FALSE;
break;
case META_PLACEMENT_STATE_CONSTRAINED_PENDING:
{
if (flags & META_MOVE_RESIZE_PLACEMENT_CHANGED ||
rel_x != wl_window->last_sent_rel_x ||
rel_y != wl_window->last_sent_rel_y ||
constrained_rect.width != window->rect.width ||
constrained_rect.height != window->rect.height)
{
MetaWaylandWindowConfiguration *configuration;
configuration =
meta_wayland_window_configuration_new_relative (rel_x,
rel_y,
configured_width,
configured_height);
meta_window_wayland_configure (wl_window, configuration);
wl_window->last_sent_rel_x = rel_x;
wl_window->last_sent_rel_y = rel_y;
window->placement.state = META_PLACEMENT_STATE_CONSTRAINED_CONFIGURED;
can_move_now = FALSE;
}
else
{
window->placement.state =
META_PLACEMENT_STATE_CONSTRAINED_FINISHED;
can_move_now = TRUE;
}
break;
}
case META_PLACEMENT_STATE_CONSTRAINED_FINISHED:
can_move_now = TRUE;
break;
}
}
else if (constrained_rect.width != window->rect.width ||
constrained_rect.height != window->rect.height ||
(flags & META_MOVE_RESIZE_STATE_CHANGED))
flags & META_MOVE_RESIZE_STATE_CHANGED)
{
MetaWaylandWindowConfiguration *configuration;
@ -328,16 +380,6 @@ meta_window_wayland_move_resize_internal (MetaWindow *window,
constrained_rect.height == 1)
return;
if (window->placement.rule)
{
configuration =
meta_wayland_window_configuration_new_relative (rel_x,
rel_y,
configured_width,
configured_height);
}
else
{
configuration =
meta_wayland_window_configuration_new (configured_x,
configured_y,
@ -345,15 +387,11 @@ meta_window_wayland_move_resize_internal (MetaWindow *window,
configured_height,
flags,
gravity);
}
meta_window_wayland_configure (wl_window, configuration);
can_move_now = FALSE;
}
else
{
/* We're just moving the window, so we don't need to wait for a configure
* and then ack to simply move the window. */
can_move_now = TRUE;
}
}
@ -365,8 +403,17 @@ meta_window_wayland_move_resize_internal (MetaWindow *window,
if (can_move_now)
{
int new_x = constrained_rect.x;
int new_y = constrained_rect.y;
new_x = constrained_rect.x;
new_y = constrained_rect.y;
}
else
{
new_x = temporary_rect.x;
new_y = temporary_rect.y;
wl_window->has_pending_state_change |=
!!(flags & META_MOVE_RESIZE_STATE_CHANGED);
}
if (new_x != window->rect.x || new_y != window->rect.y)
{
@ -375,23 +422,27 @@ meta_window_wayland_move_resize_internal (MetaWindow *window,
window->rect.y = new_y;
}
int new_buffer_x = new_x - window->custom_frame_extents.left;
int new_buffer_y = new_y - window->custom_frame_extents.top;
if (window->placement.rule &&
window->placement.state == META_PLACEMENT_STATE_CONSTRAINED_FINISHED)
{
window->placement.current.rel_x = rel_x;
window->placement.current.rel_y = rel_y;
}
if (new_buffer_x != window->buffer_rect.x || new_buffer_y != window->buffer_rect.y)
new_buffer_x = new_x - window->custom_frame_extents.left;
new_buffer_y = new_y - window->custom_frame_extents.top;
if (new_buffer_x != window->buffer_rect.x ||
new_buffer_y != window->buffer_rect.y)
{
*result |= META_MOVE_RESIZE_RESULT_MOVED;
window->buffer_rect.x = new_buffer_x;
window->buffer_rect.y = new_buffer_y;
}
if (flags & META_MOVE_RESIZE_WAYLAND_STATE_CHANGED)
if (can_move_now &&
flags & META_MOVE_RESIZE_WAYLAND_STATE_CHANGED)
*result |= META_MOVE_RESIZE_RESULT_STATE_CHANGED;
}
else
{
wl_window->has_pending_state_change = (flags & META_MOVE_RESIZE_STATE_CHANGED) != 0;
}
}
static void
@ -856,10 +907,21 @@ meta_window_wayland_finish_move_resize (MetaWindow *window,
if (!is_window_being_resized)
{
if (acked_configuration)
{
if (window->placement.rule)
{
MetaWindow *parent;
parent = meta_window_get_transient_for (window);
rect.x = parent->rect.x + acked_configuration->rel_x;
rect.y = parent->rect.y + acked_configuration->rel_y;
}
else
{
rect.x = acked_configuration->x;
rect.y = acked_configuration->y;
}
}
else
{
rect.x = window->rect.x;
@ -872,6 +934,14 @@ meta_window_wayland_finish_move_resize (MetaWindow *window,
if (rect.x != window->rect.x || rect.y != window->rect.y)
flags |= META_MOVE_RESIZE_MOVE_ACTION;
}
else
{
if (acked_configuration)
{
rect.x = acked_configuration->x;
rect.y = acked_configuration->y;
}
}
if (wl_window->has_pending_state_change && acked_configuration)
{
@ -918,9 +988,24 @@ meta_window_place_with_placement_rule (MetaWindow *window,
window->placement.rule = g_new0 (MetaPlacementRule, 1);
*window->placement.rule = *placement_rule;
window->unconstrained_rect.x = window->rect.x;
window->unconstrained_rect.y = window->rect.y;
window->unconstrained_rect.width = placement_rule->width;
window->unconstrained_rect.height = placement_rule->height;
meta_window_force_placement (window, FALSE);
meta_window_move_resize_internal (window,
(META_MOVE_RESIZE_MOVE_ACTION |
META_MOVE_RESIZE_RESIZE_ACTION |
META_MOVE_RESIZE_PLACEMENT_CHANGED),
META_GRAVITY_NORTH_WEST,
window->unconstrained_rect);
}
void
meta_window_update_placement_rule (MetaWindow *window,
MetaPlacementRule *placement_rule)
{
window->placement.state = META_PLACEMENT_STATE_INVALIDATED;
meta_window_place_with_placement_rule (window, placement_rule);
}
void

View File

@ -53,6 +53,9 @@ void meta_window_wayland_place_relative_to (MetaWindow *window,
void meta_window_place_with_placement_rule (MetaWindow *window,
MetaPlacementRule *placement_rule);
void meta_window_update_placement_rule (MetaWindow *window,
MetaPlacementRule *placement_rule);
void meta_window_wayland_set_min_size (MetaWindow *window,
int width,
int height);

View File

@ -1312,6 +1312,7 @@ meta_window_x11_move_resize_internal (MetaWindow *window,
MetaGravity gravity,
MetaRectangle unconstrained_rect,
MetaRectangle constrained_rect,
MetaRectangle intermediate_rect,
int rel_x,
int rel_y,
MetaMoveResizeFlags flags,