wayland/xdg-toplevel-drag: Add the protocol implementation

- For already mapped windows, the window drag session is started
  straight away;
- For about-to-be-mapped window (ie: undocking window use case):
  - The "shown" signal for the dragged window triggers the actual
    MetaWindowDrag once it's mapped.
  - MetaWindowWayland now handles the case of toplevel-drag and position
    the window about to be mapped according to the toplevel-drag
    parameters.
- While attached to a toplevel-drag, the window state is updated to:
  - Actor's "reactive" state is set to false, which in practice excludes
    it from the possible drag target list;
  - WindowActor's "tied to drag" state is set to true, which results in
    initial placement constraints to be skipped, so newly created
    (detached) windows can be freely dragged around.
- Toplevel drag session ends upon:
  - dnd drop and cancellation.
  - xdg_toplevel_drag_v1 object destruction (client-side).
  - data source destruction.

Status:

- [x] Basic window drag triggering
- [x] Exclude the dragged window from event targets
- [x] Event forwarding (window drag vs wayland grabs)
- [x] Offset calc relative to toplevel geometry
- [x] Attach already mapped windows
- [x] Properly support not-yet-mapped windows
- [ ] Disable visibility change animations
- [ ] Dnd events stream adaptations

Signed-off-by: Nick Diego Yamane <nickdiego@igalia.com>
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4107>
This commit is contained in:
Nick Diego Yamane 2024-10-03 21:06:48 -04:00
parent 1cb46f203c
commit 66cfbf03c9
9 changed files with 533 additions and 0 deletions

View File

@ -697,6 +697,8 @@ if have_wayland
'wayland/meta-wayland-tablet-tool.h',
'wayland/meta-wayland-text-input.c',
'wayland/meta-wayland-text-input.h',
'wayland/meta-wayland-toplevel-drag.c',
'wayland/meta-wayland-toplevel-drag.h',
'wayland/meta-wayland-touch.c',
'wayland/meta-wayland-touch.h',
'wayland/meta-wayland-transaction.c',
@ -1118,6 +1120,7 @@ if have_wayland
['xdg-foreign', 'unstable', 'v2', ],
['xdg-output', 'unstable', 'v1', ],
['xdg-shell', 'stable', ],
['xdg-toplevel-drag', 'staging', 'v1'],
['xwayland-keyboard-grab', 'unstable', 'v1', ],
['linux-drm-syncobj-v1', 'private', ],
['color-management-v1', 'private', ],

View File

@ -43,6 +43,7 @@
#include "wayland/meta-wayland-pointer.h"
#include "wayland/meta-wayland-private.h"
#include "wayland/meta-wayland-seat.h"
#include "wayland/meta-wayland-toplevel-drag.h"
#define ROOTWINDOW_DROP_MIME "application/x-rootwindow-drop"
@ -343,6 +344,12 @@ meta_wayland_drag_grab_get_origin (MetaWaylandDragGrab *drag_grab)
return drag_grab->drag_origin;
}
MetaWaylandDataSource *
meta_wayland_drag_grab_get_data_source (MetaWaylandDragGrab *drag_grab)
{
return drag_grab->drag_data_source;
}
static void
data_source_update_user_dnd_action (MetaWaylandDataSource *source,
ClutterModifierType modifiers)

View File

@ -98,3 +98,6 @@ ClutterInputDevice * meta_wayland_drag_grab_get_device (MetaWaylandDragGrab *
ClutterEventSequence **sequence);
MetaWaylandSurface * meta_wayland_drag_grab_get_origin (MetaWaylandDragGrab *drag_grab);
MetaWaylandDataSource *
meta_wayland_drag_grab_get_data_source (MetaWaylandDragGrab *drag_grab);

View File

@ -27,6 +27,7 @@
#include "wayland/meta-wayland-data-source.h"
#include "wayland/meta-wayland-private.h"
#include "wayland/meta-wayland-toplevel-drag.h"
#define ALL_ACTIONS (WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | \
WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | \
@ -44,6 +45,7 @@ typedef struct _MetaWaylandDataSourcePrivate
enum wl_data_device_manager_dnd_action user_dnd_action;
enum wl_data_device_manager_dnd_action current_dnd_action;
MetaWaylandSeat *seat;
MetaWaylandToplevelDrag *toplevel_drag;
guint actions_set : 1;
guint in_ask : 1;
guint drop_performed : 1;
@ -614,3 +616,22 @@ meta_wayland_data_source_get_compositor (MetaWaylandDataSource *source)
return priv->compositor;
}
void
meta_wayland_data_source_set_toplevel_drag (MetaWaylandDataSource *source,
MetaWaylandToplevelDrag *toplevel_drag)
{
MetaWaylandDataSourcePrivate *priv =
meta_wayland_data_source_get_instance_private (source);
priv->toplevel_drag = toplevel_drag;
}
MetaWaylandToplevelDrag *
meta_wayland_data_source_get_toplevel_drag (MetaWaylandDataSource *source)
{
MetaWaylandDataSourcePrivate *priv =
meta_wayland_data_source_get_instance_private (source);
return priv->toplevel_drag;
}

