clipboard: Refactor gdk_clipboard_read() to be async

This allows us not just to pass any mime type to the read function, but
it also makes it possible to pass multiple mime types and the clipboard
can then try them in order until it finds a supported one.

This is so far not implemented though.
This commit is contained in:
Benjamin Otte 2017-11-22 09:25:35 +01:00
parent 516f35b865
commit 41f70e18af
7 changed files with 348 additions and 72 deletions

View File

@ -113,9 +113,30 @@ gdk_clipboard_finalize (GObject *object)
G_OBJECT_CLASS (gdk_clipboard_parent_class)->finalize (object); G_OBJECT_CLASS (gdk_clipboard_parent_class)->finalize (object);
} }
static void
gdk_clipboard_real_read_async (GdkClipboard *clipboard,
GdkContentFormats *formats,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (clipboard, cancellable, callback, user_data);
g_task_set_priority (task, io_priority);
g_task_set_source_tag (task, gdk_clipboard_read_async);
g_task_set_task_data (task, gdk_content_formats_ref (formats), (GDestroyNotify) gdk_content_formats_unref);
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Reading local content not supported yet.");
g_object_unref (task);
}
static GInputStream * static GInputStream *
gdk_clipboard_real_read (GdkClipboard *clipboard, gdk_clipboard_real_read_finish (GdkClipboard *clipboard,
const char *mime_type) const char **out_mime_type,
GAsyncResult *result,
GError **error)
{ {
/* whoop whooop */ /* whoop whooop */
return g_memory_input_stream_new (); return g_memory_input_stream_new ();
@ -130,7 +151,8 @@ gdk_clipboard_class_init (GdkClipboardClass *class)
object_class->set_property = gdk_clipboard_set_property; object_class->set_property = gdk_clipboard_set_property;
object_class->finalize = gdk_clipboard_finalize; object_class->finalize = gdk_clipboard_finalize;
class->read = gdk_clipboard_real_read; class->read_async = gdk_clipboard_real_read_async;
class->read_finish = gdk_clipboard_real_read_finish;
/** /**
* GdkClipboard:display: * GdkClipboard:display:
@ -237,17 +259,84 @@ gdk_clipboard_get_formats (GdkClipboard *clipboard)
return priv->formats; return priv->formats;
} }
GInputStream * static void
gdk_clipboard_read (GdkClipboard *clipboard, gdk_clipboard_read_internal (GdkClipboard *clipboard,
const char *mime_type) GdkContentFormats *formats,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{ {
GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (clipboard); return GDK_CLIPBOARD_GET_CLASS (clipboard)->read_async (clipboard,
formats,
io_priority,
cancellable,
callback,
user_data);
}
/**
* gdk_clipboard_read_async:
* @clipboard: a #GdkClipboard
* @mime_types: a %NULL-terminated array of mime types to choose from
* @io_priority: the [I/O priority][io-priority]
* of the request.
* @cancellable: (nullable): optional #GCancellable object, %NULL to ignore.
* @callback: (scope async): callback to call when the request is satisfied
* @user_data: (closure): the data to pass to callback function
*
* Asynchronously requests an input stream to read the @clipboard's
* contents from. When the operation is finished @callback will be called.
* You can then call gdk_clipboard_read_finish() to get the result of the
* operation.
*
* The clipboard will choose the most suitable mime type from the given list
* to fulfill the request, preferring the ones listed first.
**/
void
gdk_clipboard_read_async (GdkClipboard *clipboard,
const char **mime_types,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GdkContentFormats *formats;
g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
g_return_if_fail (mime_types != NULL && mime_types[0] != NULL);
g_return_if_fail (callback != NULL);
formats = gdk_content_formats_new (mime_types, g_strv_length ((char **) mime_types));
gdk_clipboard_read_internal (clipboard, formats, io_priority, cancellable, callback, user_data);
gdk_content_formats_unref (formats);
}
/**
* gdk_clipboard_read_finish:
* @clipboard: a #GdkClipboard
* @out_mime_type: (out) (allow-none) (transfer none): pointer to store
* the chosen mime type in or %NULL
* @result: a #GAsyncResult
* @error: a #GError location to store the error occurring, or %NULL to
* ignore.
*
* Finishes an asynchronous clipboard read started with gdk_clipboard_read_async().
*
* Returns: (transfer full): a #GInputStream or %NULL on error.
**/
GInputStream *
gdk_clipboard_read_finish (GdkClipboard *clipboard,
const char **out_mime_type,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (GDK_IS_CLIPBOARD (clipboard), NULL); g_return_val_if_fail (GDK_IS_CLIPBOARD (clipboard), NULL);
g_return_val_if_fail (mime_type != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL);
g_return_val_if_fail (gdk_content_formats_contain_mime_type (priv->formats, mime_type), NULL);
return GDK_CLIPBOARD_GET_CLASS (clipboard)->read (clipboard, mime_type); return GDK_CLIPBOARD_GET_CLASS (clipboard)->read_finish (clipboard, out_mime_type, result, error);
} }
GdkClipboard * GdkClipboard *

View File

@ -43,8 +43,17 @@ GDK_AVAILABLE_IN_3_94
GdkContentFormats * gdk_clipboard_get_formats (GdkClipboard *clipboard); GdkContentFormats * gdk_clipboard_get_formats (GdkClipboard *clipboard);
GDK_AVAILABLE_IN_3_94 GDK_AVAILABLE_IN_3_94
GInputStream * gdk_clipboard_read (GdkClipboard *clipboard, void gdk_clipboard_read_async (GdkClipboard *clipboard,
const char *mime_type); const char **mime_types,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GDK_AVAILABLE_IN_3_94
GInputStream * gdk_clipboard_read_finish (GdkClipboard *clipboard,
const char **out_mime_type,
GAsyncResult *result,
GError **error);
G_END_DECLS G_END_DECLS

View File

@ -41,8 +41,16 @@ struct _GdkClipboardClass
void (* changed) (GdkClipboard *clipboard); void (* changed) (GdkClipboard *clipboard);
/* vfuncs */ /* vfuncs */
GInputStream * (* read) (GdkClipboard *clipboard, void (* read_async) (GdkClipboard *clipboard,
const char *mime_type); GdkContentFormats *formats,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GInputStream * (* read_finish) (GdkClipboard *clipboard,
const char **out_mime_type,
GAsyncResult *result,
GError **error);
}; };
GdkClipboard * gdk_clipboard_new (GdkDisplay *display); GdkClipboard * gdk_clipboard_new (GdkDisplay *display);

View File

@ -73,10 +73,16 @@ gdk_x11_clipboard_request_targets_finish (GObject *source_object,
guint i, n_atoms; guint i, n_atoms;
bytes = g_input_stream_read_bytes_finish (stream, res, &error); bytes = g_input_stream_read_bytes_finish (stream, res, &error);
if (bytes == NULL || g_bytes_get_size (bytes) == 0) if (bytes == NULL)
{ {
if (bytes) g_error_free (error);
g_bytes_unref (bytes); g_object_unref (stream);
g_object_unref (cb);
return;
}
else if (g_bytes_get_size (bytes) == 0)
{
g_bytes_unref (bytes);
g_object_unref (stream); g_object_unref (stream);
g_object_unref (cb); g_object_unref (cb);
return; return;
@ -108,24 +114,43 @@ gdk_x11_clipboard_request_targets_finish (GObject *source_object,
} }
static void static void
gdk_x11_clipboard_request_targets (GdkX11Clipboard *cb) gdk_x11_clipboard_request_targets_got_stream (GObject *source,
GAsyncResult *result,
gpointer data)
{ {
GdkX11Clipboard *cb = data;
GInputStream *stream; GInputStream *stream;
GdkDisplay *display; GdkDisplay *display;
GError *error = NULL;
display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
stream = gdk_x11_selection_input_stream_new (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), stream = gdk_x11_selection_input_stream_new_finish (result, &error);
cb->selection, if (stream == NULL)
"TARGETS", {
cb->timestamp); g_object_unref (cb);
return;
}
display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
g_input_stream_read_bytes_async (stream, g_input_stream_read_bytes_async (stream,
SELECTION_MAX_SIZE (display), SELECTION_MAX_SIZE (display),
G_PRIORITY_DEFAULT, G_PRIORITY_DEFAULT,
NULL, NULL,
gdk_x11_clipboard_request_targets_finish, gdk_x11_clipboard_request_targets_finish,
g_object_ref (cb)); cb);
}
static void
gdk_x11_clipboard_request_targets (GdkX11Clipboard *cb)
{
gdk_x11_selection_input_stream_new_async (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)),
cb->selection,
"TARGETS",
cb->timestamp,
G_PRIORITY_DEFAULT,
NULL,
gdk_x11_clipboard_request_targets_got_stream,
g_object_ref (cb));
} }
static GdkFilterReturn static GdkFilterReturn
@ -180,16 +205,90 @@ gdk_x11_clipboard_finalize (GObject *object)
G_OBJECT_CLASS (gdk_x11_clipboard_parent_class)->finalize (object); G_OBJECT_CLASS (gdk_x11_clipboard_parent_class)->finalize (object);
} }
static GInputStream * static void
gdk_x11_clipboard_read (GdkClipboard *clipboard, gdk_x11_clipboard_read_got_stream (GObject *source,
const char *mime_type) GAsyncResult *res,
gpointer data)
{
GTask *task = data;
GError *error = NULL;
GInputStream *stream;
stream = gdk_x11_selection_input_stream_new_finish (res, &error);
/* XXX: We could try more types here */
if (stream == NULL)
g_task_return_error (task, error);
else
g_task_return_pointer (task, stream, g_object_unref);
g_object_unref (task);
}
static void
gdk_x11_clipboard_read_async (GdkClipboard *clipboard,
GdkContentFormats *formats,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{ {
GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (clipboard); GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (clipboard);
const char * const *mime_types;
GTask *task;
return gdk_x11_selection_input_stream_new (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), task = g_task_new (clipboard, cancellable, callback, user_data);
cb->selection, g_task_set_priority (task, io_priority);
mime_type, g_task_set_source_tag (task, gdk_x11_clipboard_read_async);
cb->timestamp); g_task_set_task_data (task, gdk_content_formats_ref (formats), (GDestroyNotify) gdk_content_formats_unref);
/* XXX: Sort differently? */
mime_types = gdk_content_formats_get_mime_types (formats, NULL);
gdk_x11_selection_input_stream_new_async (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)),
cb->selection,
mime_types[0],
cb->timestamp,
io_priority,
cancellable,
gdk_x11_clipboard_read_got_stream,
task);
}
static GInputStream *
gdk_x11_clipboard_read_finish (GdkClipboard *clipboard,
const char **out_mime_type,
GAsyncResult *result,
GError **error)
{
GInputStream *stream;
GTask *task;
g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (clipboard)), NULL);
task = G_TASK (result);
g_return_val_if_fail (g_task_get_source_tag (task) == gdk_x11_clipboard_read_async, NULL);
stream = g_task_propagate_pointer (task, error);
if (stream)
{
if (out_mime_type)
{
GdkContentFormats *formats;
const char * const *mime_types;
formats = g_task_get_task_data (task);
mime_types = gdk_content_formats_get_mime_types (formats, NULL);
*out_mime_type = mime_types[0];
}
g_object_ref (stream);
}
else
{
if (out_mime_type)
*out_mime_type = NULL;
}
return stream;
} }
static void static void
@ -200,7 +299,8 @@ gdk_x11_clipboard_class_init (GdkX11ClipboardClass *class)
object_class->finalize = gdk_x11_clipboard_finalize; object_class->finalize = gdk_x11_clipboard_finalize;
clipboard_class->read = gdk_x11_clipboard_read; clipboard_class->read_async = gdk_x11_clipboard_read_async;
clipboard_class->read_finish = gdk_x11_clipboard_read_finish;
} }
static void static void

