[gtk+/wayland-selections: 7/15] wayland: Implement drag/source side of selections



commit e4acfb0c306ec34df23cfe5f79b3b5e8e1e38cd0
Author: Carlos Garnacho <carlosg gnome org>
Date:   Thu Aug 21 18:49:44 2014 +0200

    wayland: Implement drag/source side of selections
    
    This has been made to work similarly to X11, requests for the data device
    contents are notified through GDK_SELECTION_REQUEST events, the data stored
    in the GDK_SELECTION property as a reaction to that event is then stored
    into the wayland selection implementation, and written to the fd when
    requested/available.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=697855

 gdk/wayland/gdkdevice-wayland.c    |   26 +++
 gdk/wayland/gdkprivate-wayland.h   |   13 ++
 gdk/wayland/gdkselection-wayland.c |  337 +++++++++++++++++++++++++++++++++++-
 gdk/wayland/gdkwindow-wayland.c    |    2 +
 4 files changed, 377 insertions(+), 1 deletions(-)
---
diff --git a/gdk/wayland/gdkdevice-wayland.c b/gdk/wayland/gdkdevice-wayland.c
index 756811f..b2be302 100644
--- a/gdk/wayland/gdkdevice-wayland.c
+++ b/gdk/wayland/gdkdevice-wayland.c
@@ -2174,3 +2174,29 @@ gdk_wayland_device_unset_touch_grab (GdkDevice        *gdk_device,
   _gdk_display_end_touch_grab (gdk_device_get_display (gdk_device),
                                gdk_device, sequence);
 }
+
+struct wl_data_device *
+gdk_wayland_device_get_data_device (GdkDevice *gdk_device)
+{
+  GdkWaylandDeviceData *device;
+
+  g_return_val_if_fail (GDK_IS_WAYLAND_DEVICE (gdk_device), NULL);
+  device = GDK_WAYLAND_DEVICE (gdk_device)->device;
+
+  return device->data_device;
+}
+
+void
+gdk_wayland_device_set_selection (GdkDevice             *gdk_device,
+                                  struct wl_data_source *source)
+{
+  GdkWaylandDeviceData *device;
+  GdkWaylandDisplay *display_wayland;
+
+  g_return_if_fail (GDK_IS_WAYLAND_DEVICE (gdk_device));
+  device = GDK_WAYLAND_DEVICE (gdk_device)->device;
+  display_wayland = GDK_WAYLAND_DISPLAY (gdk_device_get_display (gdk_device));
+
+  wl_data_device_set_selection (device->data_device, source,
+                                _gdk_wayland_display_get_serial (display_wayland));
+}
diff --git a/gdk/wayland/gdkprivate-wayland.h b/gdk/wayland/gdkprivate-wayland.h
index c04d257..b72c7a6 100644
--- a/gdk/wayland/gdkprivate-wayland.h
+++ b/gdk/wayland/gdkprivate-wayland.h
@@ -147,6 +147,9 @@ uint32_t _gdk_wayland_device_get_implicit_grab_serial(GdkWaylandDevice *device,
                                                       const GdkEvent   *event);
 uint32_t _gdk_wayland_device_get_last_implicit_grab_serial (GdkWaylandDevice  *device,
                                                             GdkEventSequence **seqence);
+struct wl_data_device * gdk_wayland_device_get_data_device (GdkDevice *gdk_device);
+void gdk_wayland_device_set_selection (GdkDevice             *gdk_device,
+                                       struct wl_data_source *source);
 
 void gdk_wayland_device_unset_touch_grab (GdkDevice        *device,
                                           GdkEventSequence *sequence);
@@ -201,4 +204,14 @@ 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);
 
+void     gdk_wayland_selection_store   (GdkWindow    *window,
+                                        GdkAtom       type,
+                                        GdkPropMode   mode,
+                                        const guchar *data,
+                                        gint          len);
+struct wl_data_source * gdk_wayland_selection_get_data_source (GdkWindow *owner,
+                                                               GdkAtom    selection);
+void gdk_wayland_selection_unset_data_source (GdkAtom selection);
+
+
 #endif /* __GDK_PRIVATE_WAYLAND_H__ */
