[gtk+/wayland-selections: 6/15] wayland: Implement drop/destination side of selections



commit 3fe12c890406e7eff368c23ca18aa8048bf9c5c3
Author: Carlos Garnacho <carlosg gnome org>
Date:   Thu Aug 21 18:34:01 2014 +0200

    wayland: Implement drop/destination side of selections
    
    This implementation makes the destination side of selections work
    similarly to X11's, gdk_selection_convert() triggers data fetching,
    which is notified through GDK_SELECTION_NOTIFY events on arrival,
    the buffered data is then available through gdk_selection_property_get().
    
    https://bugzilla.gnome.org/show_bug.cgi?id=697855

 gdk/wayland/gdkdisplay-wayland.c   |   16 ++
 gdk/wayland/gdkdisplay-wayland.h   |    4 +
 gdk/wayland/gdkprivate-wayland.h   |    8 +
 gdk/wayland/gdkselection-wayland.c |  442 +++++++++++++++++++++++++++++++++++-
 4 files changed, 469 insertions(+), 1 deletions(-)
---
diff --git a/gdk/wayland/gdkdisplay-wayland.c b/gdk/wayland/gdkdisplay-wayland.c
index 0cc7eaa..ea701c8 100644
--- a/gdk/wayland/gdkdisplay-wayland.c
+++ b/gdk/wayland/gdkdisplay-wayland.c
@@ -239,6 +239,8 @@ _gdk_wayland_display_open (const gchar *display_name)
 
   gdk_input_init (display);
 
+  display_wayland->selection = gdk_wayland_selection_new ();
+
   g_signal_emit_by_name (display, "opened");
 
   return display;
@@ -261,6 +263,12 @@ gdk_wayland_display_dispose (GObject *object)
       display_wayland->event_source = NULL;
     }
 
+  if (display_wayland->selection)
+    {
+      gdk_wayland_selection_free (display_wayland->selection);
+      display_wayland->selection = NULL;
+    }
+
   G_OBJECT_CLASS (gdk_wayland_display_parent_class)->dispose (object);
 }
 
@@ -894,3 +902,11 @@ _gdk_wayland_shm_surface_get_busy (cairo_surface_t *surface)
   GdkWaylandCairoSurfaceData *data = cairo_surface_get_user_data (surface, &gdk_wayland_cairo_key);
   return data->busy;
 }
+
+GdkWaylandSelection *
+gdk_wayland_display_get_selection (GdkDisplay *display)
+{
+  GdkWaylandDisplay *wayland_display = GDK_WAYLAND_DISPLAY (display);
+
+  return wayland_display->selection;
+}
diff --git a/gdk/wayland/gdkdisplay-wayland.h b/gdk/wayland/gdkdisplay-wayland.h
index 8393b23..18f38df 100644
--- a/gdk/wayland/gdkdisplay-wayland.h
+++ b/gdk/wayland/gdkdisplay-wayland.h
@@ -39,6 +39,8 @@
 
 G_BEGIN_DECLS
 
+typedef struct _GdkWaylandSelection GdkWaylandSelection;
+
 struct _GdkWaylandDisplay
 {
   GdkDisplay parent_instance;
@@ -72,6 +74,8 @@ struct _GdkWaylandDisplay
   int compositor_version;
 
   struct xkb_context *xkb_context;
+
+  GdkWaylandSelection *selection;
 };
 
 struct _GdkWaylandDisplayClass
