[gtk+] dnd: Pass content to gdk_drag_begin()



commit 8648d5409ea53ef4bb6ad9b7940c32d663af41e5
Author: Benjamin Otte <otte redhat com>
Date:   Wed Dec 13 15:03:53 2017 +0100

    dnd: Pass content to gdk_drag_begin()
    
    Instead of just passing the GdkContentFormats, we are now passing the
    GdkContentProvider to gdk_drag_begin().
    This means that GDK itself can now query the data from the provider
    directly instead of having to send selection events.
    
    Use this to provide the private API gdk_drag_context_write() that allows
    backends to pass an output stream that this data will be written to.
    Implement this as the mechanism for providing drag data on Wayland.
    
    And to make this all work, implement a content provider named
    GtkDragContent that is implemented by reverting to the old DND
    drag-data-get machinery inside GTK, so for widgets everything works just
    like before.

 gdk/broadway/gdkdnd-broadway.c     |   13 ++--
 gdk/broadway/gdkprivate-broadway.h |   12 ++--
 gdk/gdkdnd.c                       |  173 ++++++++++++++++++++++++++++++++++++
 gdk/gdkdnd.h                       |    2 +-
 gdk/gdkdndprivate.h                |   11 +++
 gdk/gdkwindow.c                    |   23 +++--
 gdk/gdkwindowimpl.h                |    2 +-
 gdk/wayland/gdkdnd-wayland.c       |   14 ++--
 gdk/wayland/gdkprivate-wayland.h   |    2 +-
 gdk/wayland/gdkselection-wayland.c |  120 +++++++------------------
 gdk/x11/gdkdnd-x11.c               |   14 ++--
 gdk/x11/gdkprivate-x11.h           |   12 ++--
 gtk/gtkdnd.c                       |  156 ++++++++++++++++++++++++++++++++-
 13 files changed, 422 insertions(+), 132 deletions(-)
---
diff --git a/gdk/broadway/gdkdnd-broadway.c b/gdk/broadway/gdkdnd-broadway.c
index c670d8d..3747ca1 100644
--- a/gdk/broadway/gdkdnd-broadway.c
+++ b/gdk/broadway/gdkdnd-broadway.c
@@ -84,12 +84,12 @@ gdk_broadway_drag_context_finalize (GObject *object)
 /* Drag Contexts */
 
 GdkDragContext *