View File

@ -404,42 +404,64 @@ gdk_x11_selection_input_stream_filter_event (GdkXEvent *xev,
case SelectionNotify: case SelectionNotify:
{ {
GTask *task;
/* selection is not for us */
if (priv->xselection != xevent->xselection.selection || if (priv->xselection != xevent->xselection.selection ||
priv->xtarget != xevent->xselection.target) priv->xtarget != xevent->xselection.target)
return GDK_FILTER_CONTINUE; return GDK_FILTER_CONTINUE;
/* We already received a selectionNotify before */
if (priv->pending_task == NULL ||
g_task_get_source_tag (priv->pending_task) != gdk_x11_selection_input_stream_new_async)
return GDK_FILTER_CONTINUE;
GDK_NOTE(SELECTION, g_print ("%s:%s: got SelectionNotify\n", priv->selection, priv->target)); GDK_NOTE(SELECTION, g_print ("%s:%s: got SelectionNotify\n", priv->selection, priv->target));
if (xevent->xselection.property != None) task = priv->pending_task;
bytes = get_selection_property (xdisplay, xwindow, xevent->xselection.property, &type, &format); priv->pending_task = NULL;
else
bytes = NULL;
if (bytes == NULL) if (xevent->xselection.property == None)
{ {
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
_("Format %s not supported"), priv->target);
gdk_x11_selection_input_stream_complete (stream); gdk_x11_selection_input_stream_complete (stream);
} }
else else
{ {
if (type == gdk_x11_get_xatom_by_name_for_display (priv->display, "INCR")) bytes = get_selection_property (xdisplay, xwindow, xevent->xselection.property, &type, &format);
{
/* The remainder of the selection will come through PropertyNotify g_task_return_pointer (task, g_object_ref (stream), g_object_unref);
events on xwindow */
GDK_NOTE(SELECTION, g_print ("%s:%s: initiating INCR transfer\n", priv->selection, priv->target)); if (bytes == NULL)
priv->incr = TRUE; {
gdk_x11_selection_input_stream_flush (stream); gdk_x11_selection_input_stream_complete (stream);
} }
else else
{ {
g_async_queue_push (priv->chunks, bytes); if (priv->xtype == gdk_x11_get_xatom_by_name_for_display (priv->display, "INCR"))
{
/* The remainder of the selection will come through PropertyNotify
events on xwindow */
GDK_NOTE(SELECTION, g_print ("%s:%s: initiating INCR transfer\n", priv->selection, priv->target));
priv->incr = TRUE;
gdk_x11_selection_input_stream_flush (stream);
}
else
{
g_async_queue_push (priv->chunks, bytes);
gdk_x11_selection_input_stream_complete (stream); gdk_x11_selection_input_stream_complete (stream);
}
} }
XDeleteProperty (xdisplay, xwindow, xevent->xselection.property); XDeleteProperty (xdisplay, xwindow, xevent->xselection.property);
} }
}
g_object_unref (task);
}
return GDK_FILTER_REMOVE; return GDK_FILTER_REMOVE;
default: default:
@ -447,15 +469,19 @@ gdk_x11_selection_input_stream_filter_event (GdkXEvent *xev,
} }
} }
GInputStream * void
gdk_x11_selection_input_stream_new (GdkDisplay *display, gdk_x11_selection_input_stream_new_async (GdkDisplay *display,
const char *selection, const char *selection,
const char *target, const char *target,
guint32 timestamp) guint32 timestamp,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{ {
GdkX11SelectionInputStream *stream; GdkX11SelectionInputStream *stream;
GdkX11SelectionInputStreamPrivate *priv; GdkX11SelectionInputStreamPrivate *priv;
stream = g_object_new (GDK_TYPE_X11_SELECTION_INPUT_STREAM, NULL); stream = g_object_new (GDK_TYPE_X11_SELECTION_INPUT_STREAM, NULL);
priv = gdk_x11_selection_input_stream_get_instance_private (stream); priv = gdk_x11_selection_input_stream_get_instance_private (stream);
@ -477,6 +503,25 @@ gdk_x11_selection_input_stream_new (GdkDisplay *display,
GDK_X11_DISPLAY (display)->leader_window, GDK_X11_DISPLAY (display)->leader_window,
timestamp); timestamp);
return g_object_ref (stream); priv->pending_task = g_task_new (NULL, cancellable, callback, user_data);
g_task_set_source_tag (priv->pending_task, gdk_x11_selection_input_stream_new_async);
g_task_set_priority (priv->pending_task, io_priority);
}
GInputStream *
gdk_x11_selection_input_stream_new_finish (GAsyncResult *result,
GError **error)
{
GInputStream *stream;
GTask *task;
g_return_val_if_fail (g_task_is_valid (result, NULL), NULL);
task = G_TASK (result);
g_return_val_if_fail (g_task_get_source_tag (task) == gdk_x11_selection_input_stream_new_async, NULL);
stream = g_task_propagate_pointer (task, error);
if (stream)
g_object_ref (stream);
return stream;
} }

