dnd: Introduce gdk_drop_read_async() and use it

This is the replacement for selection usage.

Backend implementations for X11 (missing support for backwards compat
formats like COMPOUND_TEXT) and Wayland are included.

GTK code should be adapted to use gdk_drop_read_*() functions instead
of gtk_drag_get_data().
This commit is contained in:
Benjamin Otte 2017-12-10 01:05:37 +01:00
parent 963264a73a
commit 803cbd576f
8 changed files with 446 additions and 20 deletions

View File

@ -309,6 +309,40 @@ gdk_drag_context_finalize (GObject *object)
G_OBJECT_CLASS (gdk_drag_context_parent_class)->finalize (object);
}
static void
gdk_drag_context_read_local_async (GdkDragContext *context,
GdkContentFormats *formats,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
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_read_local_async);
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("Reading not implemented."));
g_object_unref (task);
}
static GInputStream *
gdk_drag_context_read_local_finish (GdkDragContext *context,
const char **out_mime_type,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, context), NULL);
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_drag_context_read_local_async, NULL);
if (out_mime_type)
*out_mime_type = g_task_get_task_data (G_TASK (result));
return g_task_propagate_pointer (G_TASK (result), error);
}
static void
gdk_drag_context_class_init (GdkDragContextClass *klass)
{
@ -637,6 +671,52 @@ gdk_drag_get_selection (GdkDragContext *context)
return GDK_DRAG_CONTEXT_GET_CLASS (context)->get_selection (context);
}
void
gdk_drop_read_async (GdkDragContext *context,
const char **mime_types,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GdkContentFormats *formats;
g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
g_return_if_fail (mime_types != NULL && mime_types[0] != NULL);
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
g_return_if_fail (callback != NULL);
formats = gdk_content_formats_new (mime_types, g_strv_length ((char **) mime_types));
GDK_DRAG_CONTEXT_GET_CLASS (context)->read_async (context,
formats,
io_priority,
cancellable,
callback,
user_data);
gdk_content_formats_unref (formats);
}
GInputStream *
gdk_drop_read_finish (GdkDragContext *context,
const char **out_mime_type,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
if (g_async_result_is_tagged (result, gdk_drag_context_read_local_async))
{
return gdk_drag_context_read_local_finish (context, out_mime_type, result, error);
}
else
{
return GDK_DRAG_CONTEXT_GET_CLASS (context)->read_finish (context, out_mime_type, result, error);
}
}
/**
* gdk_drag_context_get_drag_window:
* @context: a #GdkDragContext

View File

@ -121,6 +121,19 @@ void gdk_drop_finish (GdkDragContext *context,
GDK_AVAILABLE_IN_ALL
GdkAtom gdk_drag_get_selection (GdkDragContext *context);
GDK_AVAILABLE_IN_3_94
void gdk_drop_read_async (GdkDragContext *context,
const char **mime_types,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GDK_AVAILABLE_IN_3_94
GInputStream * gdk_drop_read_finish (GdkDragContext *context,
const char **out_mime_type,
GAsyncResult *result,
GError **error);
/* Source side */
GDK_AVAILABLE_IN_ALL

View File

