wayland/xdnd: Use multiple DnD X11 peers

When a drag and drop occurs from an X11 client to a Wayland native
client, mutter uses an internal X11 window as a peer for the DnD  drop
site.

That internal X11 window is moved and resized to match the Wayland
native windows as the drag destination moves.

When moving from one Wayland native window to another Wayland native
window, the same X11 window is used, and as a result no DND enter/leave
events is emitted.

In that case, the drop may occur on the wrong Wayland native window,
because no new XdndEnter/XdndLeave event were emitted.

To avoid that issue, use a pair of X11 windows instead of just one and
alternate between the two when repicking a new drop surface, so that
moving from a Wayland surface to another will always generate the
expected enter/leave events that we rely on.

Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/2136
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2305>
This commit is contained in:
Olivier Fourdan 2022-02-21 16:25:25 +01:00
parent 843fb1d31b
commit fe41e69eea

View File

@ -58,9 +58,10 @@ struct _MetaXWaylandDnd
Time client_message_timestamp;
MetaWaylandDataSource *source; /* owned by MetaWaylandDataDevice */
MetaWaylandSurface *focus_surface;
Window dnd_window; /* Mutter-internal window, acts as peer on wayland drop sites */
Window dnd_window[2]; /* Mutter-internal windows, act as peer on wayland drop sites */
Window dnd_dest; /* X11 drag dest window */
guint32 last_motion_time;
int current_dnd_window;
};
enum
@ -134,6 +135,90 @@ atom_to_action (Atom atom)
return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
}
static Window
current_dnd_window (MetaXWaylandDnd *dnd)
{
return dnd->dnd_window[dnd->current_dnd_window];
}
static Window
next_dnd_window (MetaXWaylandDnd *dnd)
{
dnd->current_dnd_window =
(dnd->current_dnd_window + 1) % G_N_ELEMENTS (dnd->dnd_window);
return current_dnd_window (dnd);
}
static void
create_dnd_windows (MetaXWaylandDnd *dnd,
MetaX11Display *x11_display)
{
Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
XSetWindowAttributes attributes;
guint32 version = XDND_VERSION;
int i;
attributes.event_mask = PropertyChangeMask | SubstructureNotifyMask;
attributes.override_redirect = True;
for (i = 0; i < G_N_ELEMENTS (dnd->dnd_window); i++)
{
dnd->dnd_window[i] =
XCreateWindow (xdisplay,
meta_x11_display_get_xroot (x11_display),
-1, -1, 1, 1,
0, /* border width */
0, /* depth */
InputOnly, /* class */
CopyFromParent, /* visual */
CWEventMask | CWOverrideRedirect,
&attributes);
XChangeProperty (xdisplay, dnd->dnd_window[i],
xdnd_atoms[ATOM_DND_AWARE],
XA_ATOM, 32, PropModeReplace,
(guchar*) &version, 1);
}
}
static void
destroy_dnd_windows (MetaXWaylandDnd *dnd,
MetaX11Display *x11_display)
{
Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
int i;
for (i = 0; i < G_N_ELEMENTS (dnd->dnd_window); i++)
{
XDestroyWindow (xdisplay, dnd->dnd_window[i]);
dnd->dnd_window[i] = None;
}
}
static void
hide_dnd_window (MetaXWaylandDnd *dnd,
MetaX11Display *x11_display,
int index)
{
Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
g_assert (index < G_N_ELEMENTS (dnd->dnd_window));
XMoveResizeWindow (xdisplay, dnd->dnd_window[index], -1, -1, 1, 1);
XUnmapWindow (xdisplay, dnd->dnd_window[index]);
}
static void
hide_all_dnd_windows (MetaXWaylandDnd *dnd,
MetaX11Display *x11_display)
{
int i;
for (i = 0; i < G_N_ELEMENTS (dnd->dnd_window); i++)
hide_dnd_window (dnd, x11_display, i);
}
static void
xdnd_send_enter (MetaXWaylandDnd *dnd,
Window dest)
@ -296,7 +381,7 @@ xdnd_send_finished (MetaXWaylandDnd *dnd,
xev.xclient.format = 32;
xev.xclient.window = dest;
xev.xclient.data.l[0] = dnd->dnd_window;
xev.xclient.data.l[0] = current_dnd_window (dnd);
if (accepted)
{
@ -326,7 +411,7 @@ xdnd_send_status (MetaXWaylandDnd *dnd,
xev.xclient.format = 32;
xev.xclient.window = dest;
xev.xclient.data.l[0] = dnd->dnd_window;
xev.xclient.data.l[0] = current_dnd_window (dnd);
xev.xclient.data.l[1] = 1 << 1; /* Bit 2: dest wants XdndPosition messages */
xev.xclient.data.l[4] = action_to_atom (action);
@ -344,7 +429,7 @@ static void
meta_xwayland_end_dnd_grab (MetaWaylandDataDevice *data_device,
gboolean success)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
MetaX11Display *x11_display = meta_get_display ()->x11_display;
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaXWaylandManager *manager = &compositor->xwayland_manager;
MetaWaylandDragGrab *drag_grab = compositor->seat->data_device.current_grab;
@ -358,8 +443,7 @@ meta_xwayland_end_dnd_grab (MetaWaylandDataDevice *data_device,
meta_wayland_data_device_end_drag (data_device);
}
XMoveResizeWindow (xdisplay, dnd->dnd_window, -1, -1, 1, 1);
XUnmapWindow (xdisplay, dnd->dnd_window);
hide_all_dnd_windows (dnd, x11_display);
}
static void
@ -654,7 +738,8 @@ repick_drop_surface (MetaWaylandCompositor *compositor,
MetaWaylandDragGrab *drag_grab,
const ClutterEvent *event)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
MetaX11Display *x11_display = meta_get_display ()->x11_display;
Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
MetaXWaylandDnd *dnd = compositor->xwayland_manager.dnd;
MetaWaylandSurface *focus = NULL;
MetaWindow *focus_window;
@ -673,8 +758,13 @@ repick_drop_surface (MetaWaylandCompositor *compositor,
if (focus_window &&
focus_window->client_type == META_WINDOW_CLIENT_TYPE_WAYLAND)
{
XMapRaised (xdisplay, dnd->dnd_window);
XMoveResizeWindow (xdisplay, dnd->dnd_window,
Window dnd_window;
hide_dnd_window (dnd, x11_display, dnd->current_dnd_window);
dnd_window = next_dnd_window (dnd);
XMapRaised (xdisplay, dnd_window);
XMoveResizeWindow (xdisplay, dnd_window,
focus_window->rect.x,
focus_window->rect.y,
focus_window->rect.width,
@ -682,8 +772,7 @@ repick_drop_surface (MetaWaylandCompositor *compositor,
}
else
{
XMoveResizeWindow (xdisplay, dnd->dnd_window, -1, -1, 1, 1);
XUnmapWindow (xdisplay, dnd->dnd_window);
hide_all_dnd_windows (dnd, x11_display);
}
}
@ -950,10 +1039,10 @@ meta_xwayland_init_dnd (MetaX11Display *x11_display)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaXWaylandManager *manager = &compositor->xwayland_manager;
Display *xdisplay = meta_x11_display_get_xdisplay (x11_display);
MetaXWaylandDnd *dnd = manager->dnd;
XSetWindowAttributes attributes;
guint32 i, version = XDND_VERSION;
guint32 i;
g_assert (manager->dnd == NULL);
@ -962,22 +1051,8 @@ meta_xwayland_init_dnd (MetaX11Display *x11_display)
for (i = 0; i < N_DND_ATOMS; i++)
xdnd_atoms[i] = gdk_x11_get_xatom_by_name (atom_names[i]);
attributes.event_mask = PropertyChangeMask | SubstructureNotifyMask;
attributes.override_redirect = True;
dnd->dnd_window = XCreateWindow (xdisplay,
meta_x11_display_get_xroot (x11_display),
-1, -1, 1, 1,
0, /* border width */
0, /* depth */
InputOnly, /* class */
CopyFromParent, /* visual */
CWEventMask | CWOverrideRedirect,
&attributes);
XChangeProperty (xdisplay, dnd->dnd_window,
xdnd_atoms[ATOM_DND_AWARE],
XA_ATOM, 32, PropModeReplace,
(guchar*) &version, 1);
create_dnd_windows (dnd, x11_display);
dnd->current_dnd_window = 0;
}
void
@ -988,8 +1063,7 @@ meta_xwayland_shutdown_dnd (MetaXWaylandManager *manager,
g_assert (dnd != NULL);
XDestroyWindow (meta_x11_display_get_xdisplay (x11_display), dnd->dnd_window);
dnd->dnd_window = None;
destroy_dnd_windows (dnd, x11_display);
g_free (dnd);
manager->dnd = NULL;