diff --git a/gdk/wayland/gdkprivate-wayland.h b/gdk/wayland/gdkprivate-wayland.h
index e56592e..c04d257 100644
--- a/gdk/wayland/gdkprivate-wayland.h
+++ b/gdk/wayland/gdkprivate-wayland.h
@@ -193,4 +193,12 @@ struct wl_buffer *_gdk_wayland_shm_surface_get_wl_buffer (cairo_surface_t *surfa
 void _gdk_wayland_shm_surface_set_busy (cairo_surface_t *surface);
 gboolean _gdk_wayland_shm_surface_get_busy (cairo_surface_t *surface);
 
+GdkWaylandSelection * gdk_wayland_display_get_selection (GdkDisplay *display);
+GdkWaylandSelection * gdk_wayland_selection_new (void);
+void gdk_wayland_selection_free (GdkWaylandSelection *selection);
+
+void gdk_wayland_selection_set_offer (struct wl_data_offer *offer);
+struct wl_data_offer * gdk_wayland_selection_get_offer (void);
+GList * gdk_wayland_selection_get_targets (void);
+
 #endif /* __GDK_PRIVATE_WAYLAND_H__ */
diff --git a/gdk/wayland/gdkselection-wayland.c b/gdk/wayland/gdkselection-wayland.c
index 6b1b44f..e8419bb 100644
--- a/gdk/wayland/gdkselection-wayland.c
+++ b/gdk/wayland/gdkselection-wayland.c
@@ -17,16 +17,348 @@
 
 #include "config.h"
 
+#include <fcntl.h>
+
+#include <gio/gunixinputstream.h>
+#include <glib-unix.h>
+
+#include "gdkwayland.h"
+#include "gdkprivate-wayland.h"
+#include "gdkdisplay-wayland.h"
 #include "gdkselection.h"
 #include "gdkproperty.h"
 #include "gdkprivate.h"
 
 #include <string.h>
 
+typedef struct _SelectionBuffer SelectionBuffer;
+typedef struct _StoredSelection StoredSelection;
+
+struct _SelectionBuffer
+{
+  GInputStream *stream;
+  GCancellable *cancellable;
+  GByteArray *data;
+  GList *requestors;
+  GdkAtom selection;
+  GdkAtom target;
+  gint ref_count;
+};
+
+struct _StoredSelection
+{
+  GdkWindow *source;
+  guchar *data;
+  gsize data_len;
+  GdkAtom type;
+  gint fd;
+};
+
+struct _DataSourceData
+{
+  GdkWindow *window;
+  GdkAtom selection;
+};
+
+struct _GdkWaylandSelection
+{
+  /* Destination-side data */
+  struct wl_data_offer *offer;
+  GdkAtom source_requested_target;
+
+  GHashTable *selection_buffers; /* Hashtable of target_atom->SelectionBuffer */
+  GList *targets; /* List of GdkAtom */
+
+  /* Source-side data */
+  StoredSelection stored_selection;
+
+  struct wl_data_source *clipboard_source;
+  GdkWindow *clipboard_owner;
+
+  struct wl_data_source *dnd_source; /* Owned by the GdkDragContext */
+  GdkWindow *dnd_owner;
+};
+
+static void selection_buffer_read (SelectionBuffer *buffer);
+
+static void
+selection_buffer_notify (SelectionBuffer *buffer)
+{
+  GdkEvent *event;
+  GList *l;
+
+  for (l = buffer->requestors; l; l = l->next)
+    {
+      event = gdk_event_new (GDK_SELECTION_NOTIFY);
+      event->selection.window = g_object_ref (l->data);
+      event->selection.send_event = FALSE;
+      event->selection.selection = buffer->selection;
+      event->selection.target = buffer->target;
+      event->selection.property = gdk_atom_intern_static_string ("GDK_SELECTION");
+      event->selection.time = GDK_CURRENT_TIME;
+      event->selection.requestor = g_object_ref (l->data);
+
+      gdk_event_put (event);
+      gdk_event_free (event);
+    }
+}
+
+static SelectionBuffer *
+selection_buffer_new (GInputStream *stream,
+                      GdkAtom       selection,
+                      GdkAtom       target)
+{
+  SelectionBuffer *buffer;
+
+  buffer = g_new0 (SelectionBuffer, 1);
+  buffer->stream = (stream) ? g_object_ref (stream) : NULL;
+  buffer->cancellable = g_cancellable_new ();
+  buffer->data = g_byte_array_new ();
+  buffer->selection = selection;
+  buffer->target = target;
+  buffer->ref_count = 1;
+
+  if (stream)
+    selection_buffer_read (buffer);
+
+  return buffer;
+}
+
+static SelectionBuffer *
+selection_buffer_ref (SelectionBuffer *buffer)
+{
+  buffer->ref_count++;
+  return buffer;
+}
+
+static void
+selection_buffer_unref (SelectionBuffer *buffer_data)
+{
+  buffer_data->ref_count--;
+
+  if (buffer_data->ref_count != 0)
+    return;
+
+  if (buffer_data->cancellable)
+    g_object_unref (buffer_data->cancellable);
+
+  if (buffer_data->stream)
+    g_object_unref (buffer_data->stream);
+
+  if (buffer_data->data)
+    g_byte_array_unref (buffer_data->data);
+
+  g_free (buffer_data);
+}
+
+static void
+selection_buffer_append_data (SelectionBuffer *buffer,
+                              gconstpointer    data,
+                              gsize            len)
+{
+  g_byte_array_append (buffer->data, data, len);
+}
+
+static void
+selection_buffer_cancel_and_unref (SelectionBuffer *buffer_data)
+{
+  if (buffer_data->cancellable)
+    g_cancellable_cancel (buffer_data->cancellable);
+
+  selection_buffer_unref (buffer_data);
+}
+
+static void
+selection_buffer_add_requestor (SelectionBuffer *buffer,
+                                GdkWindow       *requestor)
+{
+  if (!g_list_find (buffer->requestors, requestor))
+    buffer->requestors = g_list_prepend (buffer->requestors,
+                                         g_object_ref (requestor));
+}
+
+static gboolean
+selection_buffer_remove_requestor (SelectionBuffer *buffer,
+                                   GdkWindow       *requestor)
+{
+  GList *link = g_list_find (buffer->requestors, requestor);
+
+  if (!link)
+    return FALSE;
+
+  g_object_unref (link->data);
+  buffer->requestors = g_list_delete_link (buffer->requestors, link);
+  return TRUE;
+}
+
+static void
+selection_buffer_read_cb (GObject      *object,
+                          GAsyncResult *result,
+                          gpointer      user_data)
+{
+  SelectionBuffer *buffer = user_data;
+  GError *error = NULL;
+  GBytes *bytes;
+
+  bytes = g_input_stream_read_bytes_finish (buffer->stream, result, &error);
+
+  if (bytes && g_bytes_get_size (bytes) > 0)
+    {
+      selection_buffer_append_data (buffer,
+                                    g_bytes_get_data (bytes, NULL),
+                                    g_bytes_get_size (bytes));
+      selection_buffer_read (buffer);
+      g_bytes_unref (bytes);
+    }
+  else
+    {
+      if (error)
+        {
+          g_warning (G_STRLOC ": error reading selection buffer: %s\n", error->message);
+          g_error_free (error);
+        }
+      else
+        selection_buffer_notify (buffer);
+
+      g_input_stream_close (buffer->stream, NULL, NULL);
+      g_clear_object (&buffer->stream);
+      g_clear_object (&buffer->cancellable);
+
+      if (bytes)
+        g_bytes_unref (bytes);
+    }
+
+  selection_buffer_unref (buffer);
+}
+
+static void
+selection_buffer_read (SelectionBuffer *buffer)
+{
+  selection_buffer_ref (buffer);
+  g_input_stream_read_bytes_async (buffer->stream, 1000, G_PRIORITY_DEFAULT,
+                                   buffer->cancellable, selection_buffer_read_cb,
+                                   buffer);
+}
+
+GdkWaylandSelection *
+gdk_wayland_selection_new (void)
+{
+  GdkWaylandSelection *selection;
+
+  selection = g_new0 (GdkWaylandSelection, 1);
+  selection->selection_buffers = g_hash_table_new_full (NULL, NULL, NULL,
+                                                        (GDestroyNotify) selection_buffer_cancel_and_unref);
+  return selection;
+}
+
+void
+gdk_wayland_selection_free (GdkWaylandSelection *selection)
+{
+  g_hash_table_destroy (selection->selection_buffers);
+
+  if (selection->targets)
+    g_list_free (selection->targets);
+
+  g_free (selection->stored_selection.data);
+
+  if (selection->stored_selection.fd > 0)
+    close (selection->stored_selection.fd);
+
+  if (selection->offer)
+    wl_data_offer_destroy (selection->offer);
+  if (selection->clipboard_source)
+    wl_data_source_destroy (selection->clipboard_source);
+  if (selection->dnd_source)
+    wl_data_source_destroy (selection->dnd_source);
+
+  g_free (selection);
+}
+
+static void
+data_offer_offer (void                 *data,
+                  struct wl_data_offer *wl_data_offer,
+                  const char           *type)
+{
+  GdkWaylandSelection *selection = data;
+  GdkAtom atom = gdk_atom_intern (type, FALSE);
+
+  if (g_list_find (selection->targets, atom))
+    return;
+
+  selection->targets = g_list_prepend (selection->targets, atom);
+}
+
+static const struct wl_data_offer_listener data_offer_listener = {
+  data_offer_offer,
+};
+
+void
+gdk_wayland_selection_set_offer (struct wl_data_offer *wl_offer)
+{
+  GdkDisplay *display = gdk_display_get_default ();
+  GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
+
+  if (selection->offer == wl_offer)
+    return;
+
+  if (selection->offer)
+    wl_data_offer_destroy (selection->offer);
+
+  selection->offer = wl_offer;
+
+  if (wl_offer)
+    wl_data_offer_add_listener (wl_offer,
+                                &data_offer_listener,
+                                selection);
+
+  /* Clear all buffers */
+  g_hash_table_remove_all (selection->selection_buffers);
+  g_list_free (selection->targets);
+  selection->targets = NULL;
+}
+
+struct wl_data_offer *
+gdk_wayland_selection_get_offer (void)
+{
+  GdkDisplay *display = gdk_display_get_default ();
+  GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
+
+  return selection->offer;
+}
+
+GList *
+gdk_wayland_selection_get_targets (void)
+{
+  GdkDisplay *display = gdk_display_get_default ();
+  GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
+
+  return selection->targets;
+}
+
+static SelectionBuffer *
+gdk_wayland_selection_lookup_requestor_buffer (GdkWindow *requestor)
+{
+  GdkDisplay *display = gdk_display_get_default ();
+  GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
+  SelectionBuffer *buffer_data;
+  GHashTableIter iter;
+
+  g_hash_table_iter_init (&iter, selection->selection_buffers);
+
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &buffer_data))
+    {
+      if (g_list_find (buffer_data->requestors, requestor))
+        return buffer_data;
+    }
+
+  return NULL;
+}
+
 GdkWindow *
 _gdk_wayland_display_get_selection_owner (GdkDisplay *display,
                                          GdkAtom     selection)
 {
+  /* On wayland we can't sanely know who's the global selection owner */
   return NULL;
 }
 