@ -87,6 +87,16 @@ struct _GdkDragContextClass {
void (*drop_finish) (GdkDragContext *context,
gboolean success,
guint32 time_);
void (* read_async) (GdkDragContext *context,
GdkContentFormats *formats,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GInputStream * (* read_finish) (GdkDragContext *context,
const char **out_mime_type,
GAsyncResult *result,
GError **error);
gboolean (*drop_status) (GdkDragContext *context);
GdkWindow* (*get_drag_window) (GdkDragContext *context);
void (*set_hotspot) (GdkDragContext *context,

View File

@ -24,10 +24,14 @@
#include "gdkprivate-wayland.h"
#include "gdkcontentformats.h"
#include "gdkdisplay-wayland.h"
#include "gdkintl.h"
#include "gdkseat-wayland.h"
#include "gdkdeviceprivate.h"
#include <glib-unix.h>
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
#include <string.h>
#define GDK_TYPE_WAYLAND_DRAG_CONTEXT (gdk_wayland_drag_context_get_type ())
@ -324,6 +328,80 @@ gdk_wayland_drag_context_drop_finish (GdkDragContext *context,
gdk_wayland_selection_set_offer (display, selection, NULL);
}
static void
gdk_wayland_drag_context_read_async (GdkDragContext *context,
GdkContentFormats *formats,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GdkDisplay *display;
GdkContentFormats *dnd_formats;
GInputStream *stream;
struct wl_data_offer *offer;
const char *mime_type;
int pipe_fd[2];
GError *error = NULL;
GTask *task;
display = gdk_drag_context_get_display (context),
task = g_task_new (context, cancellable, callback, user_data);
g_task_set_priority (task, io_priority);
g_task_set_source_tag (task, gdk_wayland_drag_context_read_async);
GDK_NOTE (DND, char *s = gdk_content_formats_to_string (formats);
g_printerr ("%p: read for %s\n", context, s);
g_free (s); );
dnd_formats = gdk_wayland_selection_get_targets (display,
gdk_drag_get_selection (context));
mime_type = gdk_content_formats_match_mime_type (formats, dnd_formats);
if (mime_type == NULL)
{
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("No compatible transfer format found"));
return;
}
/* offer formats should be empty if we have no offer */
offer = gdk_wayland_selection_get_offer (display,
gdk_drag_get_selection (context));
g_assert (offer);
g_task_set_task_data (task, (gpointer) mime_type, NULL);
if (!g_unix_open_pipe (pipe_fd, FD_CLOEXEC, &error))
{
g_task_return_error (task, error);
return;
}
wl_data_offer_accept (offer,
_gdk_wayland_display_get_serial (GDK_WAYLAND_DISPLAY (display)),
mime_type);
wl_data_offer_receive (offer, mime_type, pipe_fd[1]);
stream = g_unix_input_stream_new (pipe_fd[0], TRUE);
close (pipe_fd[1]);
g_task_return_pointer (task, stream, g_object_unref);
}
static GInputStream *
gdk_wayland_drag_context_read_finish (GdkDragContext *context,
const char **out_mime_type,
GAsyncResult *result,
GError **error)
{
GTask *task;
g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (context)), NULL);
task = G_TASK (result);
g_return_val_if_fail (g_task_get_source_tag (task) == gdk_wayland_drag_context_read_async, NULL);
if (out_mime_type)
*out_mime_type = g_task_get_task_data (task);
return g_task_propagate_pointer (task, error);
}
static gboolean
gdk_wayland_drag_context_drop_status (GdkDragContext *context)
{
@ -471,6 +549,9 @@ gdk_wayland_drag_context_class_init (GdkWaylandDragContextClass *klass)
context_class->drag_drop = gdk_wayland_drag_context_drag_drop;
context_class->drop_reply = gdk_wayland_drag_context_drop_reply;
context_class->drop_finish = gdk_wayland_drag_context_drop_finish;
context_class->drop_finish = gdk_wayland_drag_context_drop_finish;
context_class->read_async = gdk_wayland_drag_context_read_async;
context_class->read_finish = gdk_wayland_drag_context_read_finish;
context_class->drop_status = gdk_wayland_drag_context_drop_status;
context_class->get_selection = gdk_wayland_drag_context_get_selection;
context_class->get_drag_window = gdk_wayland_drag_context_get_drag_window;

View File

@ -287,7 +287,7 @@ static const struct {
{ "SAVE_TARGETS", NULL, NULL, "NULL", 32, handle_save_targets }
};
static GSList *
GSList *
gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats)
{
GSList *targets;

View File

@ -36,6 +36,7 @@ GType gdk_x11_clipboard_get_type (void) G_GNUC_CO
GdkClipboard * gdk_x11_clipboard_new (GdkDisplay *display,
const gchar *selection);
GSList * gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats);
G_END_DECLS

View File

