[gtk+/wayland-selections: 13/17] wayland: Implement drop/destination side of selections



commit d220e2a31ada3e6f4dba788f802b1daebbb26622
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().

 gdk/wayland/gdkprivate-wayland.h   |    4 +
 gdk/wayland/gdkselection-wayland.c |  416 +++++++++++++++++++++++++++++++++++-
 2 files changed, 419 insertions(+), 1 deletions(-)
---
diff --git a/gdk/wayland/gdkprivate-wayland.h b/gdk/wayland/gdkprivate-wayland.h
index e1867f6..4e1b007 100644
--- a/gdk/wayland/gdkprivate-wayland.h
+++ b/gdk/wayland/gdkprivate-wayland.h
@@ -190,4 +190,8 @@ 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);
 
+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 e31623a..e612ab8 100644
--- a/gdk/wayland/gdkselection-wayland.c
+++ b/gdk/wayland/gdkselection-wayland.c
@@ -15,18 +15,325 @@
  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  */
 
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <unistd.h>
+
 #include "config.h"
 
+#include <gio/gunixinputstream.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 _GdkWaylandSelection GdkWaylandSelection;
+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 */
+  GHashTable *data_sources; /* Hashtable of selection_atom->GdkWindow */
+  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;
+  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, requestor);
+}
+
+static gboolean
+selection_buffer_remove_requestor (SelectionBuffer *buffer,
+                                   GdkWindow       *requestor)
+{
+  GList *link = g_list_find (buffer->requestors, requestor);
+
+  if (!link)
+    return FALSE;
+
+  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);
+}
+
+static GdkWaylandSelection *
+gdk_wayland_selection_get (void)
+{
+  static GdkWaylandSelection *selection = NULL;
+
+  if (G_UNLIKELY (!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;
+}
+
+static void
+data_offer_offer (void                 *data,
+                  struct wl_data_offer *wl_data_offer,
+                  const char           *type)
+{
+  GdkWaylandSelection *selection = data;
+
+  selection->targets = g_list_prepend (selection->targets,
+                                       gdk_atom_intern (type, FALSE));
+}
+
+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)
+{
+  GdkWaylandSelection *selection = gdk_wayland_selection_get ();
+
+  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)
+{
+  GdkWaylandSelection *selection = gdk_wayland_selection_get ();
+
+  return selection->offer;
+}
+
+GList *
+gdk_wayland_selection_get_targets (void)
+{
+  GdkWaylandSelection *selection = gdk_wayland_selection_get ();
+
+  return selection->targets;
+}
+
+static SelectionBuffer *
+gdk_wayland_selection_lookup_requestor_buffer (GdkWindow *requestor)
+{
+  GdkWaylandSelection *selection = gdk_wayland_selection_get ();
+  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 +364,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 +409,78 @@ _gdk_wayland_display_convert_selection (GdkDisplay *display,
                                        GdkAtom     target,
                                        guint32     time)
 {
+  GdkWaylandSelection *wayland_selection;
+  SelectionBuffer *buffer_data;
+
+  wayland_selection = gdk_wayland_selection_get ();
+
+  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
+        {
+          pipe2 (pipe_fd, O_CLOEXEC);
+          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 (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]