From 9b78d76873c075aaac0e5d62382bfb1cd1ba065e Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 23 Nov 2017 01:59:19 +0100 Subject: [PATCH] x11: Improve fallbacks for text (1) Try all passed in formats in order if one of them fails. (2) Don't blindly accept all formats, make sure they are mime types (3) Add a bunch of special non-mime types that plug converters to get to mime types --- gdk/x11/gdkclipboard-x11.c | 233 ++++++++++++++++++++++---- gdk/x11/gdkselectioninputstream-x11.c | 28 +++- gdk/x11/gdkselectioninputstream-x11.h | 2 + gdk/x11/gdktextlistconverter-x11.c | 169 +++++++++++++++++++ gdk/x11/gdktextlistconverter-x11.h | 44 +++++ gdk/x11/meson.build | 1 + 6 files changed, 444 insertions(+), 33 deletions(-) create mode 100644 gdk/x11/gdktextlistconverter-x11.c create mode 100644 gdk/x11/gdktextlistconverter-x11.h diff --git a/gdk/x11/gdkclipboard-x11.c b/gdk/x11/gdkclipboard-x11.c index a88c86d1ce..aef23b7ee3 100644 --- a/gdk/x11/gdkclipboard-x11.c +++ b/gdk/x11/gdkclipboard-x11.c @@ -19,8 +19,11 @@ #include "gdkclipboardprivate.h" #include "gdkclipboard-x11.h" -#include "gdkselectioninputstream-x11.h" + +#include "gdkintl.h" #include "gdkprivate-x11.h" +#include "gdkselectioninputstream-x11.h" +#include "gdktextlistconverter-x11.h" #include #include @@ -57,6 +60,121 @@ G_DEFINE_TYPE (GdkX11Clipboard, gdk_x11_clipboard, GDK_TYPE_CLIPBOARD) ? XMaxRequestSize (GDK_DISPLAY_XDISPLAY (display)) - 100 \ : XExtendedMaxRequestSize (GDK_DISPLAY_XDISPLAY (display)) - 100) +static GInputStream * +text_list_convert (GdkX11Clipboard *cb, + GInputStream *stream, + const char *encoding, + int format) +{ + GInputStream *converter_stream; + GConverter *converter; + + converter = gdk_x11_text_list_converter_to_utf8_new (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), + encoding, + format); + converter_stream = g_converter_input_stream_new (stream, converter); + + g_object_unref (converter); + g_object_unref (stream); + + return converter_stream; +} + +static GInputStream * +no_convert (GdkX11Clipboard *cb, + GInputStream *stream, + const char *encoding, + int format) +{ + return stream; +} + +static const struct { + const char *x_target; + const char *mime_type; + GInputStream * (* convert) (GdkX11Clipboard *, GInputStream *, const char *, int); +} special_targets[] = { + { "UTF8_STRING", "text/plain;charset=utf-8", no_convert }, + { "COMPOUND_TEXT", "text/plain;charset=utf-8", text_list_convert }, + { "TEXT", "text/plain;charset=utf-8", text_list_convert }, + { "STRING", "text/plain;charset=utf-8", text_list_convert } +}; + +static void +print_atoms (GdkX11Clipboard *cb, + const char *prefix, + const Atom *atoms, + gsize n_atoms) +{ + GDK_NOTE(CLIPBOARD, + GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb)); + gsize i; + + g_printerr ("%s: %s [ ", cb->selection, prefix); + for (i = 0; i < n_atoms; i++) + { + g_printerr ("%s%s", i > 0 ? ", " : "", gdk_x11_get_xatom_name_for_display (display , atoms[i])); + } + g_printerr (" ]\n"); + ); +} + +static GSList * +gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats) +{ + GSList *targets; + const char * const *mime_types; + gsize i, j, n_mime_types; + + targets = NULL; + mime_types = gdk_content_formats_get_mime_types (formats, &n_mime_types); + + for (i = 0; i < n_mime_types; i++) + { + for (j = 0; j < G_N_ELEMENTS (special_targets); j++) + { + if (g_str_equal (mime_types[i], special_targets[j].mime_type)) + targets = g_slist_prepend (targets, (gpointer) g_intern_string (special_targets[i].x_target)); + } + targets = g_slist_prepend (targets, (gpointer) mime_types[i]); + } + + return g_slist_reverse (targets); +} + +static GdkContentFormats * +gdk_x11_clipboard_formats_from_atoms (GdkDisplay *display, + const Atom *atoms, + gsize n_atoms) +{ + GdkContentFormatsBuilder *builder; + gsize i, j; + + builder = gdk_content_formats_builder_new (); + for (i = 0; i < n_atoms; i++) + { + const char *name; + + name = gdk_x11_get_xatom_name_for_display (display , atoms[i]); + if (strchr (name, '/')) + { + gdk_content_formats_builder_add_mime_type (builder, name); + continue; + } + + for (j = 0; j < G_N_ELEMENTS (special_targets); j++) + { + if (g_str_equal (name, special_targets[j].x_target)) + { + gdk_content_formats_builder_add_mime_type (builder, special_targets[j].mime_type); + break; + } + } + } + + return gdk_content_formats_builder_free (builder); +} + static void gdk_x11_clipboard_request_targets_finish (GObject *source_object, GAsyncResult *res, @@ -66,11 +184,8 @@ gdk_x11_clipboard_request_targets_finish (GObject *source_object, GdkX11Clipboard *cb = user_data; GdkDisplay *display; GdkContentFormats *formats; - GdkContentFormatsBuilder *builder; GBytes *bytes; GError *error = NULL; - const Atom *atoms; - guint i, n_atoms; bytes = g_input_stream_read_bytes_finish (stream, res, &error); if (bytes == NULL) @@ -88,17 +203,15 @@ gdk_x11_clipboard_request_targets_finish (GObject *source_object, return; } - display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb)); + print_atoms (cb, + "received targets", + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes) / sizeof (Atom)); - atoms = g_bytes_get_data (bytes, NULL); - n_atoms = g_bytes_get_size (bytes) / sizeof (Atom); - builder = gdk_content_formats_builder_new (); - for (i = 0; i < n_atoms; i++) - { - gdk_content_formats_builder_add_mime_type (builder, gdk_x11_get_xatom_name_for_display (display , atoms[i])); - } - gdk_content_formats_builder_add_formats (builder, gdk_clipboard_get_formats (GDK_CLIPBOARD (cb))); - formats = gdk_content_formats_builder_free (builder); + display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb)); + formats = gdk_x11_clipboard_formats_from_atoms (display, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes) / sizeof (Atom)); GDK_NOTE(CLIPBOARD, char *s = gdk_content_formats_to_string (formats); g_printerr ("%s: got formats: %s\n", cb->selection, s); g_free (s)); /* union with previously loaded formats */ @@ -122,13 +235,22 @@ gdk_x11_clipboard_request_targets_got_stream (GObject *source, GInputStream *stream; GdkDisplay *display; GError *error = NULL; + const char *type; + int format; - stream = gdk_x11_selection_input_stream_new_finish (result, &error); + stream = gdk_x11_selection_input_stream_new_finish (result, &type, &format, &error); if (stream == NULL) { g_object_unref (cb); return; } + else if (!g_str_equal (type, "ATOM") || format != 32) + { + g_input_stream_close (stream, NULL, NULL); + g_object_unref (stream); + g_object_unref (cb); + return; + } display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb)); @@ -213,13 +335,61 @@ gdk_x11_clipboard_read_got_stream (GObject *source, GTask *task = data; GError *error = NULL; GInputStream *stream; + const char *type; + int format; - stream = gdk_x11_selection_input_stream_new_finish (res, &error); - /* XXX: We could try more types here */ + stream = gdk_x11_selection_input_stream_new_finish (res, &type, &format, &error); if (stream == NULL) - g_task_return_error (task, error); + { + GSList *targets, *next; + + targets = g_task_get_task_data (task); + next = targets->next; + if (next) + { + GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (g_task_get_source_object (task)); + + GDK_NOTE(CLIPBOARD, g_printerr ("%s: reading %s failed, trying %s next\n", + cb->selection, (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_clipboard_get_display (GDK_CLIPBOARD (cb)), + cb->selection, + next->data, + cb->timestamp, + g_task_get_priority (task), + g_task_get_cancellable (task), + gdk_x11_clipboard_read_got_stream, + task); + g_error_free (error); + return; + } + + g_task_return_error (task, error); + } else - g_task_return_pointer (task, stream, g_object_unref); + { + GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (g_task_get_source_object (task)); + const char *mime_type = ((GSList *) g_task_get_task_data (task))->data; + gsize i; + + for (i = 0; i < G_N_ELEMENTS (special_targets); i++) + { + if (g_str_equal (mime_type, special_targets[i].x_target)) + { + 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; + } + } + + GDK_NOTE(CLIPBOARD, g_printerr ("%s: reading clipboard as %s now\n", + cb->selection, mime_type)); + g_task_return_pointer (task, stream, g_object_unref); + } g_object_unref (task); } @@ -233,20 +403,27 @@ gdk_x11_clipboard_read_async (GdkClipboard *clipboard, gpointer user_data) { GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (clipboard); - const char * const *mime_types; + GSList *targets; 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_x11_clipboard_read_async); - 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); + 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(CLIPBOARD, g_printerr ("%s: new read for %s (%u other options)\n", + cb->selection, (char *) targets->data, g_slist_length (targets->next))); gdk_x11_selection_input_stream_new_async (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), cb->selection, - mime_types[0], + targets->data, cb->timestamp, io_priority, cancellable, @@ -273,12 +450,10 @@ gdk_x11_clipboard_read_finish (GdkClipboard *clipboard, { if (out_mime_type) { - GdkContentFormats *formats; - const char * const *mime_types; + GSList *targets; - formats = g_task_get_task_data (task); - mime_types = gdk_content_formats_get_mime_types (formats, NULL); - *out_mime_type = mime_types[0]; + targets = g_task_get_task_data (task); + *out_mime_type = targets->data; } g_object_ref (stream); } diff --git a/gdk/x11/gdkselectioninputstream-x11.c b/gdk/x11/gdkselectioninputstream-x11.c index a694d05cb7..9ba704d6d0 100644 --- a/gdk/x11/gdkselectioninputstream-x11.c +++ b/gdk/x11/gdkselectioninputstream-x11.c @@ -40,6 +40,9 @@ struct GdkX11SelectionInputStreamPrivate { Atom xtarget; char *property; Atom xproperty; + const char *type; + Atom xtype; + int format; GTask *pending_task; guchar *pending_data; @@ -150,6 +153,8 @@ gdk_x11_selection_input_stream_complete (GdkX11SelectionInputStream *stream) if (priv->complete) return; + GDK_NOTE(SELECTION, g_printerr ("%s:%s: transfer complete\n", + priv->selection, priv->target)); priv->complete = TRUE; g_async_queue_push (priv->chunks, g_bytes_new (NULL, 0)); @@ -457,7 +462,8 @@ gdk_x11_selection_input_stream_filter_event (GdkXEvent *xev, } else { - bytes = get_selection_property (xdisplay, xwindow, xevent->xselection.property, &type, &format); + bytes = get_selection_property (xdisplay, xwindow, xevent->xselection.property, &priv->xtype, &priv->format); + priv->type = gdk_x11_get_xatom_name_for_display (priv->display, priv->xtype); g_task_return_pointer (task, g_object_ref (stream), g_object_unref); @@ -477,6 +483,9 @@ gdk_x11_selection_input_stream_filter_event (GdkXEvent *xev, } else { + GDK_NOTE(SELECTION, g_printerr ("%s:%s: reading %zu bytes\n", + priv->selection, priv->target, + g_bytes_get_size (bytes))); g_async_queue_push (priv->chunks, bytes); gdk_x11_selection_input_stream_complete (stream); @@ -536,9 +545,11 @@ gdk_x11_selection_input_stream_new_async (GdkDisplay *display, GInputStream * gdk_x11_selection_input_stream_new_finish (GAsyncResult *result, + const char **type, + int *format, GError **error) { - GInputStream *stream; + GdkX11SelectionInputStream *stream; GTask *task; g_return_val_if_fail (g_task_is_valid (result, NULL), NULL); @@ -547,7 +558,16 @@ gdk_x11_selection_input_stream_new_finish (GAsyncResult *result, stream = g_task_propagate_pointer (task, error); if (stream) - g_object_ref (stream); - return stream; + { + GdkX11SelectionInputStreamPrivate *priv = gdk_x11_selection_input_stream_get_instance_private (stream); + + if (type) + *type = priv->type; + if (format) + *format = priv->format; + g_object_ref (stream); + } + + return G_INPUT_STREAM (stream); } diff --git a/gdk/x11/gdkselectioninputstream-x11.h b/gdk/x11/gdkselectioninputstream-x11.h index c8bd19d55e..18c0763025 100644 --- a/gdk/x11/gdkselectioninputstream-x11.h +++ b/gdk/x11/gdkselectioninputstream-x11.h @@ -59,6 +59,8 @@ void gdk_x11_selection_input_stream_new_async (GdkDisplay GAsyncReadyCallback callback, gpointer user_data); GInputStream * gdk_x11_selection_input_stream_new_finish (GAsyncResult *result, + const char **type, + int *format, GError **error); diff --git a/gdk/x11/gdktextlistconverter-x11.c b/gdk/x11/gdktextlistconverter-x11.c new file mode 100644 index 0000000000..7fe56d1734 --- /dev/null +++ b/gdk/x11/gdktextlistconverter-x11.c @@ -0,0 +1,169 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2017 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * Author: Benjamin Otte + */ + +#include "config.h" + +#include "gdktextlistconverter-x11.h" + +#include "gdkintl.h" +#include "gdkprivate-x11.h" + +#define GDK_X11_TEXT_LIST_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GDK_TYPE_X11_TEXT_LIST_CONVERTER, GdkX11TextListConverterClass)) +#define GDK_IS_X11_TEXT_LIST_CONVERTER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GDK_TYPE_X11_TEXT_LIST_CONVERTER)) +#define GDK_X11_TEXT_LIST_CONVERTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDK_TYPE_X11_TEXT_LIST_CONVERTER, GdkX11TextListConverterClass)) + +typedef struct _GdkX11TextListConverterClass GdkX11TextListConverterClass; + +struct _GdkX11TextListConverter +{ + GObject parent_instance; + + GdkDisplay *display; + + const char *encoding; /* interned */ + gint format; +}; + +struct _GdkX11TextListConverterClass +{ + GObjectClass parent_class; +}; + +static GConverterResult +gdk_x11_text_list_converter_convert (GConverter *converter, + const void *inbuf, + gsize inbuf_size, + void *outbuf, + gsize outbuf_size, + GConverterFlags flags, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + GdkX11TextListConverter *conv = GDK_X11_TEXT_LIST_CONVERTER (converter); + gint count; + char **list; + + if (!(flags & G_CONVERTER_INPUT_AT_END)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, + _("Need complete input to do conversion")); + return G_CONVERTER_ERROR; + } + + count = _gdk_x11_display_text_property_to_utf8_list (conv->display, + conv->encoding, + conv->format, + inbuf, + inbuf_size, + &list); + if (count < 0) + { + /* XXX: add error handling from gdkselection-x11.c */ + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, + _("Not enough space in destination")); + return G_CONVERTER_ERROR; + } + else if (count == 0) + { + if (outbuf_size < 1) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, + _("Not enough space in destination")); + return G_CONVERTER_ERROR; + } + ((gchar *) outbuf)[0] = 0; + *bytes_read = inbuf_size; + *bytes_written = 1; + return G_CONVERTER_FINISHED; + } + else + { + gsize len = strlen (list[0]) + 1; + + if (outbuf_size < len) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, + _("Not enough space in destination")); + return G_CONVERTER_ERROR; + } + memcpy (outbuf, list[0], len); + g_strfreev (list); + *bytes_read = inbuf_size; + *bytes_written = len; + return G_CONVERTER_FINISHED; + } +} + +static void +gdk_x11_text_list_converter_reset (GConverter *converter) +{ +} + +static void +gdk_x11_text_list_converter_iface_init (GConverterIface *iface) +{ + iface->convert = gdk_x11_text_list_converter_convert; + iface->reset = gdk_x11_text_list_converter_reset; +} + +G_DEFINE_TYPE_WITH_CODE (GdkX11TextListConverter, gdk_x11_text_list_converter, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER, + gdk_x11_text_list_converter_iface_init)) + +static void +gdk_x11_text_list_converter_finalize (GObject *object) +{ + GdkX11TextListConverter *conv = GDK_X11_TEXT_LIST_CONVERTER (object); + + g_object_unref (conv->display); + + G_OBJECT_CLASS (gdk_x11_text_list_converter_parent_class)->finalize (object); +} + +static void +gdk_x11_text_list_converter_class_init (GdkX11TextListConverterClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gdk_x11_text_list_converter_finalize; +} + +static void +gdk_x11_text_list_converter_init (GdkX11TextListConverter *local) +{ +} + +GConverter * +gdk_x11_text_list_converter_to_utf8_new (GdkDisplay *display, + const char *encoding, + int format) +{ + GdkX11TextListConverter *conv; + + conv = g_object_new (GDK_TYPE_X11_TEXT_LIST_CONVERTER, NULL); + + conv->display = g_object_ref (display); + conv->encoding = g_intern_string (encoding); + conv->format = format; + + return G_CONVERTER (conv); +} + diff --git a/gdk/x11/gdktextlistconverter-x11.h b/gdk/x11/gdktextlistconverter-x11.h new file mode 100644 index 0000000000..9017a8b7e1 --- /dev/null +++ b/gdk/x11/gdktextlistconverter-x11.h @@ -0,0 +1,44 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2017 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * Author: Benjamin Otte + */ + +#ifndef __GDK_X11_TEXT_LIST_CONVERTER_H__ +#define __GDK_X11_TEXT_LIST_CONVERTER_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GDK_TYPE_X11_TEXT_LIST_CONVERTER (gdk_x11_text_list_converter_get_type ()) +#define GDK_X11_TEXT_LIST_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GDK_TYPE_X11_TEXT_LIST_CONVERTER, GdkX11TextListConverter)) +#define GDK_IS_X11_TEXT_LIST_CONVERTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDK_TYPE_X11_TEXT_LIST_CONVERTER)) + +typedef struct _GdkX11TextListConverter GdkX11TextListConverter; + +GType gdk_x11_text_list_converter_get_type (void) G_GNUC_CONST; + +GConverter * gdk_x11_text_list_converter_to_utf8_new (GdkDisplay *display, + const char *encoding, + int format); + + +G_END_DECLS + +#endif /* __GDK_X11_TEXT_LIST_CONVERTER_H__ */ diff --git a/gdk/x11/meson.build b/gdk/x11/meson.build index 57ed429df4..db74295d8a 100644 --- a/gdk/x11/meson.build +++ b/gdk/x11/meson.build @@ -22,6 +22,7 @@ gdk_x11_sources = files([ 'gdkscreen-x11.c', 'gdkselection-x11.c', 'gdkselectioninputstream-x11.c', + 'gdktextlistconverter-x11.c', 'gdkvisual-x11.c', 'gdkvulkancontext-x11.c', 'gdkwindow-x11.c',