mutter/src/wayland/meta-xwayland-selection.c
Carlos Garnacho 9b26694bbc wayland: Implement DnD actions as per wl_data_device v3 changes
We now additionally send:
  - wl_data_offer.source_actions
  - wl_data_source.action
  - wl_data_offer.action
  - wl_data_source.dnd_drop_performed
  - wl_data_source.dnd_finished

The protocol changes allow for compositors to implement different policies
when chosing the action, mutter uses this to reimplement the same behavior
that GTK+ traditionally had:

  - Alt/Control/Shift modifiers change the chosen action to
    ask/copy/move respectively
  - Drags with middle button start out as "ask" by default

As mutter now also grabs the keyboard and unsets the window focus for these
purposes, the window focus is restored after the drag operation has
finished.

The Xdnd bridge code is also modified to cope with actions, so mixed
wayland-x11 scenarios are able to convey that information.

https://bugzilla.gnome.org/show_bug.cgi?id=760805
2016-01-19 13:55:33 +01:00

1738 lines
56 KiB
C

/*
* Copyright © 2012 Intel Corporation
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
* that the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of the copyright holders not be used in
* advertising or publicity pertaining to distribution of the software
* without specific, written prior permission. The copyright holders make
* no representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* The file is loosely based on xwayland/selection.c from Weston */
#include "config.h"
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <glib-unix.h>
#include <gio/gunixoutputstream.h>
#include <gio/gunixinputstream.h>
#include <gdk/gdkx.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xfixes.h>
#include <meta/errors.h>
#include "meta-xwayland-private.h"
#include "meta-xwayland-selection-private.h"
#include "meta-wayland-data-device.h"
#define INCR_CHUNK_SIZE (128 * 1024)
#define XDND_VERSION 5
typedef struct {
MetaXWaylandSelection *selection_data;
GInputStream *stream;
GCancellable *cancellable;
MetaWindow *window;
XSelectionRequestEvent request_event;
guchar buffer[INCR_CHUNK_SIZE];
gsize buffer_len;
guint incr : 1;
} WaylandSelectionData;
typedef struct {
MetaXWaylandSelection *selection_data;
GOutputStream *stream;
GCancellable *cancellable;
gchar *mime_type;
guint incr : 1;
} X11SelectionData;
typedef struct {
Atom selection_atom;
Window window;
Window owner;
Time timestamp;
MetaWaylandDataSource *source; /* owned by MetaWaylandDataDevice */
WaylandSelectionData *wayland_selection;
X11SelectionData *x11_selection;
struct wl_listener ownership_listener;
} MetaSelectionBridge;
typedef struct {
MetaSelectionBridge selection;
MetaWaylandSurface *focus_surface;
Window dnd_window; /* Mutter-internal window, acts as peer on wayland drop sites */
Window dnd_dest; /* X11 drag dest window */
guint32 last_motion_time;
} MetaDndBridge;
struct _MetaWaylandDataSourceXWayland
{
MetaWaylandDataSource parent;
MetaSelectionBridge *selection;
guint has_utf8_string_atom : 1;
};
struct _MetaXWaylandSelection {
MetaSelectionBridge clipboard;
MetaDndBridge dnd;
};
enum {
ATOM_DND_SELECTION,
ATOM_DND_AWARE,
ATOM_DND_STATUS,
ATOM_DND_POSITION,
ATOM_DND_ENTER,
ATOM_DND_LEAVE,
ATOM_DND_DROP,
ATOM_DND_FINISHED,
ATOM_DND_PROXY,
ATOM_DND_TYPE_LIST,
ATOM_DND_ACTION_MOVE,
ATOM_DND_ACTION_COPY,
ATOM_DND_ACTION_ASK,
ATOM_DND_ACTION_PRIVATE,
N_DND_ATOMS
};
/* Matches order in enum above */
const gchar *atom_names[] = {
"XdndSelection",
"XdndAware",
"XdndStatus",
"XdndPosition",
"XdndEnter",
"XdndLeave",
"XdndDrop",
"XdndFinished",
"XdndProxy",
"XdndTypeList",
"XdndActionMove",
"XdndActionCopy",
"XdndActionAsk",
"XdndActionPrivate",
NULL
};
Atom xdnd_atoms[N_DND_ATOMS];
G_DEFINE_TYPE (MetaWaylandDataSourceXWayland, meta_wayland_data_source_xwayland,
META_TYPE_WAYLAND_DATA_SOURCE);
/* XDND helpers */
static Atom
action_to_atom (uint32_t action)
{
if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
return xdnd_atoms[ATOM_DND_ACTION_COPY];
else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
return xdnd_atoms[ATOM_DND_ACTION_MOVE];
else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
return xdnd_atoms[ATOM_DND_ACTION_ASK];
else
return None;
}
static enum wl_data_device_manager_dnd_action
atom_to_action (Atom atom)
{
if (atom == xdnd_atoms[ATOM_DND_ACTION_COPY] ||
atom == xdnd_atoms[ATOM_DND_ACTION_PRIVATE])
return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
else if (atom == xdnd_atoms[ATOM_DND_ACTION_MOVE])
return WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
else if (atom == xdnd_atoms[ATOM_DND_ACTION_ASK])
return WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK;
else
return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
}
static void
xdnd_send_enter (MetaXWaylandSelection *selection_data,
Window dest)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaSelectionBridge *selection = &selection_data->dnd.selection;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
MetaWaylandDataSource *data_source;
XEvent xev = { 0 };
gchar **p;
struct wl_array *source_mime_types;
data_source = compositor->seat->data_device.dnd_data_source;
xev.xclient.type = ClientMessage;
xev.xclient.message_type = xdnd_atoms[ATOM_DND_ENTER];
xev.xclient.format = 32;
xev.xclient.window = dest;
xev.xclient.data.l[0] = selection->window;
xev.xclient.data.l[1] = XDND_VERSION << 24; /* version */
xev.xclient.data.l[2] = xev.xclient.data.l[3] = xev.xclient.data.l[4] = 0;
source_mime_types = meta_wayland_data_source_get_mime_types (data_source);
if (source_mime_types->size <= 3)
{
/* The mimetype atoms fit in this same message */
gint i = 2;
wl_array_for_each (p, source_mime_types)
{
xev.xclient.data.l[i++] = gdk_x11_get_xatom_by_name (*p);
}
}
else
{
/* We have more than 3 mimetypes, we must set up
* the mimetype list as a XdndTypeList property.
*/
Atom *atomlist;
gint i = 0;
xev.xclient.data.l[1] |= 1;
atomlist = g_new0 (Atom, source_mime_types->size);
wl_array_for_each (p, source_mime_types)
{
atomlist[i++] = gdk_x11_get_xatom_by_name (*p);
}
XChangeProperty (xdisplay, selection->window,
xdnd_atoms[ATOM_DND_TYPE_LIST],
XA_ATOM, 32, PropModeReplace,
(guchar *) atomlist, i);
}
XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}
static void
xdnd_send_leave (MetaXWaylandSelection *selection_data,
Window dest)
{
MetaSelectionBridge *selection = &selection_data->dnd.selection;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
XEvent xev = { 0 };
xev.xclient.type = ClientMessage;
xev.xclient.message_type = xdnd_atoms[ATOM_DND_LEAVE];
xev.xclient.format = 32;
xev.xclient.window = dest;
xev.xclient.data.l[0] = selection->window;
XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}
static void
xdnd_send_position (MetaXWaylandSelection *selection_data,
Window dest,
uint32_t time,
int x,
int y)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaSelectionBridge *selection = &selection_data->dnd.selection;
MetaWaylandDataSource *source = compositor->seat->data_device.dnd_data_source;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
uint32_t action = 0, user_action, actions;
XEvent xev = { 0 };
user_action = meta_wayland_data_source_get_user_action (source);
actions = meta_wayland_data_source_get_actions (source);
if (user_action & actions)
action = user_action;
if (!action)
action = actions;
xev.xclient.type = ClientMessage;
xev.xclient.message_type = xdnd_atoms[ATOM_DND_POSITION];
xev.xclient.format = 32;
xev.xclient.window = dest;
xev.xclient.data.l[0] = selection->window;
xev.xclient.data.l[1] = 0;
xev.xclient.data.l[2] = (x << 16) | y;
xev.xclient.data.l[3] = time;
xev.xclient.data.l[4] = action_to_atom (action);
XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}
static void
xdnd_send_drop (MetaXWaylandSelection *selection_data,
Window dest,
uint32_t time)
{
MetaSelectionBridge *selection = &selection_data->dnd.selection;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
XEvent xev = { 0 };
xev.xclient.type = ClientMessage;
xev.xclient.message_type = xdnd_atoms[ATOM_DND_DROP];
xev.xclient.format = 32;
xev.xclient.window = dest;
xev.xclient.data.l[0] = selection->window;
xev.xclient.data.l[2] = time;
XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}
static void
xdnd_send_finished (MetaXWaylandSelection *selection_data,
Window dest,
gboolean accepted)
{
MetaDndBridge *selection = &selection_data->dnd;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
MetaWaylandDataSource *source = selection_data->dnd.selection.source;
uint32_t action = 0;
XEvent xev = { 0 };
xev.xclient.type = ClientMessage;
xev.xclient.message_type = xdnd_atoms[ATOM_DND_FINISHED];
xev.xclient.format = 32;
xev.xclient.window = dest;
xev.xclient.data.l[0] = selection->dnd_window;
if (accepted)
{
action = meta_wayland_data_source_get_current_action (source);
xev.xclient.data.l[1] = 1; /* Drop successful */
xev.xclient.data.l[2] = action_to_atom (action);
}
XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}
static void
xdnd_send_status (MetaXWaylandSelection *selection_data,
Window dest,
uint32_t action)
{
MetaDndBridge *selection = &selection_data->dnd;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
XEvent xev = { 0 };
xev.xclient.type = ClientMessage;
xev.xclient.message_type = xdnd_atoms[ATOM_DND_STATUS];
xev.xclient.format = 32;
xev.xclient.window = dest;
xev.xclient.data.l[0] = selection->dnd_window;
xev.xclient.data.l[1] = 1 << 1; /* Bit 2: dest wants XdndPosition messages */
xev.xclient.data.l[4] = action_to_atom (action);
if (xev.xclient.data.l[4])
xev.xclient.data.l[1] |= 1 << 0; /* Bit 1: dest accepts the drop */
XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}
static void
meta_xwayland_init_dnd (MetaXWaylandManager *manager)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
MetaDndBridge *dnd = &manager->selection_data->dnd;
XSetWindowAttributes attributes;
guint32 i, version = XDND_VERSION;
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,
gdk_x11_window_get_xid (gdk_get_default_root_window ()),
-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);
}
static void
meta_xwayland_shutdown_dnd (MetaXWaylandManager *manager)
{
MetaDndBridge *dnd = &manager->selection_data->dnd;
XDestroyWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
dnd->dnd_window);
dnd->dnd_window = None;
}
/* X11/Wayland data bridges */
static MetaSelectionBridge *
atom_to_selection_bridge (MetaWaylandCompositor *compositor,
Atom selection_atom)
{
MetaXWaylandSelection *selection_data = compositor->xwayland_manager.selection_data;
if (selection_atom == selection_data->clipboard.selection_atom)
return &selection_data->clipboard;
else if (selection_atom == selection_data->dnd.selection.selection_atom)
return &selection_data->dnd.selection;
else
return NULL;
}
static X11SelectionData *
x11_selection_data_new (MetaXWaylandSelection *selection_data,
int fd,
const char *mime_type)
{
X11SelectionData *data;
data = g_slice_new0 (X11SelectionData);
data->selection_data = selection_data;
data->stream = g_unix_output_stream_new (fd, TRUE);
data->cancellable = g_cancellable_new ();
data->mime_type = g_strdup (mime_type);
return data;
}
static void
x11_selection_data_free (X11SelectionData *data)
{
g_cancellable_cancel (data->cancellable);
g_object_unref (data->cancellable);
g_object_unref (data->stream);
g_free (data->mime_type);
g_slice_free (X11SelectionData, data);
}
static void
x11_selection_data_send_finished (MetaSelectionBridge *selection,
gboolean success)
{
uint32_t action = meta_wayland_data_source_get_current_action (selection->source);
if (!selection->x11_selection)
return;
if (success && action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
/* Request data deletion on the drag source */
XConvertSelection (xdisplay,
selection->selection_atom,
gdk_x11_get_xatom_by_name ("DELETE"),
gdk_x11_get_xatom_by_name ("_META_SELECTION"),
selection->window,
CurrentTime);
}
xdnd_send_finished (selection->x11_selection->selection_data,
selection->owner, success);
}
static void
x11_selection_data_finish (MetaSelectionBridge *selection,
gboolean success)
{
if (!selection->x11_selection)
return;
if (selection == &selection->x11_selection->selection_data->dnd.selection)
x11_selection_data_send_finished (selection, success);
g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free);
}
static void
x11_selection_data_close (X11SelectionData *data)
{
g_output_stream_close (data->stream, data->cancellable, NULL);
}
static void
x11_data_write_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
MetaSelectionBridge *selection = user_data;
X11SelectionData *data = selection->x11_selection;
GError *error = NULL;
gboolean success = TRUE;
g_output_stream_write_finish (G_OUTPUT_STREAM (object), res, &error);
if (error)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_error_free (error);
return;
}
g_warning ("Error writing from X11 selection: %s\n", error->message);
g_error_free (error);
success = FALSE;
}
if (success && data->incr)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
XDeleteProperty (xdisplay, selection->window,
gdk_x11_get_xatom_by_name ("_META_SELECTION"));
}
else
{
x11_selection_data_close (selection->x11_selection);
x11_selection_data_finish (selection, success);
}
}
static void
x11_selection_data_write (MetaSelectionBridge *selection,
guchar *buffer,
gulong len)
{
X11SelectionData *data = selection->x11_selection;
g_output_stream_write_async (data->stream, buffer, len,
G_PRIORITY_DEFAULT, data->cancellable,
x11_data_write_cb, selection);
}
static MetaWaylandDataSource *
data_device_get_active_source_for_atom (MetaWaylandDataDevice *data_device,
Atom selection_atom)
{
if (selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD"))
return data_device->selection_data_source;
else if (selection_atom == xdnd_atoms[ATOM_DND_SELECTION])
return data_device->dnd_data_source;
else
return NULL;
}
static WaylandSelectionData *
wayland_selection_data_new (XSelectionRequestEvent *request_event,
MetaWaylandCompositor *compositor)
{
MetaWaylandDataDevice *data_device;
MetaWaylandDataSource *wayland_source;
MetaSelectionBridge *selection;
WaylandSelectionData *data;
const gchar *mime_type;
GError *error = NULL;
int p[2];
selection = atom_to_selection_bridge (compositor, request_event->selection);
if (!selection)
return NULL;
if (!g_unix_open_pipe (p, FD_CLOEXEC, &error))
{
g_critical ("Failed to open pipe: %s\n", error->message);
g_error_free (error);
return NULL;
}
data_device = &compositor->seat->data_device;
mime_type = gdk_x11_get_xatom_name (request_event->target);
if (!g_unix_set_fd_nonblocking (p[0], TRUE, &error) ||
!g_unix_set_fd_nonblocking (p[1], TRUE, &error))
{
if (error)
{
g_critical ("Failed to make fds non-blocking: %s\n", error->message);
g_error_free (error);
}
close (p[0]);
close (p[1]);
return NULL;
}
wayland_source = data_device_get_active_source_for_atom (data_device,
selection->selection_atom),
meta_wayland_data_source_send (wayland_source, mime_type, p[1]);
data = g_slice_new0 (WaylandSelectionData);
data->request_event = *request_event;
data->cancellable = g_cancellable_new ();
data->stream = g_unix_input_stream_new (p[0], TRUE);
data->window = meta_display_lookup_x_window (meta_get_display (),
data->request_event.requestor);
if (!data->window)
{
/* Not a managed window, set the PropertyChangeMask
* for INCR deletion notifications.
*/
XSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
data->request_event.requestor, PropertyChangeMask);
}
return data;
}
static void
reply_selection_request (XSelectionRequestEvent *request_event,
gboolean accepted)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
XSelectionEvent event;
memset(&event, 0, sizeof (XSelectionEvent));
event.type = SelectionNotify;
event.time = request_event->time;
event.requestor = request_event->requestor;
event.selection = request_event->selection;
event.target = request_event->target;
event.property = accepted ? request_event->property : None;
XSendEvent (xdisplay, request_event->requestor,
False, NoEventMask, (XEvent *) &event);
}
static void
wayland_selection_data_free (WaylandSelectionData *data)
{
if (!data->window)
{
MetaDisplay *display = meta_get_display ();
meta_error_trap_push (display);
XSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
data->request_event.requestor, NoEventMask);
meta_error_trap_pop (display);
}
g_cancellable_cancel (data->cancellable);
g_object_unref (data->cancellable);
g_object_unref (data->stream);
g_slice_free (WaylandSelectionData, data);
}
static void
wayland_selection_update_x11_property (WaylandSelectionData *data)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
XChangeProperty (xdisplay,
data->request_event.requestor,
data->request_event.property,
data->request_event.target,
8, PropModeReplace,
data->buffer, data->buffer_len);
data->buffer_len = 0;
}
static void
wayland_data_read_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
MetaSelectionBridge *selection = user_data;
WaylandSelectionData *data = selection->wayland_selection;
GError *error = NULL;
gsize bytes_read;
bytes_read = g_input_stream_read_finish (G_INPUT_STREAM (object),
res, &error);
if (error)
{
g_warning ("Error transfering wayland clipboard to X11: %s\n",
error->message);
g_error_free (error);
if (data)
{
reply_selection_request (&data->request_event, FALSE);
g_clear_pointer (&selection->wayland_selection,
(GDestroyNotify) wayland_selection_data_free);
}
return;
}
data->buffer_len = bytes_read;
if (bytes_read == INCR_CHUNK_SIZE)
{
if (!data->incr)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
guint32 incr_chunk_size = INCR_CHUNK_SIZE;
/* Not yet in incr */
data->incr = TRUE;
XChangeProperty (xdisplay,
data->request_event.requestor,
data->request_event.property,
gdk_x11_get_xatom_by_name ("INCR"),
32, PropModeReplace,
(guchar *) &incr_chunk_size, 1);
reply_selection_request (&data->request_event, TRUE);
}
else
wayland_selection_update_x11_property (data);
}
else
{
if (!data->incr)
{
/* Non-incr transfer finished */
wayland_selection_update_x11_property (data);
reply_selection_request (&data->request_event, TRUE);
}
else if (data->incr)
{
/* Incr transfer complete, setting a new property */
wayland_selection_update_x11_property (data);
if (bytes_read > 0)
return;
}
g_clear_pointer (&selection->wayland_selection,
(GDestroyNotify) wayland_selection_data_free);
}
}
static void
wayland_selection_data_read (MetaSelectionBridge *selection)
{
WaylandSelectionData *data = selection->wayland_selection;
g_input_stream_read_async (data->stream, data->buffer,
INCR_CHUNK_SIZE, G_PRIORITY_DEFAULT,
data->cancellable,
wayland_data_read_cb, selection);
}
static void
meta_xwayland_selection_get_incr_chunk (MetaWaylandCompositor *compositor,
MetaSelectionBridge *selection)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
gulong nitems_ret, bytes_after_ret;
guchar *prop_ret;
int format_ret;
Atom type_ret;
XGetWindowProperty (xdisplay,
selection->window,
gdk_x11_get_xatom_by_name ("_META_SELECTION"),
0, /* offset */
0x1fffffff, /* length */
False, /* delete */
AnyPropertyType,
&type_ret,
&format_ret,
&nitems_ret,
&bytes_after_ret,
&prop_ret);
if (nitems_ret > 0)
{
x11_selection_data_write (selection, prop_ret, nitems_ret);
}
else
{
/* Transfer has completed */
x11_selection_data_close (selection->x11_selection);
x11_selection_data_finish (selection, TRUE);
}
XFree (prop_ret);
}
static void
meta_x11_source_send (MetaWaylandDataSource *source,
const gchar *mime_type,
gint fd)
{
MetaWaylandDataSourceXWayland *source_xwayland =
META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
MetaSelectionBridge *selection = source_xwayland->selection;
Atom type_atom;
if (source_xwayland->has_utf8_string_atom &&
strcmp (mime_type, "text/plain;charset=utf-8") == 0)
type_atom = gdk_x11_get_xatom_by_name ("UTF8_STRING");
else
type_atom = gdk_x11_get_xatom_by_name (mime_type);
/* Ensure we close previous transactions */
x11_selection_data_finish (selection, FALSE);
/* Takes ownership of fd */
selection->x11_selection =
x11_selection_data_new (compositor->xwayland_manager.selection_data,
fd, mime_type);
XConvertSelection (xdisplay,
selection->selection_atom, type_atom,
gdk_x11_get_xatom_by_name ("_META_SELECTION"),
selection->window,
CurrentTime);
XFlush (xdisplay);
}
static void
meta_x11_source_target (MetaWaylandDataSource *source,
const gchar *mime_type)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaWaylandDataSourceXWayland *source_xwayland =
META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
MetaSelectionBridge *selection = source_xwayland->selection;
uint32_t action = 0;
if (selection->selection_atom == xdnd_atoms[ATOM_DND_SELECTION])
{
if (mime_type)
action = meta_wayland_data_source_get_current_action (source);
xdnd_send_status (compositor->xwayland_manager.selection_data,
selection->owner, action);
}
}
static void
meta_x11_source_cancel (MetaWaylandDataSource *source)
{
MetaWaylandDataSourceXWayland *source_xwayland =
META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
MetaSelectionBridge *selection = source_xwayland->selection;
x11_selection_data_send_finished (selection, FALSE);
g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free);
}
static void
meta_x11_source_action (MetaWaylandDataSource *source,
uint32_t action)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaWaylandDataSourceXWayland *source_xwayland =
META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
MetaSelectionBridge *selection = source_xwayland->selection;
if (selection->selection_atom == xdnd_atoms[ATOM_DND_SELECTION])
{
if (!meta_wayland_data_source_has_target (source))
action = 0;
xdnd_send_status (compositor->xwayland_manager.selection_data,
selection->owner, action);
}
}
static void
meta_x11_source_drop_performed (MetaWaylandDataSource *source)
{
}
static void
meta_x11_source_drag_finished (MetaWaylandDataSource *source)
{
MetaWaylandDataSourceXWayland *source_xwayland =
META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
MetaSelectionBridge *selection = source_xwayland->selection;
if (selection->x11_selection)
x11_selection_data_send_finished (selection, TRUE);
}
static void
meta_wayland_data_source_xwayland_init (MetaWaylandDataSourceXWayland *source_xwayland)
{
}
static void
meta_wayland_data_source_xwayland_class_init (MetaWaylandDataSourceXWaylandClass *klass)
{
MetaWaylandDataSourceClass *data_source_class =
META_WAYLAND_DATA_SOURCE_CLASS (klass);
data_source_class->send = meta_x11_source_send;
data_source_class->target = meta_x11_source_target;
data_source_class->cancel = meta_x11_source_cancel;
data_source_class->action = meta_x11_source_action;
data_source_class->drop_performed = meta_x11_source_drop_performed;
data_source_class->drag_finished = meta_x11_source_drag_finished;
}
static MetaWaylandDataSource *
meta_wayland_data_source_xwayland_new (MetaSelectionBridge *selection)
{
MetaWaylandDataSourceXWayland *source_xwayland;
source_xwayland = g_object_new (META_TYPE_WAYLAND_DATA_SOURCE_XWAYLAND, NULL);
source_xwayland->selection = selection;
return META_WAYLAND_DATA_SOURCE (source_xwayland);
}
static void
meta_x11_drag_dest_focus_in (MetaWaylandDataDevice *data_device,
MetaWaylandSurface *surface,
MetaWaylandDataOffer *offer)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
compositor->xwayland_manager.selection_data->dnd.dnd_dest = surface->window->xwindow;
xdnd_send_enter (compositor->xwayland_manager.selection_data,
compositor->xwayland_manager.selection_data->dnd.dnd_dest);
}
static void
meta_x11_drag_dest_focus_out (MetaWaylandDataDevice *data_device,
MetaWaylandSurface *surface)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
xdnd_send_leave (compositor->xwayland_manager.selection_data,
compositor->xwayland_manager.selection_data->dnd.dnd_dest);
compositor->xwayland_manager.selection_data->dnd.dnd_dest = None;
}
static void
meta_x11_drag_dest_motion (MetaWaylandDataDevice *data_device,
MetaWaylandSurface *surface,
const ClutterEvent *event)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
guint32 time;
gfloat x, y;
time = clutter_event_get_time (event);
clutter_event_get_coords (event, &x, &y);
xdnd_send_position (compositor->xwayland_manager.selection_data,
compositor->xwayland_manager.selection_data->dnd.dnd_dest,
time, x, y);
}
static void
meta_x11_drag_dest_drop (MetaWaylandDataDevice *data_device,
MetaWaylandSurface *surface)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
xdnd_send_drop (compositor->xwayland_manager.selection_data,
compositor->xwayland_manager.selection_data->dnd.dnd_dest,
meta_display_get_current_time_roundtrip (meta_get_display ()));
}
static void
meta_x11_drag_dest_update (MetaWaylandDataDevice *data_device,
MetaWaylandSurface *surface)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaWaylandSeat *seat = compositor->seat;
ClutterPoint pos;
clutter_input_device_get_coords (seat->pointer.device, NULL, &pos);
xdnd_send_position (compositor->xwayland_manager.selection_data,
compositor->xwayland_manager.selection_data->dnd.dnd_dest,
clutter_get_current_event_time (),
pos.x, pos.y);
}
static const MetaWaylandDragDestFuncs meta_x11_drag_dest_funcs = {
meta_x11_drag_dest_focus_in,
meta_x11_drag_dest_focus_out,
meta_x11_drag_dest_motion,
meta_x11_drag_dest_drop,
meta_x11_drag_dest_update
};
const MetaWaylandDragDestFuncs *
meta_xwayland_selection_get_drag_dest_funcs (void)
{
return &meta_x11_drag_dest_funcs;
}
static gboolean
meta_xwayland_data_source_fetch_mimetype_list (MetaWaylandDataSource *source,
Window window,
Atom prop)
{
MetaWaylandDataSourceXWayland *source_xwayland =
META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
gulong nitems_ret, bytes_after_ret, i;
Atom *atoms, type_ret, utf8_string;
int format_ret;
struct wl_array *source_mime_types;
source_mime_types = meta_wayland_data_source_get_mime_types (source);
if (source_mime_types->size != 0)
return TRUE;
utf8_string = gdk_x11_get_xatom_by_name ("UTF8_STRING");
XGetWindowProperty (xdisplay, window, prop,
0, /* offset */
0x1fffffff, /* length */
True, /* delete */
AnyPropertyType,
&type_ret,
&format_ret,
&nitems_ret,
&bytes_after_ret,
(guchar **) &atoms);
if (nitems_ret == 0 || type_ret != XA_ATOM)
{
XFree (atoms);
return FALSE;
}
for (i = 0; i < nitems_ret; i++)
{
const gchar *mime_type;
if (atoms[i] == utf8_string)
{
meta_wayland_data_source_add_mime_type (source,
"text/plain;charset=utf-8");
source_xwayland->has_utf8_string_atom = TRUE;
}
mime_type = gdk_x11_get_xatom_name (atoms[i]);
meta_wayland_data_source_add_mime_type (source, mime_type);
}
XFree (atoms);
return TRUE;
}
static void
meta_xwayland_selection_get_x11_targets (MetaWaylandCompositor *compositor,
MetaSelectionBridge *selection)
{
MetaWaylandDataSource *data_source;
data_source = meta_wayland_data_source_xwayland_new (selection);
if (meta_xwayland_data_source_fetch_mimetype_list (data_source,
selection->window,
gdk_x11_get_xatom_by_name ("_META_SELECTION")))
{
g_clear_object (&selection->source);
selection->source = data_source;
if (selection->selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD"))
{
meta_wayland_data_device_set_selection (&compositor->seat->data_device, data_source,
wl_display_next_serial (compositor->wayland_display));
}
}
else
g_object_unref (data_source);
}
static void
meta_xwayland_selection_get_x11_data (MetaWaylandCompositor *compositor,
MetaSelectionBridge *selection)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
gulong nitems_ret, bytes_after_ret;
guchar *prop_ret;
int format_ret;
Atom type_ret;
if (!selection->x11_selection)
return;
XGetWindowProperty (xdisplay,
selection->window,
gdk_x11_get_xatom_by_name ("_META_SELECTION"),
0, /* offset */
0x1fffffff, /* length */
True, /* delete */
AnyPropertyType,
&type_ret,
&format_ret,
&nitems_ret,
&bytes_after_ret,
&prop_ret);
selection->x11_selection->incr = (type_ret == gdk_x11_get_xatom_by_name ("INCR"));
if (selection->x11_selection->incr)
return;
if (type_ret == gdk_x11_get_xatom_by_name (selection->x11_selection->mime_type))
x11_selection_data_write (selection, prop_ret, nitems_ret);
XFree (prop_ret);
}
static gboolean
meta_xwayland_selection_handle_selection_notify (MetaWaylandCompositor *compositor,
XEvent *xevent)
{
XSelectionEvent *event = (XSelectionEvent *) xevent;
MetaSelectionBridge *selection;
selection = atom_to_selection_bridge (compositor, event->selection);
if (!selection)
return FALSE;
/* convert selection failed */
if (event->property == None)
{
g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free);
return FALSE;
}
if (event->target == gdk_x11_get_xatom_by_name ("TARGETS"))
meta_xwayland_selection_get_x11_targets (compositor, selection);
else
meta_xwayland_selection_get_x11_data (compositor, selection);
return TRUE;
}
static void
meta_xwayland_selection_send_targets (MetaWaylandCompositor *compositor,
const MetaWaylandDataSource *data_source,
Window requestor,
Atom property)
{
Atom *targets;
gchar **p;
int i = 0;
struct wl_array *source_mime_types;
if (!data_source)
return;
source_mime_types = meta_wayland_data_source_get_mime_types (data_source);
if (source_mime_types->size == 0)
return;
/* Make extra room for TIMESTAMP/TARGETS */
targets = g_new (Atom, source_mime_types->size + 2);
wl_array_for_each (p, source_mime_types)
{
targets[i++] = gdk_x11_get_xatom_by_name (*p);
}
targets[i++] = gdk_x11_get_xatom_by_name ("TIMESTAMP");
targets[i++] = gdk_x11_get_xatom_by_name ("TARGETS");
XChangeProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
requestor, property,
XA_ATOM, 32, PropModeReplace,
(guchar *) targets, i);
g_free (targets);
}
static void
meta_xwayland_selection_send_timestamp (MetaWaylandCompositor *compositor,
Window requestor,
Atom property,
Time timestamp)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
XChangeProperty (xdisplay, requestor, property,
XA_INTEGER, 32,
PropModeReplace,
(guchar *) &timestamp, 1);
}
static void
meta_xwayland_selection_send_incr_chunk (MetaWaylandCompositor *compositor,
MetaSelectionBridge *selection)
{
if (!selection->wayland_selection)
return;
if (selection->wayland_selection->buffer_len > 0)
wayland_selection_update_x11_property (selection->wayland_selection);
else
wayland_selection_data_read (selection);
}
static gboolean
handle_incr_chunk (MetaWaylandCompositor *compositor,
MetaSelectionBridge *selection,
XPropertyEvent *event)
{
if (selection->x11_selection &&
selection->x11_selection->incr &&
event->window == selection->owner &&
event->state == PropertyNewValue &&
event->atom == gdk_x11_get_xatom_by_name ("_META_SELECTION"))
{
/* X11 to Wayland */
meta_xwayland_selection_get_incr_chunk (compositor, selection);
return TRUE;
}
else if (selection->wayland_selection &&
selection->wayland_selection->incr &&
event->window == selection->window &&
event->state == PropertyDelete &&
event->atom == selection->wayland_selection->request_event.property)
{
/* Wayland to X11 */
meta_xwayland_selection_send_incr_chunk (compositor, selection);
return TRUE;
}
return FALSE;
}
static gboolean
meta_xwayland_selection_handle_property_notify (MetaWaylandCompositor *compositor,
XEvent *xevent)
{
MetaXWaylandSelection *selection_data = compositor->xwayland_manager.selection_data;
XPropertyEvent *event = (XPropertyEvent *) xevent;
return handle_incr_chunk (compositor, &selection_data->clipboard, event);
}
static gboolean
meta_xwayland_selection_handle_selection_request (MetaWaylandCompositor *compositor,
XEvent *xevent)
{
XSelectionRequestEvent *event = (XSelectionRequestEvent *) xevent;
MetaWaylandDataSource *data_source;
MetaSelectionBridge *selection;
selection = atom_to_selection_bridge (compositor, event->selection);
if (!selection)
return FALSE;
/* We must fetch from the currently active source, not the Xwayland one */
data_source = data_device_get_active_source_for_atom (&compositor->seat->data_device,
selection->selection_atom);
if (!data_source)
return FALSE;
g_clear_pointer (&selection->wayland_selection,
(GDestroyNotify) wayland_selection_data_free);
if (event->target == gdk_x11_get_xatom_by_name ("TARGETS"))
{
meta_xwayland_selection_send_targets (compositor,
data_source,
event->requestor,
event->property);
reply_selection_request (event, TRUE);
}
else if (event->target == gdk_x11_get_xatom_by_name ("TIMESTAMP"))
{
meta_xwayland_selection_send_timestamp (compositor,
event->requestor, event->property,
selection->timestamp);
reply_selection_request (event, TRUE);
}
else if (data_source && event->target == gdk_x11_get_xatom_by_name ("DELETE"))
{
reply_selection_request (event, TRUE);
}
else
{
if (data_source &&
meta_wayland_data_source_has_mime_type (data_source,
gdk_x11_get_xatom_name (event->target)))
{
selection->wayland_selection = wayland_selection_data_new (event,
compositor);
if (selection->wayland_selection)
wayland_selection_data_read (selection);
}
if (!selection->wayland_selection)
reply_selection_request (event, FALSE);
}
return TRUE;
}
static MetaWaylandSurface *
pick_drop_surface (MetaWaylandCompositor *compositor,
const ClutterEvent *event)
{
MetaDisplay *display = meta_get_display ();
MetaWindow *focus_window = NULL;
ClutterPoint pos;
clutter_event_get_coords (event, &pos.x, &pos.y);
focus_window = meta_stack_get_default_focus_window_at_point (display->screen->stack,
NULL, NULL,
pos.x, pos.y);
return focus_window ? focus_window->surface : NULL;
}
static void
repick_drop_surface (MetaWaylandCompositor *compositor,
MetaWaylandDragGrab *drag_grab,
const ClutterEvent *event)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
MetaDndBridge *dnd = &compositor->xwayland_manager.selection_data->dnd;
MetaWaylandSurface *focus = NULL;
focus = pick_drop_surface (compositor, event);
dnd->focus_surface = focus;
if (meta_wayland_drag_grab_get_focus (drag_grab) == focus)
return;
if (focus &&
focus->window->client_type == META_WINDOW_CLIENT_TYPE_WAYLAND)
{
XMapWindow (xdisplay, dnd->dnd_window);
XMoveResizeWindow (xdisplay, dnd->dnd_window,
focus->window->rect.x,
focus->window->rect.y,
focus->window->rect.width,
focus->window->rect.height);
}
else
{
XMoveResizeWindow (xdisplay, dnd->dnd_window, -1, -1, 1, 1);
XUnmapWindow (xdisplay, dnd->dnd_window);
}
}
static void
drag_xgrab_focus (MetaWaylandPointerGrab *grab,
MetaWaylandSurface *surface)
{
/* Do not update the focus here. First, the surface may perfectly
* be the X11 source DnD icon window's, so we can only be fooled
* here. Second, delaying focus handling to XdndEnter/Leave
* makes us do the negotiation orderly on the X11 side.
*/
}
static void
drag_xgrab_motion (MetaWaylandPointerGrab *grab,
const ClutterEvent *event)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaDndBridge *dnd = &compositor->xwayland_manager.selection_data->dnd;
MetaWaylandSeat *seat = compositor->seat;
repick_drop_surface (compositor,
(MetaWaylandDragGrab *) grab,
event);
dnd->last_motion_time = clutter_event_get_time (event);
meta_wayland_pointer_send_motion (&seat->pointer, event);
}
static void
drag_xgrab_button (MetaWaylandPointerGrab *grab,
const ClutterEvent *event)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaWaylandSeat *seat = compositor->seat;
meta_wayland_pointer_send_button (&seat->pointer, event);
}
static const MetaWaylandPointerGrabInterface drag_xgrab_interface = {
drag_xgrab_focus,
drag_xgrab_motion,
drag_xgrab_button,
};
static gboolean
meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor,
XEvent *xevent)
{
XClientMessageEvent *event = (XClientMessageEvent *) xevent;
MetaDndBridge *dnd = &compositor->xwayland_manager.selection_data->dnd;
MetaWaylandSeat *seat = compositor->seat;
/* Source side messages */
if (event->window == dnd->selection.window)
{
MetaWaylandDataSource *data_source;
uint32_t action = 0;
data_source = compositor->seat->data_device.dnd_data_source;
if (!data_source)
return FALSE;
if (event->message_type == xdnd_atoms[ATOM_DND_STATUS])
{
/* The first bit in data.l[1] is set if the drag was accepted */
meta_wayland_data_source_set_has_target (data_source,
(event->data.l[1] & 1) != 0);
/* data.l[4] contains the action atom */
if (event->data.l[4])
action = atom_to_action ((Atom) event->data.l[4]);
meta_wayland_data_source_set_current_action (data_source, action);
return TRUE;
}
else if (event->message_type == xdnd_atoms[ATOM_DND_FINISHED])
{
/* Reject messages mid-grab */
if (compositor->seat->data_device.current_grab)
return FALSE;
meta_wayland_data_source_notify_finish (data_source);
return TRUE;
}
}
/* Dest side messages */
else if (dnd->selection.source &&
compositor->seat->data_device.current_grab &&
(Window) event->data.l[0] == dnd->selection.owner)
{
MetaWaylandDragGrab *drag_grab = compositor->seat->data_device.current_grab;
MetaWaylandSurface *drag_focus = meta_wayland_drag_grab_get_focus (drag_grab);
if (!drag_focus &&
event->message_type != xdnd_atoms[ATOM_DND_ENTER])
return FALSE;
if (event->message_type == xdnd_atoms[ATOM_DND_ENTER])
{
/* Bit 1 in data.l[1] determines whether there's 3 or less mimetype
* atoms (and are thus contained in this same message), or whether
* there's more than 3 and we need to check the XdndTypeList property
* for the full list.
*/
if (!(event->data.l[1] & 1))
{
/* Mimetypes are contained in this message */
const gchar *mimetype;
gint i;
struct wl_array *source_mime_types;
/* We only need to fetch once */
source_mime_types =
meta_wayland_data_source_get_mime_types (dnd->selection.source);
if (source_mime_types->size == 0)
{
for (i = 2; i <= 4; i++)
{
if (event->data.l[i] == None)
break;
mimetype = gdk_x11_get_xatom_name (event->data.l[i]);
meta_wayland_data_source_add_mime_type (dnd->selection.source,
mimetype);
}
}
}
else
{
/* Fetch mimetypes from type list */
meta_xwayland_data_source_fetch_mimetype_list (dnd->selection.source,
event->data.l[0],
xdnd_atoms[ATOM_DND_TYPE_LIST]);
}
meta_wayland_drag_grab_set_focus (drag_grab, dnd->focus_surface);
return TRUE;
}
else if (event->message_type == xdnd_atoms[ATOM_DND_POSITION])
{
ClutterEvent *motion;
ClutterPoint pos;
uint32_t action = 0;
motion = clutter_event_new (CLUTTER_MOTION);
clutter_input_device_get_coords (seat->pointer.device, NULL, &pos);
clutter_event_set_coords (motion, pos.x, pos.y);
clutter_event_set_device (motion, seat->pointer.device);
clutter_event_set_source_device (motion, seat->pointer.device);
clutter_event_set_time (motion, dnd->last_motion_time);
action = atom_to_action ((Atom) event->data.l[4]);
meta_wayland_data_source_set_actions (dnd->selection.source, action);
meta_wayland_surface_drag_dest_motion (drag_focus, motion);
xdnd_send_status (compositor->xwayland_manager.selection_data,
(Window) event->data.l[0],
meta_wayland_data_source_get_current_action (dnd->selection.source));
clutter_event_free (motion);
return TRUE;
}
else if (event->message_type == xdnd_atoms[ATOM_DND_LEAVE])
{
meta_wayland_drag_grab_set_focus (drag_grab, NULL);
return TRUE;
}
else if (event->message_type == xdnd_atoms[ATOM_DND_DROP])
{
meta_wayland_surface_drag_dest_drop (drag_focus);
return TRUE;
}
}
return FALSE;
}
static gboolean
meta_xwayland_selection_handle_xfixes_selection_notify (MetaWaylandCompositor *compositor,
XEvent *xevent)
{
XFixesSelectionNotifyEvent *event = (XFixesSelectionNotifyEvent *) xevent;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
MetaSelectionBridge *selection;
selection = atom_to_selection_bridge (compositor, event->selection);
if (!selection)
return FALSE;
if (selection->selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD"))
{
if (event->owner == None)
{
if (selection->source && selection->owner != selection->window)
{
/* An X client went away, clear the selection */
g_clear_object (&selection->source);
}
selection->owner = None;
}
else
{
selection->owner = event->owner;
if (selection->owner == selection->window)
{
/* This our own selection window */
selection->timestamp = event->timestamp;
return TRUE;
}
g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free);
XConvertSelection (xdisplay,
event->selection,
gdk_x11_get_xatom_by_name ("TARGETS"),
gdk_x11_get_xatom_by_name ("_META_SELECTION"),
selection->window,
selection->timestamp);
XFlush (xdisplay);
}
}
else if (selection->selection_atom == xdnd_atoms[ATOM_DND_SELECTION])
{
MetaWaylandDataDevice *data_device = &compositor->seat->data_device;
MetaXWaylandSelection *selection_data = compositor->xwayland_manager.selection_data;
selection->owner = event->owner;
if (event->owner != None && event->owner != selection->window)
{
MetaWaylandSurface *focus;
focus = compositor->seat->pointer.focus_surface;
selection->source = meta_wayland_data_source_xwayland_new (selection);
meta_wayland_data_device_set_dnd_source (&compositor->seat->data_device,
selection->source);
meta_wayland_data_device_start_drag (data_device,
wl_resource_get_client (focus->resource),
&drag_xgrab_interface,
focus, selection->source,
NULL);
}
else if (event->owner == None)
{
meta_wayland_data_device_end_drag (data_device);
XUnmapWindow (xdisplay, selection_data->dnd.dnd_window);
}
}
return TRUE;
}
gboolean
meta_xwayland_selection_handle_event (XEvent *xevent)
{
MetaWaylandCompositor *compositor;
compositor = meta_wayland_compositor_get_default ();
if (!compositor->xwayland_manager.selection_data)
return FALSE;
switch (xevent->type)
{
case SelectionNotify:
return meta_xwayland_selection_handle_selection_notify (compositor, xevent);
case PropertyNotify:
return meta_xwayland_selection_handle_property_notify (compositor, xevent);
case SelectionRequest:
return meta_xwayland_selection_handle_selection_request (compositor, xevent);
case ClientMessage:
return meta_xwayland_selection_handle_client_message (compositor, xevent);
default:
{
MetaDisplay *display = meta_get_display ();
if (xevent->type - display->xfixes_event_base == XFixesSelectionNotify)
return meta_xwayland_selection_handle_xfixes_selection_notify (compositor, xevent);
return FALSE;
}
}
}
static void
meta_selection_bridge_ownership_notify (struct wl_listener *listener,
void *data)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
MetaSelectionBridge *selection =
wl_container_of (listener, selection, ownership_listener);
MetaWaylandDataSource *owner = data;
if (!owner && selection->window == selection->owner)
{
XSetSelectionOwner (xdisplay, selection->selection_atom,
None, selection->timestamp);
}
else if (owner && selection->source != owner)
{
XSetSelectionOwner (xdisplay,
selection->selection_atom,
selection->window,
CurrentTime);
}
}
static void
init_selection_bridge (MetaSelectionBridge *selection,
Atom selection_atom,
struct wl_signal *signal)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
XSetWindowAttributes attributes;
guint mask;
attributes.event_mask = PropertyChangeMask;
selection->ownership_listener.notify = meta_selection_bridge_ownership_notify;
wl_signal_add (signal, &selection->ownership_listener);
selection->selection_atom = selection_atom;
selection->window =
XCreateWindow (xdisplay,
gdk_x11_window_get_xid (gdk_get_default_root_window ()),
-1, -1, 1, 1, /* position */
0, /* border width */
0, /* depth */
InputOnly, /* class */
CopyFromParent, /* visual */
CWEventMask,
&attributes);
mask = XFixesSetSelectionOwnerNotifyMask |
XFixesSelectionWindowDestroyNotifyMask |
XFixesSelectionClientCloseNotifyMask;
XFixesSelectSelectionInput (xdisplay, selection->window,
selection_atom, mask);
}
static void
shutdown_selection_bridge (MetaSelectionBridge *selection)
{
wl_list_remove (&selection->ownership_listener.link);
XDestroyWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
selection->window);
g_clear_pointer (&selection->wayland_selection,
(GDestroyNotify) wayland_selection_data_free);
g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free);
}
void
meta_xwayland_init_selection (void)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaXWaylandManager *manager = &compositor->xwayland_manager;
g_assert (manager->selection_data == NULL);
manager->selection_data = g_slice_new0 (MetaXWaylandSelection);
meta_xwayland_init_dnd (manager);
init_selection_bridge (&manager->selection_data->clipboard,
gdk_x11_get_xatom_by_name ("CLIPBOARD"),
&compositor->seat->data_device.selection_ownership_signal);
init_selection_bridge (&manager->selection_data->dnd.selection,
xdnd_atoms[ATOM_DND_SELECTION],
&compositor->seat->data_device.dnd_ownership_signal);
}
void
meta_xwayland_shutdown_selection (void)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaXWaylandManager *manager = &compositor->xwayland_manager;
MetaXWaylandSelection *selection = manager->selection_data;
g_assert (selection != NULL);
g_clear_object (&selection->clipboard.source);
meta_xwayland_shutdown_dnd (manager);
shutdown_selection_bridge (&selection->clipboard);
shutdown_selection_bridge (&selection->dnd.selection);
g_slice_free (MetaXWaylandSelection, selection);
manager->selection_data = NULL;
}