dnd: Pass content to gdk_drag_begin()
authorBenjamin Otte <otte@redhat.com>
Wed, 13 Dec 2017 14:03:53 +0000 (15:03 +0100)
committerBenjamin Otte <otte@redhat.com>
Wed, 13 Dec 2017 14:05:27 +0000 (15:05 +0100)
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.

13 files changed:
gdk/broadway/gdkdnd-broadway.c
gdk/broadway/gdkprivate-broadway.h
gdk/gdkdnd.c
gdk/gdkdnd.h
gdk/gdkdndprivate.h
gdk/gdkwindow.c
gdk/gdkwindowimpl.h
gdk/wayland/gdkdnd-wayland.c
gdk/wayland/gdkprivate-wayland.h
gdk/wayland/gdkselection-wayland.c
gdk/x11/gdkdnd-x11.c
gdk/x11/gdkprivate-x11.h
gtk/gtkdnd.c

index c670d8d3d9f7644b7a26cc230bc30fcb21731c80..3747ca1e88e67df58469e06fedae7b3d7f2e1c63 100644 (file)
@@ -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;
index bc40ea9c32ee520c9239841b4ae82ef2ac6a0507..39523a9079c3ebddd4ed2c5e23269c84dd50cec7 100644 (file)
@@ -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,
index 956be1a196ec2d686648d29aee5c1aa104ed6fa2..2344b7ee6547c218e26747b2adeb2353bb62eaa4 100644 (file)
@@ -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)
@@ -352,6 +372,24 @@ gdk_drag_context_class_init (GdkDragContextClass *klass)
   object_class->set_property = gdk_drag_context_set_property;
   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:
    *
@@ -369,6 +407,22 @@ gdk_drag_context_class_init (GdkDragContextClass *klass)
                          G_PARAM_STATIC_STRINGS |
                          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
@@ -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,
index 4a3926e589814a918e47d7078fb74375decdfb8e..8f75a7d1d2d6c001443324f46862371f299d38e1 100644 (file)
@@ -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);
index 5cb477acb6856e16a420be35e0d6cb2cfd39f26d..127b51426503334c817afd89734954c10a1aa094 100644 (file)
@@ -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
index c504bc889b815e9b2a2ceb3705f9bd739168ff30..a9a15c7eec0c7b3dbf893ddeaaea1c01d2d3946f 100644 (file)
@@ -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);
 }
 
 /**
index f1abe353074ae5ae167379589e588ab930359fc6..4383b8453801514a100c9e4d3ac8d023fec1ded4 100644 (file)
@@ -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);
index 208b71e20ddc912254b8aa243d16b31653988114..ac849741a9a3c3ae6cd959c8d88a3f5eec178a4f 100644 (file)
@@ -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);
 
index 6b6e356a11cca6bf1278baa8f47fdb1609a51127..a6ee0c0cdbeac53339a384208425903671301c57 100644 (file)
@@ -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);
index 4634033ab4f980e203255de386a8b1440ab38b1d..6d1904822a727b2f69a1aae36e36ac9d2026d592 100644 (file)
@@ -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
index 3443fb2a28b9087b48fa42c7f15856ebd29b9700..c36c10bf41259e0e0b04d6a9eb7f793f531f25f5 100644 (file)
@@ -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);
index 8d0919763e3531381932795c152cf0823e99b863..6ebd0e8eb146a7d3acf19fc706e18cc993e98214 100644 (file)
@@ -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);
 
index 085a7a97741e6687a6ca042d8cb1d99aed7580e7..dd2a0034e0b2ee257af6c37fa33428a111906c29 100644 (file)
@@ -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;