wayland: Add X11/wayland selection interoperation

This piece of code hooks in both wl_data_device and the relevant X
selection events, an X11 Window is set up so it can act as the clipboard
owner when any wayland client owns the selection, reacting to
SelectionRequest events, and returning the data from the wayland client
FD to any X11 requestor through X properties.

In the opposite direction, SelectionNotify messages are received,
which results in the property contents being converted then written
into the wayland requestor's FD.

This code also takes care of the handling incremental transfers through
the INCR property type, reading/writing data chunk by chunk.

https://bugzilla.gnome.org/show_bug.cgi?id=738312
This commit is contained in:
Carlos Garnacho 2014-10-10 18:55:00 +02:00
parent 4b5f5abb4f
commit 4fc1811c15
6 changed files with 968 additions and 0 deletions

View File

@ -243,6 +243,7 @@ libmutter_la_SOURCES += \
wayland/meta-wayland-private.h \ wayland/meta-wayland-private.h \
wayland/meta-xwayland.c \ wayland/meta-xwayland.c \
wayland/meta-xwayland.h \ wayland/meta-xwayland.h \
wayland/meta-xwayland-selection.c \
wayland/meta-xwayland-private.h \ wayland/meta-xwayland-private.h \
wayland/meta-wayland-buffer.c \ wayland/meta-wayland-buffer.c \
wayland/meta-wayland-buffer.h \ wayland/meta-wayland-buffer.h \

View File

@ -33,6 +33,8 @@
#include "meta-wayland-surface.h" #include "meta-wayland-surface.h"
#include "meta-wayland-seat.h" #include "meta-wayland-seat.h"
typedef struct _MetaXWaylandSelection MetaXWaylandSelection;
typedef struct typedef struct
{ {
struct wl_list link; struct wl_list link;
@ -52,6 +54,8 @@ typedef struct
char *display_name; char *display_name;
GMainLoop *init_loop; GMainLoop *init_loop;
MetaXWaylandSelection *selection_data;
} MetaXWaylandManager; } MetaXWaylandManager;
struct _MetaWaylandCompositor struct _MetaWaylandCompositor

View File

@ -34,4 +34,9 @@ meta_xwayland_complete_init (void);
void void
meta_xwayland_stop (MetaXWaylandManager *manager); meta_xwayland_stop (MetaXWaylandManager *manager);
/* wl_data_device/X11 selection interoperation */
void meta_xwayland_init_selection (void);
void meta_xwayland_shutdown_selection (void);
gboolean meta_xwayland_selection_handle_event (XEvent *xevent);
#endif /* META_XWAYLAND_PRIVATE_H */ #endif /* META_XWAYLAND_PRIVATE_H */

View File