@ -25,16 +25,19 @@
#include "config.h"
#include "gdkx11dnd.h"
#include "gdkdndprivate.h"
#include "gdkdeviceprivate.h"
#include "gdkinternals.h"
#include "gdkasync.h"
#include "gdkcontentformatsprivate.h"
#include "gdkclipboardprivate.h"
#include "gdkclipboard-x11.h"
#include "gdkdeviceprivate.h"
#include "gdkdisplay-x11.h"
#include "gdkdndprivate.h"
#include "gdkinternals.h"
#include "gdkintl.h"
#include "gdkproperty.h"
#include "gdkprivate-x11.h"
#include "gdkscreen-x11.h"
#include "gdkdisplay-x11.h"
#include "gdkselectioninputstream-x11.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
@ -242,6 +245,135 @@ static void gdk_x11_drag_context_cancel (GdkDragContext *con
static void gdk_x11_drag_context_drop_performed (GdkDragContext *context,
guint32 time);
static void
gdk_x11_drag_context_read_got_stream (GObject *source,
GAsyncResult *res,
gpointer data)
{
GTask *task = data;
GError *error = NULL;
GInputStream *stream;
const char *type;
int format;
stream = gdk_x11_selection_input_stream_new_finish (res, &type, &format, &error);
if (stream == NULL)
{
GSList *targets, *next;
targets = g_task_get_task_data (task);
next = targets->next;
if (next)
{
GdkDragContext *context = GDK_DRAG_CONTEXT (g_task_get_source_object (task));
GDK_NOTE (DND, g_printerr ("reading %s failed, trying %s next\n",
(char *) targets->data, (char *) next->data));
targets->next = NULL;
g_task_set_task_data (task, next, (GDestroyNotify) g_slist_free);
gdk_x11_selection_input_stream_new_async (gdk_drag_context_get_display (context),
gdk_drag_get_selection (context),
next->data,
CurrentTime,
g_task_get_priority (task),
g_task_get_cancellable (task),
gdk_x11_drag_context_read_got_stream,
task);
g_error_free (error);
return;
}
g_task_return_error (task, error);
}
else
{
const char *mime_type = ((GSList *) g_task_get_task_data (task))->data;
#if 0
gsize i;
for (i = 0; i < G_N_ELEMENTS (special_targets); i++)
{
if (g_str_equal (mime_type, special_targets[i].x_target))
{
g_assert (special_targets[i].mime_type != NULL);
GDK_NOTE(CLIPBOARD, g_printerr ("%s: reading with converter from %s to %s\n",
cb->selection, mime_type, special_targets[i].mime_type));
mime_type = g_intern_string (special_targets[i].mime_type);
g_task_set_task_data (task, g_slist_prepend (NULL, (gpointer) mime_type), (GDestroyNotify) g_slist_free);
stream = special_targets[i].convert (cb, stream, type, format);
break;
}
}
#endif
GDK_NOTE(DND, g_printerr ("reading DND as %s now\n",
mime_type));
g_task_return_pointer (task, stream, g_object_unref);
}
g_object_unref (task);
}
static void
gdk_x11_drag_context_read_async (GdkDragContext *context,
GdkContentFormats *formats,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSList *targets;
GTask *task;
task = g_task_new (context, cancellable, callback, user_data);
g_task_set_priority (task, io_priority);
g_task_set_source_tag (task, gdk_x11_drag_context_read_async);
targets = gdk_x11_clipboard_formats_to_targets (formats);
g_task_set_task_data (task, targets, (GDestroyNotify) g_slist_free);
if (targets == NULL)
{
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("No compatible transfer format found"));
return;
}
GDK_NOTE(DND, g_printerr ("new read for %s (%u other options)\n",
(char *) targets->data, g_slist_length (targets->next)));
gdk_x11_selection_input_stream_new_async (gdk_drag_context_get_display (context),
gdk_drag_get_selection (context),
targets->data,
CurrentTime,
io_priority,
cancellable,
gdk_x11_drag_context_read_got_stream,
task);
}
static GInputStream *
gdk_x11_drag_context_read_finish (GdkDragContext *context,
const char **out_mime_type,
GAsyncResult *result,
GError **error)
{
GTask *task;
g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (context)), NULL);
task = G_TASK (result);
g_return_val_if_fail (g_task_get_source_tag (task) == gdk_x11_drag_context_read_async, NULL);
if (out_mime_type)
{
GSList *targets;
targets = g_task_get_task_data (task);
*out_mime_type = targets ? targets->data : NULL;
}
return g_task_propagate_pointer (task, error);
}
static void
gdk_x11_drag_context_class_init (GdkX11DragContextClass *klass)
{
@ -258,6 +390,8 @@ gdk_x11_drag_context_class_init (GdkX11DragContextClass *klass)
context_class->drop_reply = gdk_x11_drag_context_drop_reply;
context_class->drop_finish = gdk_x11_drag_context_drop_finish;
context_class->drop_status = gdk_x11_drag_context_drop_status;
context_class->read_async = gdk_x11_drag_context_read_async;
context_class->read_finish = gdk_x11_drag_context_read_finish;
context_class->get_selection = gdk_x11_drag_context_get_selection;
context_class->get_drag_window = gdk_x11_drag_context_get_drag_window;
context_class->set_hotspot = gdk_x11_drag_context_set_hotspot;

View File

@ -326,6 +326,115 @@ gtk_drag_get_event_actions (const GdkEvent *event,
* Destination side *
********************/
typedef struct {
GdkDragContext *context;
GtkWidget *widget;
const char *mime_type;
guint time;
} GtkDragGetData;
static void
gtk_drag_get_data_finish (GtkDragGetData *data,
guchar *bytes,
gsize size)
{
GtkDragDestSite *site;
GtkSelectionData sdata;
site = g_object_get_data (G_OBJECT (data->widget), "gtk-drag-dest");
sdata.selection = gdk_drag_get_selection (data->context);
sdata.target = data->mime_type;
sdata.type = data->mime_type;
sdata.format = 8;
sdata.length = size;
sdata.data = bytes;
sdata.display = gtk_widget_get_display (data->widget);
if (site && site->target_list)
{
if (gdk_content_formats_contain_mime_type (site->target_list, data->mime_type))
{
if (!(site->flags & GTK_DEST_DEFAULT_DROP) ||
size >= 0)
g_signal_emit_by_name (data->widget,
"drag-data-received",
data->context,
&sdata,
data->time);
}
}
else
{
g_signal_emit_by_name (data->widget,
"drag-data-received",
data->context,
&sdata,
data->time);
}
if (site && site->flags & GTK_DEST_DEFAULT_DROP)
{
gtk_drag_finish (data->context,
size > 0,
(gdk_drag_context_get_selected_action (data->context) == GDK_ACTION_MOVE),
data->time);
}
g_object_unref (data->widget);
g_slice_free (GtkDragGetData, data);
}
static void
gtk_drag_get_data_got_data (GObject *source,
GAsyncResult *result,
gpointer data)
{
gssize written;
written = g_output_stream_splice_finish (G_OUTPUT_STREAM (source), result, NULL);
if (written < 0)
{
gtk_drag_get_data_finish (data, NULL, 0);
}
else
{
gtk_drag_get_data_finish (data,
g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (source)),
g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (source)));
}
}
static void
gtk_drag_get_data_got_stream (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GtkDragGetData *data = user_data;
GInputStream *input_stream;
GOutputStream *output_stream;
input_stream = gdk_drop_read_finish (GDK_DRAG_CONTEXT (source), &data->mime_type, result, NULL);
if (input_stream == NULL)
{
gtk_drag_get_data_finish (data, NULL, 0);
return;
}
output_stream = g_memory_output_stream_new_resizable ();
g_output_stream_splice_async (output_stream,
input_stream,
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
G_PRIORITY_DEFAULT,
NULL,
gtk_drag_get_data_got_data,
data);
g_object_unref (output_stream);
g_object_unref (input_stream);
}
/**
* gtk_drag_get_data: (method)
* @widget: the widget that will receive the
@ -351,25 +460,23 @@ gtk_drag_get_data (GtkWidget *widget,
GdkAtom target,
guint32 time_)
{
GtkWidget *selection_widget;
GtkDragGetData *data;
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
selection_widget = gtk_drag_get_ipc_widget (widget);
data = g_slice_new0 (GtkDragGetData);
data->widget = g_object_ref (widget);
data->context = context;
data->mime_type = target;
data->time = time_;
g_object_ref (context);
g_object_ref (widget);
g_signal_connect (selection_widget, "selection-received",
G_CALLBACK (gtk_drag_selection_received), widget);
g_object_set_data (G_OBJECT (selection_widget), I_("drag-context"), context);
gtk_selection_convert (selection_widget,
gdk_drag_get_selection (context),
target,
time_);
gdk_drop_read_async (context,
(const gchar *[2]) { target, NULL },
G_PRIORITY_DEFAULT,
NULL,
gtk_drag_get_data_got_stream,
data);
}
/**