diff --git a/gdk/wayland/gdkselection-wayland.c b/gdk/wayland/gdkselection-wayland.c
index e8419bb..3c549e0 100644
--- a/gdk/wayland/gdkselection-wayland.c
+++ b/gdk/wayland/gdkselection-wayland.c
@@ -60,6 +60,13 @@ struct _DataSourceData
   GdkAtom selection;
 };
 
+enum {
+  ATOM_CLIPBOARD,
+  ATOM_DND
+};
+
+static GdkAtom atoms[2] = { 0 };
+
 struct _GdkWaylandSelection
 {
   /* Destination-side data */
@@ -245,6 +252,10 @@ gdk_wayland_selection_new (void)
 {
   GdkWaylandSelection *selection;
 
+  /* init atoms */
+  atoms[ATOM_CLIPBOARD] = gdk_atom_intern_static_string ("CLIPBOARD");
+  atoms[ATOM_DND] = gdk_atom_intern_static_string ("GdkWaylandSelection");
+
   selection = g_new0 (GdkWaylandSelection, 1);
   selection->selection_buffers = g_hash_table_new_full (NULL, NULL, NULL,
                                                         (GDestroyNotify) selection_buffer_cancel_and_unref);
@@ -335,6 +346,103 @@ gdk_wayland_selection_get_targets (void)
   return selection->targets;
 }
 
+void
+gdk_wayland_selection_emit_request (GdkWindow *window,
+                                    GdkAtom    selection,
+                                    GdkAtom    target)
+{
+  GdkEvent *event;
+
+  event = gdk_event_new (GDK_SELECTION_REQUEST);
+  event->selection.window = g_object_ref (window);
+  event->selection.send_event = FALSE;
+  event->selection.selection = selection;
+  event->selection.target = target;
+  event->selection.property = gdk_atom_intern_static_string ("GDK_SELECTION");
+  event->selection.time = GDK_CURRENT_TIME;
+  event->selection.requestor = g_object_ref (window);
+
+  gdk_event_put (event);
+  gdk_event_free (event);
+}
+
+gboolean
+gdk_wayland_selection_check_write (GdkWaylandSelection *selection)
+{
+  gssize len, bytes_written = 0;
+  gchar *buf;
+
+  if (selection->stored_selection.fd < 0 ||
+      selection->stored_selection.data_len == 0)
+    return FALSE;
+
+  len = selection->stored_selection.data_len;
+  buf = (gchar *) selection->stored_selection.data;
+
+  while (len > 0)
+    {
+      bytes_written += write (selection->stored_selection.fd,
+                              buf + bytes_written, len);
+
+      if (bytes_written < 0)
+        break;
+
+      len -= bytes_written;
+    }
+
+  close (selection->stored_selection.fd);
+  selection->stored_selection.fd = -1;
+
+  return bytes_written != 0;
+}
+
+void
+gdk_wayland_selection_store (GdkWindow    *window,
+                             GdkAtom       type,
+                             GdkPropMode   mode,
+                             const guchar *data,
+                             gint          len)
+{
+  GdkDisplay *display = gdk_display_get_default ();
+  GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
+  GArray *array;
+
+  array = g_array_new (TRUE, FALSE, sizeof (guchar));
+  g_array_append_vals (array, data, len);
+
+  if (selection->stored_selection.data)
+    {
+      if (mode != GDK_PROP_MODE_REPLACE &&
+          type != selection->stored_selection.type)
+        {
+          g_warning (G_STRLOC ": Attempted to append/prepend selection data with "
+                     "type %s into the current selection with type %s",
+                     gdk_atom_name (type),
+                     gdk_atom_name (selection->stored_selection.type));
+          return;
+        }
+
+      /* In these cases we also replace the stored data, so we
+       * apply the inverse operation into the just given data.
+       */
+      if (mode == GDK_PROP_MODE_APPEND)
+        g_array_prepend_vals (array, selection->stored_selection.data,
+                              selection->stored_selection.data_len - 1);
+      else if (mode == GDK_PROP_MODE_PREPEND)
+        g_array_append_vals (array, selection->stored_selection.data,
+                             selection->stored_selection.data_len - 1);
+
+      g_free (selection->stored_selection.data);
+    }
+
+  selection->stored_selection.source = window;
+  selection->stored_selection.data_len = array->len;
+  selection->stored_selection.data = (guchar *) g_array_free (array, FALSE);
+  selection->stored_selection.type = type;
+
+  gdk_wayland_selection_check_write (selection);
+}
+
 static SelectionBuffer *
 gdk_wayland_selection_lookup_requestor_buffer (GdkWindow *requestor)
 {
@@ -354,6 +462,220 @@ gdk_wayland_selection_lookup_requestor_buffer (GdkWindow *requestor)
   return NULL;
 }
 
+static gboolean
+gdk_wayland_selection_request_target (GdkWaylandSelection *wayland_selection,
+                                      GdkWindow           *window,
+                                      GdkAtom              target,
+                                      gint                 fd)
+{
+  GdkAtom selection;
+
+  if (wayland_selection->clipboard_owner == window)
+    selection = atoms[ATOM_CLIPBOARD];
+  else if (wayland_selection->dnd_owner == window)
+    selection = atoms[ATOM_DND];
+  else
+    return FALSE;
+
+  if (fd >= 0)
+    wayland_selection->stored_selection.fd = fd;
+
+  if (wayland_selection->source_requested_target == target)
+    return FALSE;
+
+  wayland_selection->source_requested_target = target;
+
+  if (target != GDK_NONE)
+    {
+      gdk_wayland_selection_emit_request (window, selection, target);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+data_source_target (void                  *data,
+                    struct wl_data_source *source,
+                    const char            *mime_type)
+{
+  GdkWaylandSelection *wayland_selection = data;
+  GdkDragContext *context;
+  GdkWindow *window;
+
+  g_debug (G_STRLOC ": %s source = %p, mime_type = %s",
+           G_STRFUNC, source, mime_type);
+
+  if (!mime_type)
+    return;
+
+  if (source == wayland_selection->dnd_source)
+    {
+      window = wayland_selection->dnd_owner;
+      context = gdk_wayland_drag_context_lookup_by_data_source (source);
+      _gdk_wayland_drag_context_emit_event (context, GDK_DRAG_STATUS,
+                                            GDK_CURRENT_TIME);
+    }
+  else if (source == wayland_selection->clipboard_source)
+    window = wayland_selection->clipboard_owner;
+  else
+    return;
+
+  gdk_wayland_selection_request_target (wayland_selection, window,
+                                        gdk_atom_intern (mime_type, FALSE),
+                                        -1);
+}
+
+static void
+data_source_send (void                  *data,
+                  struct wl_data_source *source,
+                  const char            *mime_type,
+                  int32_t                fd)
+{
+  GdkWaylandSelection *wayland_selection = data;
+  GdkDragContext *context;
+  GdkWindow *window;
+
+  g_debug (G_STRLOC ": %s source = %p, mime_type = %s, fd = %d",
+           G_STRFUNC, source, mime_type, fd);
+
+  if (!mime_type)
+    return;
+
+  context = gdk_wayland_drag_context_lookup_by_data_source (source);
+
+  if (source == wayland_selection->dnd_source)
+    window = wayland_selection->dnd_owner;
+  else if (source == wayland_selection->clipboard_source)
+    window = wayland_selection->clipboard_owner;
+  else
+    return;
+
+  if (!gdk_wayland_selection_request_target (wayland_selection, window,
+                                             gdk_atom_intern (mime_type, FALSE),
+                                             fd))
+    gdk_wayland_selection_check_write (wayland_selection);
+
+  if (context)
+    {
+      gdk_wayland_drag_context_undo_grab (context);
+      _gdk_wayland_drag_context_emit_event (context, GDK_DROP_FINISHED,
+                                            GDK_CURRENT_TIME);
+    }
+
+  wayland_selection->source_requested_target = GDK_NONE;
+}
+
+static void
+data_source_cancelled (void                  *data,
+                       struct wl_data_source *source)
+{
+  GdkDragContext *context;
+
+  g_debug (G_STRLOC ": %s source = %p",
+           G_STRFUNC, source);
+
+  context = gdk_wayland_drag_context_lookup_by_data_source (source);
+
+  if (context)
+    gdk_wayland_drag_context_undo_grab (context);
+}
+
+static const struct wl_data_source_listener data_source_listener = {
+  data_source_target,
+  data_source_send,
+  data_source_cancelled
+};
+
+struct wl_data_source *
+gdk_wayland_selection_get_data_source (GdkWindow *owner,
+                                       GdkAtom    selection)
+{
+  GdkDisplay *display = gdk_display_get_default ();
+  GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display);
+  struct wl_data_source *source = NULL;
+  GdkWaylandDisplay *display_wayland;
+  gboolean is_clipboard = FALSE;
+
+  if (selection == atoms[ATOM_DND])
+    {
+      if (wayland_selection->dnd_source &&
+          (!owner || owner == wayland_selection->dnd_owner))
+        return wayland_selection->dnd_source;
+    }
+  else if (selection == atoms[ATOM_CLIPBOARD])
+    {
+      if (wayland_selection->clipboard_source &&
+          (!owner || owner == wayland_selection->clipboard_owner))
+        return wayland_selection->clipboard_source;
+
+      if (wayland_selection->clipboard_source)
+        {
+          wl_data_source_destroy (wayland_selection->clipboard_source);
+          wayland_selection->clipboard_source = NULL;
+        }
+
+      is_clipboard = TRUE;
+    }
+  else
+    return NULL;
+
+  if (!owner)
+    return NULL;
+
+  display_wayland = GDK_WAYLAND_DISPLAY (gdk_window_get_display (owner));
+
+  source = wl_data_device_manager_create_data_source (display_wayland->data_device_manager);
+  wl_data_source_add_listener (source,
+                               &data_source_listener,
+                               wayland_selection);
+
+  if (is_clipboard)
+    {
+      wayland_selection->clipboard_source = source;
+      wayland_selection->clipboard_owner = owner;
+    }
+  else
+    {
+      wayland_selection->dnd_source = source;
+      wayland_selection->dnd_owner = owner;
+    }
+
+  return source;
+}
+
+void
+gdk_wayland_selection_unset_data_source (GdkAtom selection)
+{
+  GdkDisplay *display = gdk_display_get_default ();
+  GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display);
+
+  if (selection == atoms[ATOM_CLIPBOARD])
+    {
+      GdkDeviceManager *device_manager;
+      GdkDisplay *display;
+      GdkDevice *device;
+
+      display = gdk_display_get_default ();
+      device_manager = gdk_display_get_device_manager (display);
+      device = gdk_device_manager_get_client_pointer (device_manager);
+
+      gdk_wayland_device_set_selection (device, NULL);
+      wayland_selection->clipboard_owner = NULL;
+
+      if (wayland_selection->clipboard_source)
+        {
+          wl_data_source_destroy (wayland_selection->clipboard_source);
+          wayland_selection->clipboard_source = NULL;
+        }
+    }
+  else if (selection == atoms[ATOM_DND])
+    {
+      wayland_selection->dnd_owner = NULL;
+      wayland_selection->dnd_source = NULL;
+    }
+}
+
 GdkWindow *
 _gdk_wayland_display_get_selection_owner (GdkDisplay *display,
                                          GdkAtom     selection)
@@ -369,7 +691,20 @@ _gdk_wayland_display_set_selection_owner (GdkDisplay *display,
                                          guint32     time,
                                          gboolean    send_event)
 {
-  return TRUE;
+  GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display);
+
+  if (selection == atoms[ATOM_CLIPBOARD])
+    {
+      wayland_selection->clipboard_owner = owner;
+      return TRUE;
+    }
+  else if (selection == atoms[ATOM_DND])
+    {
+      wayland_selection->dnd_owner = owner;
+      return TRUE;
+    }
+
+  return FALSE;
 }
 
 void
diff --git a/gdk/wayland/gdkwindow-wayland.c b/gdk/wayland/gdkwindow-wayland.c
index e8b2ecf..f96b6b8 100644
--- a/gdk/wayland/gdkwindow-wayland.c
+++ b/gdk/wayland/gdkwindow-wayland.c
@@ -1949,6 +1949,8 @@ gdk_wayland_window_change_property (GdkWindow    *window,
                                     const guchar *data,
                                     gint          nelements)
 {
+  if (property == gdk_atom_intern_static_string ("GDK_SELECTION"))
+    gdk_wayland_selection_store (window, type, mode, data, nelements * (format / 8));
 }
 
 static void


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