[mutter] wayland: Add X11/wayland selection interoperation



commit 4fc1811c15f0caf342aa7c87ee75366144bea5a8
Author: Carlos Garnacho <carlosg gnome org>
Date:   Fri Oct 10 18:55:00 2014 +0200

    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

 src/Makefile.am                       |    1 +
 src/wayland/meta-wayland-private.h    |    4 +
 src/wayland/meta-xwayland-private.h   |    5 +
 src/wayland/meta-xwayland-selection.c |  944 +++++++++++++++++++++++++++++++++
 src/wayland/meta-xwayland.c           |    4 +
 src/x11/events.c                      |   10 +
 6 files changed, 968 insertions(+), 0 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 5e7e8a3..301452d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -243,6 +243,7 @@ libmutter_la_SOURCES +=                             \
        wayland/meta-wayland-private.h          \
        wayland/meta-xwayland.c                 \
        wayland/meta-xwayland.h                 \
+       wayland/meta-xwayland-selection.c       \
        wayland/meta-xwayland-private.h         \
        wayland/meta-wayland-buffer.c           \
        wayland/meta-wayland-buffer.h           \
diff --git a/src/wayland/meta-wayland-private.h b/src/wayland/meta-wayland-private.h
index edcc60a..c93fcf2 100644
--- a/src/wayland/meta-wayland-private.h
+++ b/src/wayland/meta-wayland-private.h
@@ -33,6 +33,8 @@
 #include "meta-wayland-surface.h"
 #include "meta-wayland-seat.h"
 
+typedef struct _MetaXWaylandSelection MetaXWaylandSelection;
+
 typedef struct
 {
   struct wl_list link;
@@ -52,6 +54,8 @@ typedef struct
   char *display_name;
 
   GMainLoop *init_loop;
+
+  MetaXWaylandSelection *selection_data;
 } MetaXWaylandManager;
 
 struct _MetaWaylandCompositor
diff --git a/src/wayland/meta-xwayland-private.h b/src/wayland/meta-xwayland-private.h
index 83b2986..594ab1c 100644
--- a/src/wayland/meta-xwayland-private.h
+++ b/src/wayland/meta-xwayland-private.h
@@ -34,4 +34,9 @@ meta_xwayland_complete_init (void);
 void
 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 */
diff --git a/src/wayland/meta-xwayland-selection.c b/src/wayland/meta-xwayland-selection.c
new file mode 100644
index 0000000..c296801
--- /dev/null
+++ b/src/wayland/meta-xwayland-selection.c
@@ -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;
+}
diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c
index a7beb0c..cb70724 100644
--- a/src/wayland/meta-xwayland.c
+++ b/src/wayland/meta-xwayland.c
@@ -522,6 +522,8 @@ meta_xwayland_complete_init (void)
      we won't reset the tty).
   */
   XSetIOErrorHandler (x_io_error);
+
+  meta_xwayland_init_selection ();
 }
 
 void
@@ -529,6 +531,8 @@ meta_xwayland_stop (MetaXWaylandManager *manager)
 {
   char path[256];
 
+  meta_xwayland_shutdown_selection ();
+
   snprintf (path, sizeof path, "/tmp/.X11-unix/X%d", manager->display_index);
   unlink (path);
 
diff --git a/src/x11/events.c b/src/x11/events.c
index 5d5a5dc..f70838b 100644
--- a/src/x11/events.c
+++ b/src/x11/events.c
@@ -39,6 +39,7 @@
 #ifdef HAVE_WAYLAND
 #include "wayland/meta-xwayland.h"
 #include "wayland/meta-wayland-private.h"
+#include "wayland/meta-xwayland-private.h"
 #endif
 
 static XIEvent *
@@ -1676,6 +1677,15 @@ meta_display_handle_xevent (MetaDisplay *display,
     }
 #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->monitor_cache_invalidated = TRUE;
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]