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.
This commit is contained in:
Benjamin Otte
2017-12-13 15:03:53 +01:00
parent c30cd885dd
commit 8648d5409e
13 changed files with 421 additions and 131 deletions

View 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;

View 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,

View 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,

View 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);

View 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

View 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)
gdk_drag_begin (GdkWindow *window,
GdkDevice *device,
GdkContentProvider *content,
GdkDragAction actions,
gint dx,
gint dy)
{
return GDK_WINDOW_IMPL_GET_CLASS (window->impl)->drag_begin (window, device, formats, actions, dx, 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);
}
/**

View 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);

View 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);

View 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);

View 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,
@@ -820,45 +763,48 @@ data_source_target (void *data,
source, mime_type));
}
static void
gdk_wayland_drag_context_write_done (GObject *context,
GAsyncResult *result,
gpointer user_data)
{
GError *error = NULL;
if (!gdk_drag_context_write_finish (GDK_DRAG_CONTEXT (context), result, &error))
{
GDK_NOTE(DND, g_printerr ("%p: failed to write stream: %s\n", context, error->message));
g_error_free (error);
}
}
static void
data_source_send (void *data,
struct wl_data_source *source,
const char *mime_type,
int32_t fd)
{
GdkWaylandSelection *wayland_selection = data;
GdkWindow *window;
GdkAtom selection;
GdkDragContext *context;
GOutputStream *stream;
GDK_NOTE (EVENTS,
g_message ("data source send, source = %p, mime_type = %s, fd = %d",
source, mime_type, fd));
if (!mime_type)
{
close (fd);
return;
}
if (source == wayland_selection->dnd_source)
{
window = wayland_selection->dnd_owner;
selection = atoms[ATOM_DND];
}
else
{
close (fd);
return;
}
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

View 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);

View 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);

View 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;