-_gdk_broadway_window_drag_begin (GdkWindow         *window,
-                                GdkDevice         *device,
-                                GdkContentFormats *formats,
-                                 GdkDragAction      actions,
-                                 gint               dx,
-                                 gint               dy)
+_gdk_broadway_window_drag_begin (GdkWindow          *window,
+                                GdkDevice          *device,
+                                GdkContentProvider *content,
+                                 GdkDragAction       actions,
+                                 gint                dx,
+                                 gint                dy)
 {
   GdkDragContext *new_context;
 
@@ -98,6 +98,7 @@ _gdk_broadway_window_drag_begin (GdkWindow         *window,
 
   new_context = g_object_new (GDK_TYPE_BROADWAY_DRAG_CONTEXT,
                               "display", gdk_window_get_display (window),
+                              "content", content,
                              NULL);
 
   return new_context;
diff --git a/gdk/broadway/gdkprivate-broadway.h b/gdk/broadway/gdkprivate-broadway.h
index bc40ea9..39523a9 100644
--- a/gdk/broadway/gdkprivate-broadway.h
+++ b/gdk/broadway/gdkprivate-broadway.h
@@ -47,12 +47,12 @@ void gdk_broadway_window_set_nodes (GdkWindow *window,
                                     GPtrArray *node_textures);
 
 void     _gdk_broadway_window_register_dnd (GdkWindow      *window);
-GdkDragContext * _gdk_broadway_window_drag_begin (GdkWindow         *window,
-                                                 GdkDevice         *device,
-                                                 GdkContentFormats *formats,
-                                                  GdkDragAction      actions,
-                                                  gint               dx,
-                                                  gint               dy);
+GdkDragContext * _gdk_broadway_window_drag_begin (GdkWindow          *window,
+                                                 GdkDevice          *device,
+                                                 GdkContentProvider *content,
+                                                  GdkDragAction       actions,
+                                                  gint                dx,
+                                                  gint                dy);
 void     _gdk_broadway_window_translate         (GdkWindow *window,
                                                 cairo_region_t *area,
                                                 gint       dx,
diff --git a/gdk/gdkdnd.c b/gdk/gdkdnd.c
index 956be1a..2344b7e 100644
--- a/gdk/gdkdnd.c
+++ b/gdk/gdkdnd.c
@@ -29,6 +29,8 @@
 #include "gdkwindow.h"
 #include "gdkintl.h"
 #include "gdkcontentformats.h"
+#include "gdkcontentprovider.h"
+#include "gdkcontentserializer.h"
 #include "gdkcursor.h"
 #include "gdkenumtypes.h"
 #include "gdkeventsprivate.h"
@@ -48,7 +50,9 @@ static struct {
 
 enum {
   PROP_0,
+  PROP_CONTENT,
   PROP_DISPLAY,
+  PROP_FORMATS,
   N_PROPERTIES
 };
 
@@ -261,6 +265,12 @@ gdk_drag_context_set_property (GObject      *gobject,
 
   switch (prop_id)
     {
+    case PROP_CONTENT:
+      context->content = g_value_dup_object (value);
+      if (context->content)
+        context->formats = gdk_content_provider_ref_formats (context->content);
+      break;
+
     case PROP_DISPLAY:
       context->display = g_value_get_object (value);
       g_assert (context->display != NULL);
@@ -282,10 +292,18 @@ gdk_drag_context_get_property (GObject    *gobject,
 
   switch (prop_id)
     {
+    case PROP_CONTENT:
+      g_value_set_object (value, context->content);
+      break;
+
     case PROP_DISPLAY:
       g_value_set_object (value, context->display);
       break;
 
+    case PROP_FORMATS:
+      g_value_set_boxed (value, context->formats);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
       break;
@@ -298,6 +316,8 @@ gdk_drag_context_finalize (GObject *object)
   GdkDragContext *context = GDK_DRAG_CONTEXT (object);
 
   contexts = g_list_remove (contexts, context);
+
+  g_clear_object (&context->content);
   g_clear_pointer (&context->formats, gdk_content_formats_unref);
 
   if (context->source_window)
@@ -353,6 +373,24 @@ gdk_drag_context_class_init (GdkDragContextClass *klass)
   object_class->finalize = gdk_drag_context_finalize;
 
   /**
+   * GdkDragContext:content:
+   *
+   * The #GdkContentProvider or %NULL if the context is not a source-side
+   * context.
+   *
+   * Since: 3.94
+   */
+  properties[PROP_CONTENT] =
+    g_param_spec_object ("content",
+                         "Content",
+                         "The content being dragged",
+                         GDK_TYPE_CONTENT_PROVIDER,
+                         G_PARAM_READWRITE |
+                         G_PARAM_CONSTRUCT_ONLY |
+                         G_PARAM_STATIC_STRINGS |
+                         G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
    * GdkDragContext:display:
    *
    * The #GdkDisplay that the drag context belongs to.
@@ -370,6 +408,22 @@ gdk_drag_context_class_init (GdkDragContextClass *klass)
                          G_PARAM_EXPLICIT_NOTIFY);
 
   /**
+   * GdkDragContext:formats:
+   *
+   * The possible formats that the context can provide its data in.
+   *
+   * Since: 3.94
+   */
+  properties[PROP_FORMATS] =
+    g_param_spec_boxed ("formats",
+                        "Formats",
+                        "The possible formats for data",
+                        GDK_TYPE_CONTENT_FORMATS,
+                        G_PARAM_READABLE |
+                        G_PARAM_STATIC_STRINGS |
+                        G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
    * GdkDragContext::cancel:
    * @context: The object on which the signal is emitted
    * @reason: The reason the context was cancelled
@@ -655,6 +709,125 @@ gdk_drag_get_selection (GdkDragContext *context)
   return GDK_DRAG_CONTEXT_GET_CLASS (context)->get_selection (context);
 }
 
+static void
+gdk_drag_context_write_done (GObject      *content,
+                             GAsyncResult *result,
+                             gpointer      task)
+{
+  GError *error = NULL;
+
+  if (gdk_content_provider_write_mime_type_finish (GDK_CONTENT_PROVIDER (content), result, &error))
+    g_task_return_boolean (task, TRUE);
+  else
+    g_task_return_error (task, error);
+
+  g_object_unref (task);
+}
+
+static void
+gdk_drag_context_write_serialize_done (GObject      *content,
+                                       GAsyncResult *result,
+                                       gpointer      task)
+{
+  GError *error = NULL;
+
+  if (gdk_content_serialize_finish (result, &error))
+    g_task_return_boolean (task, TRUE);
+  else
+    g_task_return_error (task, error);
+
+  g_object_unref (task);
+}
+
+void
+gdk_drag_context_write_async (GdkDragContext      *context,
+                              const char          *mime_type,
+                              GOutputStream       *stream,
+                              int                  io_priority,
+                              GCancellable        *cancellable,
+                              GAsyncReadyCallback  callback,
+                              gpointer             user_data)
+{
+  GdkContentFormats *formats, *mime_formats;
+  GTask *task;
+  GType gtype;
+
+  g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
+  g_return_if_fail (context->content);
+  g_return_if_fail (mime_type != NULL);
+  g_return_if_fail (mime_type == g_intern_string (mime_type));
+  g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
+  g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+  g_return_if_fail (callback != NULL);
+
+  task = g_task_new (context, cancellable, callback, user_data);
+  g_task_set_priority (task, io_priority);
+  g_task_set_source_tag (task, gdk_drag_context_write_async);
+
+  formats = gdk_content_provider_ref_formats (context->content);
+  if (gdk_content_formats_contain_mime_type (formats, mime_type))
+    {
+      gdk_content_provider_write_mime_type_async (context->content,
+                                                  mime_type,
+                                                  stream,
+                                                  io_priority,
+                                                  cancellable,
+                                                  gdk_drag_context_write_done,
+                                                  task);
+      gdk_content_formats_unref (formats);
+      return;
+    }
+
+  mime_formats = gdk_content_formats_new ((const gchar *[2]) { mime_type, NULL }, 1);
+  mime_formats = gdk_content_formats_union_serialize_gtypes (mime_formats);
+  gtype = gdk_content_formats_match_gtype (formats, mime_formats);
+  if (gtype != G_TYPE_INVALID)
+    {
+      GValue value = G_VALUE_INIT;
+      GError *error = NULL;
+
+      g_assert (gtype != G_TYPE_INVALID);
+      
+      g_value_init (&value, gtype);
+      if (gdk_content_provider_get_value (context->content, &value, &error))
+        {
+          gdk_content_serialize_async (stream,
+                                       mime_type,
+                                       &value,
+                                       io_priority,
+                                       cancellable,
+                                       gdk_drag_context_write_serialize_done,
+                                       g_object_ref (task));
+        }
+      else
+        {
+          g_task_return_error (task, error);
+        }
+      
+      g_value_unset (&value);
+    }
+  else
+    {
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                               _("No compatible formats to transfer clipboard contents."));
+    }
+
+  gdk_content_formats_unref (mime_formats);
+  gdk_content_formats_unref (formats);
+  g_object_unref (task);
+}
+
+gboolean
+gdk_drag_context_write_finish (GdkDragContext *context,
+                               GAsyncResult   *result,
+                               GError        **error)
+{
+  g_return_val_if_fail (g_task_is_valid (result, context), FALSE);
+  g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_drag_context_write_async, FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error); 
+}
+
 void
 gdk_drop_read_async (GdkDragContext      *context,
                      const char         **mime_types,
diff --git a/gdk/gdkdnd.h b/gdk/gdkdnd.h
index 4a3926e..8f75a7d 100644
--- a/gdk/gdkdnd.h
+++ b/gdk/gdkdnd.h
@@ -136,7 +136,7 @@ GInputStream *          gdk_drop_read_finish            (GdkDragContext        *
 GDK_AVAILABLE_IN_ALL
 GdkDragContext *        gdk_drag_begin                  (GdkWindow              *window,
                                                          GdkDevice              *device,
-                                                         GdkContentFormats      *formats,
+                                                         GdkContentProvider     *content,
                                                          GdkDragAction           actions,
                                                          gint                    dx,
                                                          gint                    dy);
diff --git a/gdk/gdkdndprivate.h b/gdk/gdkdndprivate.h
index 5cb477a..127b514 100644
--- a/gdk/gdkdndprivate.h
+++ b/gdk/gdkdndprivate.h
@@ -134,6 +134,7 @@ struct _GdkDragContext {
   GdkWindow *dest_window;
   GdkWindow *drag_window;
 
+  GdkContentProvider *content;
   GdkContentFormats *formats;
   GdkDragAction actions;
   GdkDragAction suggested_action;
@@ -177,6 +178,16 @@ void     gdk_drag_find_window             (GdkDragContext   *context,
                                            GdkWindow       **dest_window,
                                            GdkDragProtocol  *protocol);
 
+void                    gdk_drag_context_write_async            (GdkDragContext         *context,
+                                                                 const char             *mime_type,
+                                                                 GOutputStream          *stream,
+                                                                 int                     io_priority,
+                                                                 GCancellable           *cancellable,
+                                                                 GAsyncReadyCallback     callback,
+                                                                 gpointer                user_data);
+gboolean                gdk_drag_context_write_finish           (GdkDragContext         *context,
+                                                                 GAsyncResult           *result,
+                                                                 GError                **error);
 
 
 G_END_DECLS
diff --git a/gdk/gdkwindow.c b/gdk/gdkwindow.c
index c504bc8..a9a15c7 100644
--- a/gdk/gdkwindow.c
+++ b/gdk/gdkwindow.c
@@ -6927,7 +6927,7 @@ gdk_window_register_dnd (GdkWindow *window)
  * gdk_drag_begin:
  * @window: the source window for this drag
  * @device: the device that controls this drag
- * @formats: (transfer none): the offered formats
+ * @content: (transfer none): the offered content
  * @actions: the actions supported by this drag
  * @dx: the x offset to @device's position where the drag nominally started
  * @dy: the y offset to @device's position where the drag nominally started
@@ -6940,14 +6940,19 @@ gdk_window_register_dnd (GdkWindow *window)
  *     %NULL on error.
  */
 GdkDragContext *
-gdk_drag_begin (GdkWindow         *window,
-                GdkDevice         *device,
-                GdkContentFormats *formats,
-                GdkDragAction      actions,
-                gint               dx,
-                gint               dy)
-{
-  return GDK_WINDOW_IMPL_GET_CLASS (window->impl)->drag_begin (window, device, formats, actions, dx, dy);
+gdk_drag_begin (GdkWindow          *window,
+                GdkDevice          *device,
+                GdkContentProvider *content,
+                GdkDragAction       actions,
+                gint                dx,
+                gint                dy)
+{
+  g_return_val_if_fail (GDK_IS_WINDOW (window), NULL);
+  g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (gdk_window_get_display (window) == gdk_device_get_display (device), NULL);
+  g_return_val_if_fail (GDK_IS_CONTENT_PROVIDER (content), NULL);
+
+  return GDK_WINDOW_IMPL_GET_CLASS (window->impl)->drag_begin (window, device, content, actions, dx, dy);
 }
 
 /**
diff --git a/gdk/gdkwindowimpl.h b/gdk/gdkwindowimpl.h
index f1abe35..4383b84 100644
--- a/gdk/gdkwindowimpl.h
+++ b/gdk/gdkwindowimpl.h
@@ -219,7 +219,7 @@ struct _GdkWindowImplClass
   void         (* register_dnd)         (GdkWindow *window);
   GdkDragContext * (*drag_begin)        (GdkWindow        *window,
                                          GdkDevice        *device,
-                                         GdkContentFormats *formats,
+                                         GdkContentProvider*content,
                                          GdkDragAction     actions,
                                          gint              dx,
                                          gint              dy);
diff --git a/gdk/wayland/gdkdnd-wayland.c b/gdk/wayland/gdkdnd-wayland.c
index 208b71e..ac84974 100644
--- a/gdk/wayland/gdkdnd-wayland.c
+++ b/gdk/wayland/gdkdnd-wayland.c
@@ -548,12 +548,12 @@ create_dnd_window (GdkDisplay *display)
 }
 
 GdkDragContext *
-_gdk_wayland_window_drag_begin (GdkWindow         *window,
-                               GdkDevice         *device,
-                               GdkContentFormats *formats,
-                                GdkDragAction      actions,
-                                gint               dx,
-                                gint               dy)
+_gdk_wayland_window_drag_begin (GdkWindow          *window,
+                               GdkDevice          *device,
+                               GdkContentProvider *content,
+                                GdkDragAction       actions,
+                                gint                dx,
+                                gint                dy)
 {
   GdkWaylandDragContext *context_wayland;
   GdkDragContext *context;
@@ -566,11 +566,11 @@ _gdk_wayland_window_drag_begin (GdkWindow         *window,
 
   context_wayland = g_object_new (GDK_TYPE_WAYLAND_DRAG_CONTEXT,
                                   "display", display_wayland,
+                                  "content", content,
                                   NULL);
   context = GDK_DRAG_CONTEXT (context_wayland);
   context->source_window = g_object_ref (window);
   context->is_source = TRUE;
-  context->formats = gdk_content_formats_ref (formats);
 
   gdk_drag_context_set_device (context, device);
 
diff --git a/gdk/wayland/gdkprivate-wayland.h b/gdk/wayland/gdkprivate-wayland.h
index 6b6e356..a6ee0c0 100644
--- a/gdk/wayland/gdkprivate-wayland.h
+++ b/gdk/wayland/gdkprivate-wayland.h
@@ -93,7 +93,7 @@ void       gdk_wayland_window_sync (GdkWindow *window);
 void            _gdk_wayland_window_register_dnd          (GdkWindow *window);
 GdkDragContext *_gdk_wayland_window_drag_begin            (GdkWindow *window,
                                                           GdkDevice *device,
-                                                          GdkContentFormats *formats,
+                                                          GdkContentProvider *content,
                                                            GdkDragAction actions,
                                                            gint       dx,
                                                            gint       dy);
diff --git a/gdk/wayland/gdkselection-wayland.c b/gdk/wayland/gdkselection-wayland.c
index 4634033..6d19048 100644
--- a/gdk/wayland/gdkselection-wayland.c
+++ b/gdk/wayland/gdkselection-wayland.c
@@ -753,63 +753,6 @@ gdk_wayland_selection_lookup_requestor_buffer (GdkWindow *requestor)
   return NULL;
 }
 
-static gboolean
-gdk_wayland_selection_source_handles_target (GdkWaylandSelection *wayland_selection,
-                                             GdkAtom              target)
-{
-  GdkAtom atom;
-  guint i;
-
-  if (target == NULL)
-    return FALSE;
-
-  for (i = 0; i < wayland_selection->source_targets->len; i++)
-    {
-      atom = g_array_index (wayland_selection->source_targets, GdkAtom, i);
-
-      if (atom == target)
-        return TRUE;
-    }
-
-  return FALSE;
-}
-
-static gboolean
-gdk_wayland_selection_request_target (GdkWaylandSelection *wayland_selection,
-                                      GdkWindow           *window,
-                                      GdkAtom              selection,
-                                      GdkAtom              target,
-                                      gint                 fd)
-{
-  if (wayland_selection->stored_selection.fd == fd &&
-      wayland_selection->requested_target == target)
-    return FALSE;
-
-  /* If we didn't issue gdk_wayland_selection_check_write() yet
-   * on a previous fd, it will still linger here. Just close it,
-   * as we can't have more than one fd on the fly.
-   */
-  if (wayland_selection->stored_selection.fd >= 0)
-    close (wayland_selection->stored_selection.fd);
-
-  wayland_selection->stored_selection.fd = fd;
-  wayland_selection->requested_target = target;
-
-  if (window &&
-      gdk_wayland_selection_source_handles_target (wayland_selection, target))
-    {
-      gdk_wayland_selection_emit_request (window, selection, target);
-      return TRUE;
-    }
-  else
-    {
-      close (fd);
-      wayland_selection->stored_selection.fd = -1;
-    }
-
-  return FALSE;
-}
-
 static void
 data_source_target (void                  *data,
                     struct wl_data_source *source,
@@ -821,44 +764,47 @@ data_source_target (void                  *data,
 }
 
 static void
-data_source_send (void                  *data,
-                  struct wl_data_source *source,
-                  const char            *mime_type,
-                  int32_t                fd)
+gdk_wayland_drag_context_write_done (GObject      *context,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
 {
-  GdkWaylandSelection *wayland_selection = data;
-  GdkWindow *window;
-  GdkAtom selection;
-
-  GDK_NOTE (EVENTS,
-            g_message ("data source send, source = %p, mime_type = %s, fd = %d",
-                       source, mime_type, fd));
+  GError *error = NULL;
 
-  if (!mime_type)
+  if (!gdk_drag_context_write_finish (GDK_DRAG_CONTEXT (context), result, &error))
     {
-      close (fd);
-      return;
+      GDK_NOTE(DND, g_printerr ("%p: failed to write stream: %s\n", context, error->message));
+      g_error_free (error);
     }
+}
 
-  if (source == wayland_selection->dnd_source)
-    {
-      window = wayland_selection->dnd_owner;
-      selection = atoms[ATOM_DND];
-    }
-  else
-    {
-      close (fd);
-      return;
-    }
+static void
+data_source_send (void                  *data,
+                  struct wl_data_source *source,
+                  const char            *mime_type,
+                  int32_t                fd)
+{
+  GdkDragContext *context;
+  GOutputStream *stream;
 
-  if (!window)
+  context = gdk_wayland_drag_context_lookup_by_data_source (source);
+  if (!context)
     return;
 
-  if (!gdk_wayland_selection_request_target (wayland_selection, window,
-                                             selection,
-                                             gdk_atom_intern (mime_type, FALSE),
-                                             fd))
-    gdk_wayland_selection_check_write (wayland_selection);
+  GDK_NOTE (DND, g_printerr ("%p: data source send request for %s on fd %d\n",
+                             source, mime_type, fd));
+
+  //mime_type = gdk_intern_mime_type (mime_type);
+  mime_type = g_intern_string (mime_type);
+  stream = g_unix_output_stream_new (fd, TRUE);
+
+  gdk_drag_context_write_async (context,
+                                mime_type,
+                                stream,
+                                G_PRIORITY_DEFAULT,
+                                NULL,
+                                gdk_wayland_drag_context_write_done,
+                                context);
+  g_object_unref (stream);
 }
 
 static void
diff --git a/gdk/x11/gdkdnd-x11.c b/gdk/x11/gdkdnd-x11.c
index 3443fb2..c36c10b 100644
--- a/gdk/x11/gdkdnd-x11.c
+++ b/gdk/x11/gdkdnd-x11.c
@@ -2857,25 +2857,25 @@ drag_context_ungrab (GdkDragContext *context)
 }
 
 GdkDragContext *
-_gdk_x11_window_drag_begin (GdkWindow         *window,
-                            GdkDevice         *device,
-                            GdkContentFormats *formats,
-                            GdkDragAction      actions,
-                            gint               dx,
-                            gint               dy)
+_gdk_x11_window_drag_begin (GdkWindow          *window,
+                            GdkDevice          *device,
+                            GdkContentProvider *content,
+                            GdkDragAction       actions,
+                            gint                dx,
+                            gint                dy)
 {
   GdkDragContext *context;
   int x_root, y_root;
 
   context = (GdkDragContext *) g_object_new (GDK_TYPE_X11_DRAG_CONTEXT,
                                              "display", gdk_window_get_display (window),
+                                             "content", content,
                                              NULL);
 
   context->is_source = TRUE;
   context->source_window = window;
   g_object_ref (window);
 
-  context->formats = gdk_content_formats_ref (formats);
   precache_target_list (context);
 
   gdk_drag_context_set_device (context, device);
diff --git a/gdk/x11/gdkprivate-x11.h b/gdk/x11/gdkprivate-x11.h
index 8d09197..6ebd0e8 100644
--- a/gdk/x11/gdkprivate-x11.h
+++ b/gdk/x11/gdkprivate-x11.h
@@ -282,12 +282,12 @@ void _gdk_x11_cursor_display_finalize (GdkDisplay *display);
 
 void _gdk_x11_window_register_dnd (GdkWindow *window);
 
-GdkDragContext * _gdk_x11_window_drag_begin (GdkWindow         *window,
-                                             GdkDevice         *device,
-                                             GdkContentFormats *formats,
-                                             GdkDragAction      actions,
-                                             gint               x_root,
-                                             gint               y_root);
+GdkDragContext * _gdk_x11_window_drag_begin (GdkWindow          *window,
+                                             GdkDevice          *device,
+                                             GdkContentProvider *content,
+                                             GdkDragAction       actions,
+                                             gint                dx,
+                                             gint                dy);
 
 GdkGrabStatus _gdk_x11_convert_grab_status (gint status);
 
diff --git a/gtk/gtkdnd.c b/gtk/gtkdnd.c
index 085a7a9..dd2a003 100644
--- a/gtk/gtkdnd.c
+++ b/gtk/gtkdnd.c
@@ -960,6 +960,150 @@ gtk_drag_dest_drop (GtkWidget      *widget,
  * Source side *
  ***************/
 
+#define GTK_TYPE_DRAG_CONTENT            (gtk_drag_content_get_type ())
+#define GTK_DRAG_CONTENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_DRAG_CONTENT, 
GtkDragContent))
+#define GTK_IS_DRAG_CONTENT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_DRAG_CONTENT))
+#define GTK_DRAG_CONTENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_DRAG_CONTENT, 
GtkDragContentClass))
+#define GTK_IS_DRAG_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_DRAG_CONTENT))
+#define GTK_DRAG_CONTENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_DRAG_CONTENT, 
GtkDragContentClass))
+
+typedef struct _GtkDragContent GtkDragContent;
+typedef struct _GtkDragContentClass GtkDragContentClass;
+
+struct _GtkDragContent
+{
+  GdkContentProvider parent;
+
+  GtkWidget *widget;
+  GdkDragContext *context;
+  GdkContentFormats *formats;
+  guint32 time;
+};
+
+struct _GtkDragContentClass
+{
+  GdkContentProviderClass parent_class;
+};
+
+GType gtk_drag_content_get_type (void) G_GNUC_CONST;
+
+G_DEFINE_TYPE (GtkDragContent, gtk_drag_content, GDK_TYPE_CONTENT_PROVIDER)
+
+static GdkContentFormats *
+gtk_drag_content_ref_formats (GdkContentProvider *provider)
+{
+  GtkDragContent *content = GTK_DRAG_CONTENT (provider);
+
+  return gdk_content_formats_ref (content->formats);
+}
+
+static void
+gtk_drag_content_write_mime_type_done (GObject      *stream,
+                                       GAsyncResult *result,
+                                       gpointer      task)
+{
+  GError *error = NULL;
+
+  if (!g_output_stream_write_all_finish (G_OUTPUT_STREAM (stream),
+                                         result,
+                                         NULL,
+                                         &error))
+    {
+      g_task_return_error (task, error);
+    }
+  else
+    {
+      g_task_return_boolean (task, TRUE);
+    }
+
+  g_object_unref (task);
+}
+
+static void
+gtk_drag_content_write_mime_type_async (GdkContentProvider  *provider,
+                                        const char          *mime_type,
+                                        GOutputStream       *stream,
+                                        int                  io_priority,
+                                        GCancellable        *cancellable,
+                                        GAsyncReadyCallback  callback,
+                                        gpointer             user_data)
+{
+  GtkDragContent *content = GTK_DRAG_CONTENT (provider);
+  GtkSelectionData sdata = { 0, };
+  GTask *task;
+
+  task = g_task_new (content, cancellable, callback, user_data);
+  g_task_set_priority (task, io_priority);
+  g_task_set_source_tag (task, gtk_drag_content_write_mime_type_async);
+
+  sdata.selection = gdk_drag_get_selection (content->context);
+  sdata.target = gdk_atom_intern (mime_type, FALSE);
+  sdata.length = -1;
+  sdata.display = gtk_widget_get_display (content->widget);
+  
+  g_signal_emit_by_name (content->widget, "drag-data-get",
+                         content->context,
+                         &sdata,
+                         content->time);
+
+  if (sdata.length == -1)
+    {
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+                               _("Cannot provide contents as ā€œ%sā€"), mime_type);
+      g_object_unref (task);
+      return;
+    }
+  g_task_set_task_data (task, sdata.data, g_free);
+
+  g_output_stream_write_all_async (stream,
+                                   sdata.data,
+                                   sdata.length,
+                                   io_priority,
+                                   cancellable,
+                                   gtk_drag_content_write_mime_type_done,
+                                   task);
+}
+
+static gboolean
+gtk_drag_content_write_mime_type_finish (GdkContentProvider  *provider,
+                                         GAsyncResult        *result,
+                                         GError             **error)
+{
+  g_return_val_if_fail (g_task_is_valid (result, provider), FALSE);
+  g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gtk_drag_content_write_mime_type_async, 
FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+gtk_drag_content_finalize (GObject *object)
+{
+  GtkDragContent *content = GTK_DRAG_CONTENT (object);
+
+  g_clear_object (&content->widget);
+  g_clear_pointer (&content->formats, (GDestroyNotify) gdk_content_formats_unref);
+
+  G_OBJECT_CLASS (gtk_drag_content_parent_class)->finalize (object);
+}
+
+static void
+gtk_drag_content_class_init (GtkDragContentClass *class)
+{
+  GdkContentProviderClass *provider_class = GDK_CONTENT_PROVIDER_CLASS (class);
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = gtk_drag_content_finalize;
+
+  provider_class->ref_formats = gtk_drag_content_ref_formats;
+  provider_class->write_mime_type_async = gtk_drag_content_write_mime_type_async;
+  provider_class->write_mime_type_finish = gtk_drag_content_write_mime_type_finish;
+}
+
+static void
+gtk_drag_content_init (GtkDragContent *content)
+{
+}
+
 /* Like gtk_drag_begin(), but also takes a GtkIconHelper
  * so that we can set the icon from the source site information
  */
@@ -979,6 +1123,7 @@ gtk_drag_begin_internal (GtkWidget          *widget,
   GdkWindow *ipc_window;
   int dx, dy;
   GdkAtom selection;
+  GtkDragContent *content;
   guint32 time;
 
   ipc_widget = gtk_drag_get_ipc_widget (widget);
@@ -1001,13 +1146,22 @@ gtk_drag_begin_internal (GtkWidget          *widget,
   dx -= x;
   dy -= y;
 
-  context = gdk_drag_begin (ipc_window, device, target_list, actions, dx, dy);
+  content = g_object_new (GTK_TYPE_DRAG_CONTENT, NULL);
+  content->widget = g_object_ref (widget);
+  content->formats = gdk_content_formats_ref (target_list);
+  content->time = time;
+
+  context = gdk_drag_begin (ipc_window, device, GDK_CONTENT_PROVIDER (content), actions, dx, dy);
   if (context == NULL)
     {
       gtk_drag_release_ipc_widget (ipc_widget);
+      g_object_unref (content);
       return NULL;
     }
 
+  content->context = context;
+  g_object_unref (content);
+
   info = gtk_drag_get_source_info (context, TRUE);
 
   info->ipc_widget = ipc_widget;


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