View File

@ -110,3 +110,10 @@ gboolean meta_wayland_data_source_get_drop_performed (MetaWaylandDataSource *sou
void meta_wayland_data_source_notify_drop_performed (MetaWaylandDataSource *source);
void meta_wayland_data_source_notify_finish (MetaWaylandDataSource *source);
void
meta_wayland_data_source_set_toplevel_drag (MetaWaylandDataSource *source,
MetaWaylandToplevelDrag *toplevel_drag);
MetaWaylandToplevelDrag *
meta_wayland_data_source_get_toplevel_drag (MetaWaylandDataSource *source);

View File

@ -0,0 +1,435 @@
/*
* Copyright (C) 2024 Igalia, S.L.
*
* Author: Nick Yamane <nickdiego@igalia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include "clutter-mutter.h"
#include "config.h"
#include "meta-wayland-toplevel-drag.h"
#include <glib.h>
#include "compositor/compositor-private.h"
#include "compositor/meta-window-drag.h"
#include "core/window-private.h"
#include "meta/meta-debug.h"
#include "meta/meta-enums.h"
#include "meta/meta-wayland-surface.h"
#include "meta/types.h"
#include "meta/window.h"
#include "wayland/meta-wayland-data-device.h"
#include "wayland/meta-wayland-data-source.h"
#include "wayland/meta-wayland-private.h"
#include "wayland/meta-wayland-seat.h"
#include "wayland/meta-wayland-surface-private.h"
#include "wayland/meta-wayland-types.h"
#include "wayland/meta-wayland-versions.h"
#include "wayland/meta-wayland-xdg-shell.h"
#include "xdg-toplevel-drag-v1-server-protocol.h"
static void
xdg_toplevel_drag_destructor (struct wl_resource *resource)
{
MetaWaylandToplevelDrag *toplevel_drag = wl_resource_get_user_data (resource);
g_assert (toplevel_drag);
meta_topic (META_DEBUG_WAYLAND, "Destroying xdg_toplevel_drag#%u",
wl_resource_get_id (resource));
meta_wayland_toplevel_drag_end (toplevel_drag);
g_free (toplevel_drag);
}
static void
on_dragged_window_unmanaging (MetaWindow *window,
MetaWaylandToplevelDrag *toplevel_drag)
{
meta_topic (META_DEBUG_WAYLAND, "Dragged window destroyed.");
g_clear_signal_handler (&toplevel_drag->window_unmanaging_handler_id, window);
g_clear_signal_handler (&toplevel_drag->window_shown_handler_id, window);
toplevel_drag->dragged_surface = NULL;
}
static void
on_window_drag_ended (MetaWindowDrag *window_drag,
MetaWaylandToplevelDrag *toplevel_drag)
{
meta_topic (META_DEBUG_WAYLAND, "Window drag ended.");
g_clear_signal_handler (&toplevel_drag->drag_ended_handler_id, window_drag);
toplevel_drag->window_drag = NULL;
}
static void
on_data_source_destroyed (MetaWaylandDataSource *data_source,
MetaWaylandToplevelDrag *toplevel_drag)
{
meta_topic (META_DEBUG_WAYLAND,
"Data source destroyed before xdg_toplevel_drag#%d",
wl_resource_get_id (toplevel_drag->resource));
g_clear_signal_handler (&toplevel_drag->source_destroyed_handler_id,
data_source);
meta_wayland_toplevel_drag_end (toplevel_drag);
}
static void
add_window_geometry_origin (MetaWaylandSurface *dragged_surface,
int *x_offset,
int *y_offset)
{
MtkRectangle toplevel_geometry;
toplevel_geometry = meta_wayland_xdg_surface_get_window_geometry (
META_WAYLAND_XDG_SURFACE (dragged_surface->role));
if (x_offset)
*x_offset = *x_offset + toplevel_geometry.x;
if (y_offset)
*y_offset = *y_offset + toplevel_geometry.y;
}
static void
xdg_toplevel_drag_destroy (struct wl_client *client,
struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static MetaWaylandSurface *
surface_from_xdg_toplevel_resource (struct wl_resource *resource)
{
MetaWaylandSurfaceRole *surface_role = wl_resource_get_user_data (resource);
if (!META_IS_WAYLAND_SURFACE_ROLE (surface_role))
return NULL;
return meta_wayland_surface_role_get_surface (surface_role);
}
static void
start_window_drag (MetaWindow *dragged_window,
MetaWaylandToplevelDrag *toplevel_drag,
graphene_point_t *offset_hint)
{
MetaWaylandSeat *seat;
MetaWaylandDragGrab *drag_grab;
MetaSurfaceActor *surface_actor;
MetaContext *context;
MetaBackend *backend;
ClutterInputDevice *device;
ClutterEventSequence *sequence;
ClutterActor *stage, *grab_actor;
MetaCompositor *compositor;
uint32_t timestamp;
gboolean started;
g_assert (toplevel_drag);
g_assert (toplevel_drag->data_source);
g_assert (toplevel_drag->dragged_surface);
seat = meta_wayland_data_source_get_seat (toplevel_drag->data_source);
if (!seat)
return;
drag_grab = meta_wayland_data_device_get_current_grab (&seat->data_device);
if (!drag_grab ||
toplevel_drag->data_source != meta_wayland_drag_grab_get_data_source (drag_grab))
{
meta_topic (META_DEBUG_WAYLAND, "No drag grab found, earlying out.");
return;
}
/* Disable events on the dragged surface, so drag enter and leave events
* can be detected for other surfaces. */
surface_actor = meta_wayland_surface_get_actor (toplevel_drag->dragged_surface);
clutter_actor_set_reactive (CLUTTER_ACTOR (surface_actor), FALSE);
meta_topic (META_DEBUG_WAYLAND, "Starting window drag. window=%s offset=(%.0f, %.0f)",
dragged_window->desc,
(offset_hint ? offset_hint->x : -1),
(offset_hint ? offset_hint->y : -1));
device = meta_wayland_drag_grab_get_device (drag_grab, &sequence);
timestamp = meta_display_get_current_time_roundtrip (dragged_window->display);
/* Re-use the current wayland input's grab actor for the newly started
* window drag session. */
context = meta_display_get_context (dragged_window->display);
backend = meta_context_get_backend (context);
stage = meta_backend_get_stage (backend);
grab_actor = clutter_stage_get_grab_actor (CLUTTER_STAGE (stage));
compositor = dragged_window->display->compositor;
started = meta_compositor_drag_window (compositor, dragged_window,
META_GRAB_OP_MOVING_UNCONSTRAINED, device,
sequence, timestamp, offset_hint, grab_actor);
if (!started)
return;
toplevel_drag->window_drag = meta_compositor_get_current_window_drag (compositor);
toplevel_drag->drag_ended_handler_id =
g_signal_connect (toplevel_drag->window_drag,
"ended",
G_CALLBACK (on_window_drag_ended),
toplevel_drag);
}
static void
on_dragged_window_shown (MetaWindow *window,
MetaWaylandToplevelDrag *toplevel_drag)
{
g_assert (window->mapped);
g_clear_signal_handler (&toplevel_drag->window_shown_handler_id, window);
if (toplevel_drag->data_source && toplevel_drag->dragged_surface)
start_window_drag (window, toplevel_drag, NULL);
}
static void
xdg_toplevel_drag_attach (struct wl_client *client,
struct wl_resource *resource,
struct wl_resource *toplevel,
int32_t x_offset,
int32_t y_offset)
{
MetaWaylandSurface *dragged_surface;
MetaWindow *dragged_window;
float screen_x, screen_y;
MetaWaylandToplevelDrag *toplevel_drag = wl_resource_get_user_data (resource);
/* Toplevel drag becomes inert if the associated data source is destroyed */
if (!toplevel_drag->data_source)
return;
dragged_surface = surface_from_xdg_toplevel_resource (toplevel);
dragged_window = meta_wayland_surface_get_window (dragged_surface);
g_return_if_fail (dragged_window != NULL);
if (toplevel_drag->dragged_surface != NULL)
{
wl_resource_post_error (
resource, XDG_TOPLEVEL_DRAG_V1_ERROR_TOPLEVEL_ATTACHED,
"toplevel drag already has a surface attached");
return;
}
meta_topic (META_DEBUG_WAYLAND,
"Attaching xdg_toplevel#%u to xdg_toplevel_drag#%u "
"data_source#%p window=%s drag_offset=(%d, %d)",
wl_resource_get_id (toplevel), wl_resource_get_id (resource),
toplevel_drag->data_source, dragged_window->desc, x_offset, y_offset);
toplevel_drag->dragged_surface = dragged_surface;
toplevel_drag->x_offset = x_offset;
toplevel_drag->y_offset = y_offset;
toplevel_drag->window_unmanaging_handler_id =
g_signal_connect (dragged_window,
"unmanaging",
G_CALLBACK (on_dragged_window_unmanaging),
toplevel_drag);
if (dragged_window->mapped)
{
/* {x,y}_offset values are relative to the toplevel geometry. */
add_window_geometry_origin (dragged_surface, &x_offset, &y_offset);
meta_wayland_surface_get_absolute_coordinates (dragged_surface,
(float) x_offset,
(float) y_offset,
&screen_x, &screen_y);
start_window_drag (dragged_window, toplevel_drag,
&GRAPHENE_POINT_INIT (screen_x, screen_y));
}
else
{
meta_topic (META_DEBUG_WAYLAND, "Window not mapped yet, monitoring.");
toplevel_drag->window_shown_handler_id =
g_signal_connect (dragged_window,
"shown",
G_CALLBACK (on_dragged_window_shown),
toplevel_drag);
}
}
static const struct xdg_toplevel_drag_v1_interface meta_wayland_toplevel_drag_interface = {
.destroy = xdg_toplevel_drag_destroy,
.attach = xdg_toplevel_drag_attach,
};
static void
xdg_toplevel_drag_manager_destroy (struct wl_client *client,
struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static void
xdg_toplevel_drag_manager_get_toplevel_drag (struct wl_client *client,
struct wl_resource *resource,
uint32_t toplevel_drag_id,
struct wl_resource *data_source_resource)
{
MetaWaylandDataSource *data_source;
MetaWaylandToplevelDrag *toplevel_drag;
struct wl_resource *toplevel_drag_resource;
data_source = wl_resource_get_user_data (data_source_resource);
toplevel_drag = meta_wayland_data_source_get_toplevel_drag (data_source);
if (toplevel_drag)
{
wl_resource_post_error (
resource, XDG_TOPLEVEL_DRAG_MANAGER_V1_ERROR_INVALID_SOURCE,
"toplevel drag resource already exists on data source");
return;
}
toplevel_drag_resource = wl_resource_create (client,
&xdg_toplevel_drag_v1_interface,
wl_resource_get_version (resource),
toplevel_drag_id);
toplevel_drag = g_new0 (MetaWaylandToplevelDrag, 1);
toplevel_drag->resource = toplevel_drag_resource;
toplevel_drag->data_source = data_source;
toplevel_drag->source_destroyed_handler_id =
g_signal_connect (data_source,
"destroy",
G_CALLBACK (on_data_source_destroyed),
toplevel_drag);
meta_wayland_data_source_set_toplevel_drag (data_source,
toplevel_drag);
wl_resource_set_implementation (toplevel_drag_resource,
&meta_wayland_toplevel_drag_interface,
toplevel_drag,
xdg_toplevel_drag_destructor);
}
static const struct xdg_toplevel_drag_manager_v1_interface meta_wayland_toplevel_drag_manager_interface = {
.destroy = xdg_toplevel_drag_manager_destroy,
.get_xdg_toplevel_drag = xdg_toplevel_drag_manager_get_toplevel_drag,
};
static void
xdg_toplevel_drag_bind (struct wl_client *client,
void *data,
uint32_t version,
uint32_t id)
{
struct wl_resource *resource;
resource = wl_resource_create (client,
&xdg_toplevel_drag_manager_v1_interface,
version,
id);
wl_resource_set_implementation (resource,
&meta_wayland_toplevel_drag_manager_interface,
data,
NULL);
}
void
meta_wayland_init_toplevel_drag (MetaWaylandCompositor *compositor)
{
if (wl_global_create (compositor->wayland_display,
&xdg_toplevel_drag_manager_v1_interface,
META_XDG_TOPLEVEL_DRAG_VERSION,
compositor,
xdg_toplevel_drag_bind) == NULL)
g_error ("Failed to register a global xdg_toplevel_drag object");
}
gboolean
meta_wayland_toplevel_drag_calc_origin_for_dragged_window (MetaWaylandToplevelDrag *toplevel_drag,
MtkRectangle *bounds_out)
{
MetaWaylandSeat *seat;
MetaWaylandDragGrab *drag_grab;
ClutterInputDevice *device;
ClutterEventSequence *sequence;
graphene_point_t coords;
g_assert (toplevel_drag);
g_assert (bounds_out);
seat = meta_wayland_data_source_get_seat (toplevel_drag->data_source);
if (!seat)
return FALSE;
drag_grab = meta_wayland_data_device_get_current_grab (&seat->data_device);
if (!drag_grab)
return FALSE;
device = meta_wayland_drag_grab_get_device (drag_grab, &sequence);
clutter_seat_query_state (clutter_input_device_get_seat (device),
device, sequence, &coords, NULL);
meta_topic (META_DEBUG_WAYLAND,
"Calculated position for the dragged window. "
"offset=(%d, %d) new_origin=(%.0f, %.0f)",
toplevel_drag->x_offset, toplevel_drag->y_offset, coords.x, coords.y);
bounds_out->x = (int) coords.x - toplevel_drag->x_offset;
bounds_out->y = (int) coords.y - toplevel_drag->y_offset;
return TRUE;
}
void
meta_wayland_toplevel_drag_end (MetaWaylandToplevelDrag *toplevel_drag)
{
MetaWindow *window;
MetaWindowActor *window_actor;
MetaSurfaceActor *surface_actor;
g_return_if_fail (toplevel_drag != NULL);
meta_topic (META_DEBUG_WAYLAND, "Ending toplevel drag.");
if (toplevel_drag->window_drag)
{
window = meta_window_drag_get_window (toplevel_drag->window_drag);
g_clear_signal_handler (&toplevel_drag->drag_ended_handler_id,
toplevel_drag->window_drag);
meta_window_drag_end (toplevel_drag->window_drag);
window_actor = meta_window_actor_from_window (window);
if (window_actor)
meta_window_actor_set_tied_to_drag (window_actor, FALSE);
toplevel_drag->window_drag = NULL;
}
if (toplevel_drag->dragged_surface)
{
surface_actor = meta_wayland_surface_get_actor (toplevel_drag->dragged_surface);
if (surface_actor)
clutter_actor_set_reactive (CLUTTER_ACTOR (surface_actor), TRUE);
window = meta_wayland_surface_get_window (toplevel_drag->dragged_surface);
if (window)
{
g_clear_signal_handler (&toplevel_drag->window_unmanaging_handler_id, window);
g_clear_signal_handler (&toplevel_drag->window_shown_handler_id, window);
}
toplevel_drag->dragged_surface = NULL;
}
if (toplevel_drag->data_source)
{
g_clear_signal_handler (&toplevel_drag->source_destroyed_handler_id,
toplevel_drag->data_source);
meta_wayland_data_source_set_toplevel_drag (toplevel_drag->data_source, NULL);
toplevel_drag->data_source = NULL;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2024 Igalia, S.L.
*
* Author: Nick Yamane <nickdiego@igalia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <glib.h>
#include <stdint.h>
#include "compositor/meta-window-drag.h"
#include "mtk/mtk.h"
#include "wayland/meta-wayland-types.h"
#include "wayland/meta-wayland-data-source.h"
struct _MetaWaylandToplevelDrag
{
struct wl_resource *resource;
MetaWaylandDataSource *data_source;
MetaWaylandSurface *dragged_surface;
int32_t x_offset, y_offset;
MetaWindowDrag *window_drag;
gulong window_unmanaging_handler_id;
gulong window_shown_handler_id;
gulong drag_ended_handler_id;
gulong source_destroyed_handler_id;
};
void
meta_wayland_init_toplevel_drag (MetaWaylandCompositor *compositor);
gboolean
meta_wayland_toplevel_drag_calc_origin_for_dragged_window (MetaWaylandToplevelDrag *drag,
MtkRectangle *bounds_out);
void
meta_wayland_toplevel_drag_end (MetaWaylandToplevelDrag *drag);

View File

@ -75,3 +75,5 @@ typedef struct _MetaWaylandClient MetaWaylandClient;
typedef struct _MetaWaylandDrmLeaseManager MetaWaylandDrmLeaseManager;
typedef struct _MetaWaylandXdgSessionManager MetaWaylandXdgSessionManager;
typedef struct _MetaWaylandToplevelDrag MetaWaylandToplevelDrag;

View File

@ -62,3 +62,4 @@
#define META_WP_DRM_LEASE_DEVICE_V1_VERSION 1
#define META_XDG_SESSION_MANAGER_V1_VERSION 1
#define META_WP_SYSTEM_BELL_V1_VERSION 1
#define META_XDG_TOPLEVEL_DRAG_VERSION 1