View File

@ -50,10 +50,17 @@ struct GdkX11SelectionInputStreamClass
GType gdk_x11_selection_input_stream_get_type (void) G_GNUC_CONST; GType gdk_x11_selection_input_stream_get_type (void) G_GNUC_CONST;
GInputStream * gdk_x11_selection_input_stream_new (GdkDisplay *display, void gdk_x11_selection_input_stream_new_async (GdkDisplay *display,
const char *selection, const char *selection,
const char *target, const char *target,
guint32 timestamp); guint32 timestamp,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GInputStream * gdk_x11_selection_input_stream_new_finish (GAsyncResult *result,
GError **error);
G_END_DECLS G_END_DECLS

View File

@ -49,6 +49,30 @@ pixbuf_loaded_cb (GObject *stream,
g_object_unref (pixbuf); g_object_unref (pixbuf);
} }
static void
clipboard_read_pixbuf_cb (GObject *clipboard,
GAsyncResult *result,
gpointer image)
{
GInputStream *stream;
GError *error = NULL;
stream = gdk_clipboard_read_finish (GDK_CLIPBOARD (clipboard),
NULL,
result,
&error);
if (stream)
{
gdk_pixbuf_new_from_stream_async (stream, NULL, pixbuf_loaded_cb, image);
g_object_unref (stream);
}
else
{
g_print ("%s\n", error->message);
g_error_free (error);
}
}
static void static void
visible_child_changed_cb (GtkWidget *stack, visible_child_changed_cb (GtkWidget *stack,
GParamSpec *pspec, GParamSpec *pspec,
@ -63,19 +87,13 @@ visible_child_changed_cb (GtkWidget *stack,
else if (g_str_equal (visible_child, "image")) else if (g_str_equal (visible_child, "image"))
{ {
GtkWidget *image = gtk_stack_get_child_by_name (GTK_STACK (stack), "image"); GtkWidget *image = gtk_stack_get_child_by_name (GTK_STACK (stack), "image");
GdkContentFormats *formats;
GInputStream *stream;
formats = gdk_clipboard_get_formats (clipboard); gdk_clipboard_read_async (clipboard,
if (gdk_content_formats_contain_mime_type (formats, "image/png")) (const gchar*[2]) { "image/png", NULL },
stream = gdk_clipboard_read (clipboard, "image/png"); G_PRIORITY_DEFAULT,
else NULL,
stream = NULL; clipboard_read_pixbuf_cb,
if (stream) image);
{
gdk_pixbuf_new_from_stream_async (stream, NULL, pixbuf_loaded_cb, image);
g_object_unref (stream);
}
} }
} }