@ -0,0 +1,944 @@
/*
* 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 <meta/errors.h>
#include "meta-xwayland-private.h"
#include "meta-wayland-data-device.h"
#define INCR_CHUNK_SIZE (128 * 1024)
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 selection : 3;
guint incr : 1;
} X11SelectionData;
typedef struct {
Atom selection_atom;
Window window;
Window owner;
Time timestamp;
const MetaWaylandDataSource *source;
WaylandSelectionData *wayland_selection;
X11SelectionData *x11_selection;
struct wl_listener ownership_listener;
} MetaSelectionBridge;
struct _MetaXWaylandSelection {
MetaSelectionBridge clipboard;
};
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
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_data_write_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
MetaSelectionBridge *selection = user_data;
X11SelectionData *data = selection->x11_selection;
GError *error = NULL;
g_output_stream_write_finish (G_OUTPUT_STREAM (object), res, &error);
if (data->incr)
{
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
XDeleteProperty (xdisplay, selection->window,
gdk_x11_get_xatom_by_name ("_META_SELECTION"));
}
if (error)
{
if (error->domain != G_IO_ERROR ||
error->code != G_IO_ERROR_CANCELLED)
g_warning ("Error writing from X11 selection: %s\n", error->message);
g_error_free (error);
}
if (!data->incr)
{
g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free);
}
}
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
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 */
g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free);
}
XFree (prop_ret);
}
static void
meta_x11_source_send (MetaWaylandDataSource *source,
const gchar *mime_type,
gint fd)
{
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
MetaSelectionBridge *selection = source->user_data;
Atom type_atom;
if (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);
g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free);
/* 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)
{
}
static void
meta_x11_source_cancel (MetaWaylandDataSource *source)
{
MetaSelectionBridge *selection = source->user_data;
g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free);
}
static const MetaWaylandDataSourceFuncs meta_x11_source_funcs = {
meta_x11_source_send,
meta_x11_source_target,
meta_x11_source_cancel
};
static gboolean
meta_xwayland_data_source_fetch_mimetype_list (MetaWaylandDataSource *source,
Window window,
Atom prop)
{
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;
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)
mime_type = "text/plain;charset=utf-8";
else
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_new (&meta_x11_source_funcs,
NULL, selection);
if (meta_xwayland_data_source_fetch_mimetype_list (data_source,
selection->window,
gdk_x11_get_xatom_by_name ("_META_SELECTION")))
{
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
{
meta_wayland_data_source_free (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;
if (!data_source)
return;
if (data_source->mime_types.size == 0)
return;
/* Make extra room for TIMESTAMP/TARGETS */
targets = g_new (Atom, data_source->mime_types.size + 2);
wl_array_for_each (p, &data_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 &&
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 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 (event->owner == None)
{
if (selection->source && selection->owner != selection->window)
{
/* An X client went away, clear the selection */
if (selection->selection_atom == gdk_x11_get_xatom_by_name ("CLIPBOARD"))
{
meta_wayland_data_device_set_selection (&compositor->seat->data_device, NULL,
wl_display_next_serial (compositor->wayland_display));
}
selection->source = NULL;
}
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);
}
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);
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 (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);
init_selection_bridge (&manager->selection_data->clipboard,
gdk_x11_get_xatom_by_name ("CLIPBOARD"),
&compositor->seat->data_device.selection_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);
if (selection->clipboard.source)
{
meta_wayland_data_device_set_selection (&compositor->seat->data_device, NULL,
wl_display_next_serial (compositor->wayland_display));
}
shutdown_selection_bridge (&selection->clipboard);
g_slice_free (MetaXWaylandSelection, selection);
manager->selection_data = NULL;
}

View File

@ -522,6 +522,8 @@ meta_xwayland_complete_init (void)
we won't reset the tty). we won't reset the tty).
*/ */
XSetIOErrorHandler (x_io_error); XSetIOErrorHandler (x_io_error);
meta_xwayland_init_selection ();
} }
void void
@ -529,6 +531,8 @@ meta_xwayland_stop (MetaXWaylandManager *manager)
{ {
char path[256]; char path[256];
meta_xwayland_shutdown_selection ();
snprintf (path, sizeof path, "/tmp/.X11-unix/X%d", manager->display_index); snprintf (path, sizeof path, "/tmp/.X11-unix/X%d", manager->display_index);
unlink (path); unlink (path);

View File

@ -39,6 +39,7 @@
#ifdef HAVE_WAYLAND #ifdef HAVE_WAYLAND
#include "wayland/meta-xwayland.h" #include "wayland/meta-xwayland.h"
#include "wayland/meta-wayland-private.h" #include "wayland/meta-wayland-private.h"
#include "wayland/meta-xwayland-private.h"
#endif #endif
static XIEvent * static XIEvent *
@ -1676,6 +1677,15 @@ meta_display_handle_xevent (MetaDisplay *display,
} }
#endif #endif
#ifdef HAVE_WAYLAND
if (meta_is_wayland_compositor () &&
meta_xwayland_selection_handle_event (event))
{
bypass_gtk = bypass_compositor = TRUE;
goto out;
}
#endif
display->current_time = event_get_time (display, event); display->current_time = event_get_time (display, event);
display->monitor_cache_invalidated = TRUE; display->monitor_cache_invalidated = TRUE;