@@ -57,7 +389,42 @@ _gdk_wayland_display_get_selection_property (GdkDisplay  *display,
                                             GdkAtom     *ret_type,
                                             gint        *ret_format)
 {
-  return 0;
+  SelectionBuffer *buffer_data;
+  gsize len;
+
+  buffer_data = gdk_wayland_selection_lookup_requestor_buffer (requestor);
+
+  if (!buffer_data)
+    return 0;
+
+  selection_buffer_remove_requestor (buffer_data, requestor);
+  len = buffer_data->data->len;
+
+  if (data)
+    {
+      guchar *buffer;
+
+      buffer = g_new0 (guchar, len + 1);
+      memcpy (buffer, buffer_data->data->data, len);
+      *data = buffer;
+    }
+
+  if (buffer_data->target == gdk_atom_intern_static_string ("TARGETS"))
+    {
+      if (ret_type)
+        *ret_type = GDK_SELECTION_TYPE_ATOM;
+      if (ret_format)
+        *ret_format = 32;
+    }
+  else
+    {
+      if (ret_type)
+        *ret_type = GDK_SELECTION_TYPE_STRING;
+      if (ret_format)
+        *ret_format = 8;
+    }
+
+  return len;
 }
 
 void
@@ -67,6 +434,79 @@ _gdk_wayland_display_convert_selection (GdkDisplay *display,
                                        GdkAtom     target,
                                        guint32     time)
 {
+  GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display);
+  SelectionBuffer *buffer_data;
+
+  if (!wayland_selection->offer)
+    {
+      GdkEvent *event;
+
+      event = gdk_event_new (GDK_SELECTION_NOTIFY);
+      event->selection.window = g_object_ref (requestor);
+      event->selection.send_event = FALSE;
+      event->selection.selection = selection;
+      event->selection.target = target;
+      event->selection.property = GDK_NONE;
+      event->selection.time = GDK_CURRENT_TIME;
+      event->selection.requestor = g_object_ref (requestor);
+
+      gdk_event_put (event);
+      gdk_event_free (event);
+      return;
+    }
+
+  buffer_data = g_hash_table_lookup (wayland_selection->selection_buffers,
+                                     target);
+
+  if (buffer_data)
+    selection_buffer_add_requestor (buffer_data, requestor);
+  else
+    {
+      GInputStream *stream = NULL;
+      int pipe_fd[2], natoms = 0;
+      GdkAtom *atoms = NULL;
+
+      if (target == gdk_atom_intern_static_string ("TARGETS"))
+        {
+          gint i = 0;
+          GList *l;
+
+          natoms = g_list_length (wayland_selection->targets);
+          atoms = g_new0 (GdkAtom, natoms);
+
+          for (l = wayland_selection->targets; l; l = l->next)
+            atoms[i++] = l->data;
+        }
+      else
+        {
+          g_unix_open_pipe (pipe_fd, FD_CLOEXEC, NULL);
+          wl_data_offer_receive (wayland_selection->offer,
+                                 gdk_atom_name (target),
+                                 pipe_fd[1]);
+          stream = g_unix_input_stream_new (pipe_fd[0], TRUE);
+          close (pipe_fd[1]);
+        }
+
+      buffer_data = selection_buffer_new (stream, selection, target);
+      selection_buffer_add_requestor (buffer_data, requestor);
+
+      if (stream)
+        g_object_unref (stream);
+
+      if (atoms)
+        {
+          /* Store directly the local atoms */
+          selection_buffer_append_data (buffer_data, atoms, natoms * sizeof (GdkAtom));
+          g_free (atoms);
+        }
+
+      g_hash_table_insert (wayland_selection->selection_buffers,
+                           GDK_ATOM_TO_POINTER (target),
+                           buffer_data);
+    }
+
+  if (!buffer_data->stream)
+    selection_buffer_notify (buffer_data);
 }
 
 gint


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