gtk/gdk/x11/gdkclipboard-x11.c
Benjamin Otte 66f1fef083 x11: Explicitly close_async() the output stream
We need to be very careful when writing data, because if we aren't, sync
functions will be called on the output stream and X11 does not like that
at all.
2021-12-13 01:51:35 +01:00

868 lines
31 KiB
C

/* GDK - The GIMP Drawing Kit
* 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 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gdkclipboardprivate.h"
#include "gdkclipboard-x11.h"
#include "gdkintl.h"
#include "gdkdisplay-x11.h"
#include "gdkprivate-x11.h"
#include "gdkselectioninputstream-x11.h"
#include "gdkselectionoutputstream-x11.h"
#include "gdktextlistconverter-x11.h"
#include "gdk/gdk-private.h"
#include <string.h>
#include <X11/Xatom.h>
#ifdef HAVE_XFIXES
#include <X11/extensions/Xfixes.h>
#endif
#define IDLE_ABORT_TIME 30 /* seconds */
typedef struct _GdkX11ClipboardClass GdkX11ClipboardClass;
typedef struct _RetrievalInfo RetrievalInfo;
struct _GdkX11Clipboard
{
GdkClipboard parent;
char *selection;
Atom xselection;
gulong timestamp;
GTask *store_task;
};
struct _GdkX11ClipboardClass
{
GdkClipboardClass parent_class;
};
G_DEFINE_TYPE (GdkX11Clipboard, gdk_x11_clipboard, GDK_TYPE_CLIPBOARD)
static void
print_atoms (GdkX11Clipboard *cb,
const char *prefix,
const Atom *atoms,
gsize n_atoms)
{
GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), CLIPBOARD, {
gsize i;
GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
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 void
gdk_x11_clipboard_default_output_closed (GObject *stream,
GAsyncResult *result,
gpointer user_data)
{
GError *error = NULL;
if (!g_output_stream_close_finish (G_OUTPUT_STREAM (stream), result, &error))
{
GDK_NOTE (CLIPBOARD,
g_printerr ("-------: failed to close stream: %s\n",
error->message));
g_error_free (error);
}
g_object_unref (stream);
}
static void
gdk_x11_clipboard_default_output_done (GObject *clipboard,
GAsyncResult *result,
gpointer user_data)
{
GOutputStream *stream = user_data;
GError *error = NULL;
if (!gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, &error))
{
GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (clipboard)), CLIPBOARD,
g_printerr ("%s: failed to write stream: %s\n",
GDK_X11_CLIPBOARD (clipboard)->selection, error->message));
g_error_free (error);
}
g_output_stream_close_async (stream,
G_PRIORITY_DEFAULT,
NULL,
gdk_x11_clipboard_default_output_closed,
NULL);
}
static void
gdk_x11_clipboard_default_output_handler (GOutputStream *stream,
const char *mime_type,
gpointer user_data)
{
gdk_clipboard_write_async (GDK_CLIPBOARD (user_data),
mime_type,
stream,
G_PRIORITY_DEFAULT,
NULL,
gdk_x11_clipboard_default_output_done,
stream);
}
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);
const char *type;
int format;
} special_targets[] = {
{ "UTF8_STRING", "text/plain;charset=utf-8", no_convert, "UTF8_STRING", 8 },
{ "COMPOUND_TEXT", "text/plain;charset=utf-8", text_list_convert, "COMPOUND_TEXT", 8 },
{ "TEXT", "text/plain;charset=utf-8", text_list_convert, "STRING", 8 },
{ "STRING", "text/plain;charset=utf-8", text_list_convert, "STRING", 8 },
{ "TARGETS", NULL, NULL, "ATOM", 32 },
{ "TIMESTAMP", NULL, NULL, "INTEGER", 32 },
{ "SAVE_TARGETS", NULL, NULL, "NULL", 32 }
};
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 (special_targets[j].mime_type == NULL)
continue;
if (g_str_equal (mime_types[i], special_targets[j].mime_type))
targets = g_slist_prepend (targets, (gpointer) g_intern_string (special_targets[j].x_target));
}
targets = g_slist_prepend (targets, (gpointer) mime_types[i]);
}
return g_slist_reverse (targets);
}
Atom *
gdk_x11_clipboard_formats_to_atoms (GdkDisplay *display,
gboolean include_special,
GdkContentFormats *formats,
gsize *n_atoms)
{
GSList *l, *targets;
Atom *atoms;
gsize i;
targets = gdk_x11_clipboard_formats_to_targets (formats);
if (include_special)
{
for (i = 0; i < G_N_ELEMENTS (special_targets); i++)
{
if (special_targets[i].mime_type != NULL)
continue;
targets = g_slist_prepend (targets, (gpointer) g_intern_string (special_targets[i].x_target));
}
}
*n_atoms = g_slist_length (targets);
atoms = g_new (Atom, *n_atoms);
i = 0;
for (l = targets; l; l = l->next)
atoms[i++] = gdk_x11_get_xatom_by_name_for_display (display, l->data);
return atoms;
}
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))
{
if (special_targets[j].mime_type)
gdk_content_formats_builder_add_mime_type (builder, special_targets[j].mime_type);
break;
}
}
}
return gdk_content_formats_builder_free_to_formats (builder);
}
static void
gdk_x11_clipboard_request_targets_finish (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GInputStream *stream = G_INPUT_STREAM (source_object);
GdkX11Clipboard *cb = user_data;
GdkDisplay *display;
GdkContentFormats *formats;
GBytes *bytes;
GError *error = NULL;
display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
bytes = g_input_stream_read_bytes_finish (stream, res, &error);
if (bytes == NULL)
{
GDK_DISPLAY_NOTE (display, CLIPBOARD,
g_printerr ("%s: error reading TARGETS: %s\n", cb->selection, error->message));
g_error_free (error);
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 (cb);
return;
}
else if (gdk_clipboard_is_local (GDK_CLIPBOARD (cb)))
{
/* FIXME: Use a cancellable for this request, so that we don't do he brittle
* is_local() check */
g_bytes_unref (bytes);
g_object_unref (stream);
g_object_unref (cb);
return;
}
print_atoms (cb,
"received targets",
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes) / sizeof (Atom));
formats = gdk_x11_clipboard_formats_from_atoms (display,
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes) / sizeof (Atom));
GDK_DISPLAY_NOTE (display, 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 */
formats = gdk_content_formats_union (formats, gdk_clipboard_get_formats (GDK_CLIPBOARD (cb)));
gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb), formats);
gdk_content_formats_unref (formats);
g_bytes_unref (bytes);
g_input_stream_read_bytes_async (stream,
gdk_x11_display_get_max_request_size (display),
G_PRIORITY_DEFAULT,
NULL,
gdk_x11_clipboard_request_targets_finish,
cb);
}
static void
gdk_x11_clipboard_request_targets_got_stream (GObject *source,
GAsyncResult *result,
gpointer data)
{
GdkX11Clipboard *cb = data;
GInputStream *stream;
GdkDisplay *display;
GError *error = NULL;
const char *type;
int format;
display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
stream = gdk_x11_selection_input_stream_new_finish (result, &type, &format, &error);
if (stream == NULL)
{
GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: can't request TARGETS: %s\n", cb->selection, error->message));
g_object_unref (cb);
g_error_free (error);
return;
}
else if (!g_str_equal (type, "ATOM") || format != 32)
{
GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: Wrong reply type to TARGETS: type %s != ATOM or format %d != 32\n",
cb->selection, type, format));
g_input_stream_close (stream, NULL, NULL);
g_object_unref (stream);
g_object_unref (cb);
return;
}
g_input_stream_read_bytes_async (stream,
gdk_x11_display_get_max_request_size (display),
G_PRIORITY_DEFAULT,
NULL,
gdk_x11_clipboard_request_targets_finish,
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 void
gdk_x11_clipboard_claim_remote (GdkX11Clipboard *cb,
guint32 timestamp)
{
GdkContentFormats *empty;
empty = gdk_content_formats_new (NULL, 0);
gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb), empty);
gdk_content_formats_unref (empty);
cb->timestamp = timestamp;
gdk_x11_clipboard_request_targets (cb);
}
static gboolean
gdk_x11_clipboard_xevent (GdkDisplay *display,
const XEvent *xevent,
gpointer data)
{
GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (data);
Window xwindow;
xwindow = GDK_X11_DISPLAY (display)->leader_window;
if (xevent->xany.window != xwindow)
return FALSE;
switch (xevent->type)
{
case SelectionClear:
if (xevent->xselectionclear.selection != cb->xselection)
return FALSE;
if (xevent->xselectionclear.time < cb->timestamp)
{
GDK_DISPLAY_NOTE (display, CLIPBOARD,
g_printerr ("%s: ignoring SelectionClear with too old timestamp (%lu vs %lu)\n",
cb->selection, xevent->xselectionclear.time, cb->timestamp));
return FALSE;
}
GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: got SelectionClear\n", cb->selection));
gdk_x11_clipboard_claim_remote (cb, xevent->xselectionclear.time);
return TRUE;
case SelectionNotify:
/* This code only checks clipboard manager replies, so... */
if (!g_str_equal (cb->selection, "CLIPBOARD"))
return FALSE;
/* selection is not for us */
if (xevent->xselection.selection != gdk_x11_get_xatom_by_name_for_display (display, "CLIPBOARD_MANAGER") ||
xevent->xselection.target != gdk_x11_get_xatom_by_name_for_display (display, "SAVE_TARGETS"))
return FALSE;
/* We already received a selectionNotify before */
if (cb->store_task == NULL)
{
GDK_DISPLAY_NOTE (display, CLIPBOARD,
g_printerr ("%s: got SelectionNotify for nonexisting task?!\n", cb->selection));
return FALSE;
}
GDK_DISPLAY_NOTE (display, CLIPBOARD,
g_printerr ("%s: got SelectionNotify for store task\n", cb->selection));
if (xevent->xselection.property != None)
g_task_return_boolean (cb->store_task, TRUE);
else
g_task_return_new_error (cb->store_task, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Clipboard manager could not store selection."));
g_clear_object (&cb->store_task);
return FALSE;
case SelectionRequest:
{
#ifdef G_ENABLE_DEBUG
const char *target, *property;
#endif
if (xevent->xselectionrequest.selection != cb->xselection)
return FALSE;
#ifdef G_ENABLE_DEBUG
target = gdk_x11_get_xatom_name_for_display (display, xevent->xselectionrequest.target);
if (xevent->xselectionrequest.property == None)
property = target;
else
property = gdk_x11_get_xatom_name_for_display (display, xevent->xselectionrequest.property);
#endif
if (!gdk_clipboard_is_local (GDK_CLIPBOARD (cb)))
{
GDK_DISPLAY_NOTE (display, CLIPBOARD,
g_printerr ("%s: got SelectionRequest for %s @ %s even though we don't own the selection, huh?\n",
cb->selection, target, property));
return TRUE;
}
if (xevent->xselectionrequest.requestor == None)
{
GDK_DISPLAY_NOTE (display, CLIPBOARD,
g_printerr ("%s: got SelectionRequest for %s @ %s with NULL window, ignoring\n",
cb->selection, target, property));
return TRUE;
}
GDK_DISPLAY_NOTE (display, CLIPBOARD,
g_printerr ("%s: got SelectionRequest for %s @ %s\n", cb->selection, target, property));
gdk_x11_selection_output_streams_create (display,
gdk_clipboard_get_formats (GDK_CLIPBOARD (cb)),
xevent->xselectionrequest.requestor,
xevent->xselectionrequest.selection,
xevent->xselectionrequest.target,
xevent->xselectionrequest.property ? xevent->xselectionrequest.property
: xevent->xselectionrequest.target,
xevent->xselectionrequest.time,
gdk_x11_clipboard_default_output_handler,
cb);
return TRUE;
}
default:
#ifdef HAVE_XFIXES
if (xevent->type - GDK_X11_DISPLAY (display)->xfixes_event_base == XFixesSelectionNotify)
{
XFixesSelectionNotifyEvent *sn = (XFixesSelectionNotifyEvent *) xevent;
if (sn->selection != cb->xselection)
return FALSE;
if (sn->selection_timestamp < cb->timestamp)
{
GDK_DISPLAY_NOTE (display, CLIPBOARD,
g_printerr ("%s: Ignoring XFixesSelectionNotify with too old timestamp (%lu vs %lu)\n",
cb->selection, sn->selection_timestamp, cb->timestamp));
return FALSE;
}
if (sn->owner == GDK_X11_DISPLAY (display)->leader_window)
{
GDK_DISPLAY_NOTE (display, CLIPBOARD,
g_printerr ("%s: Ignoring XFixesSelectionNotify for ourselves\n", cb->selection));
return FALSE;
}
GDK_DISPLAY_NOTE (display, CLIPBOARD,
g_printerr ("%s: Received XFixesSelectionNotify, claiming selection\n", cb->selection));
gdk_x11_clipboard_claim_remote (cb, sn->selection_timestamp);
}
#endif
return FALSE;
}
}
static void
gdk_x11_clipboard_finalize (GObject *object)
{
GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (object);
g_signal_handlers_disconnect_by_func (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)),
gdk_x11_clipboard_xevent,
cb);
g_free (cb->selection);
G_OBJECT_CLASS (gdk_x11_clipboard_parent_class)->finalize (object);
}
static gboolean
gdk_x11_clipboard_claim (GdkClipboard *clipboard,
GdkContentFormats *formats,
gboolean local,
GdkContentProvider *content)
{
if (local)
{
GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (clipboard);
GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
Window xwindow = GDK_X11_DISPLAY (display)->leader_window;
guint32 time;
time = gdk_x11_get_server_time (GDK_X11_DISPLAY (display)->leader_gdk_surface);
if (content)
{
XSetSelectionOwner (xdisplay, cb->xselection, xwindow, time);
if (XGetSelectionOwner (xdisplay, cb->xselection) != xwindow)
{
GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: failed XSetSelectionOwner()\n", cb->selection));
return FALSE;
}
}
else
{
XSetSelectionOwner (xdisplay, cb->xselection, None, time);
}
cb->timestamp = time;
GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: claimed via XSetSelectionOwner()\n", cb->selection));
}
return GDK_CLIPBOARD_CLASS (gdk_x11_clipboard_parent_class)->claim (clipboard, formats, local, content);
}
static void
gdk_x11_clipboard_store_async (GdkClipboard *clipboard,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (clipboard);
GdkDisplay *display = gdk_clipboard_get_display (clipboard);
Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
Atom clipboard_manager, save_targets, property_name;
GdkContentProvider *content;
GdkContentFormats *formats;
Atom *atoms;
gsize n_atoms;
int error;
/* clipboard managers don't work on anything but the clipbpoard selection */
if (!g_str_equal (cb->selection, "CLIPBOARD"))
{
GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: can only store on CLIPBOARD\n", cb->selection));
GDK_CLIPBOARD_CLASS (gdk_x11_clipboard_parent_class)->store_async (clipboard,
io_priority,
cancellable,
callback,
user_data);
return;
}
cb->store_task = g_task_new (clipboard, cancellable, callback, user_data);
g_task_set_priority (cb->store_task, io_priority);
g_task_set_source_tag (cb->store_task, gdk_x11_clipboard_store_async);
clipboard_manager = gdk_x11_get_xatom_by_name_for_display (display, "CLIPBOARD_MANAGER");
save_targets = gdk_x11_get_xatom_by_name_for_display (display, "SAVE_TARGETS");
if (XGetSelectionOwner (xdisplay, clipboard_manager) == None)
{
GDK_DISPLAY_NOTE (display, CLIPBOARD,
g_printerr ("%s: XGetSelectionOwner (CLIPBOARD_MANAGER) returned None, aborting.\n",
cb->selection));
g_task_return_new_error (cb->store_task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("Cannot store clipboard. No clipboard manager is active."));
g_clear_object (&cb->store_task);
return;
}
content = gdk_clipboard_get_content (clipboard);
if (content == NULL)
{
GDK_DISPLAY_NOTE (display, CLIPBOARD, g_printerr ("%s: storing empty clipboard: SUCCESS!\n", cb->selection));
g_task_return_boolean (cb->store_task, TRUE);
g_clear_object (&cb->store_task);
return;
}
formats = gdk_content_provider_ref_storable_formats (content);
formats = gdk_content_formats_union_serialize_mime_types (formats);
atoms = gdk_x11_clipboard_formats_to_atoms (display, FALSE, formats, &n_atoms);
print_atoms (cb, "requesting store from clipboard manager", atoms, n_atoms);
gdk_content_formats_unref (formats);
gdk_x11_display_error_trap_push (display);
if (n_atoms > 0)
{
property_name = gdk_x11_get_xatom_by_name_for_display (display, "GDK_CLIPBOARD_SAVE_TARGETS");
XChangeProperty (xdisplay, GDK_X11_DISPLAY (display)->leader_window,
property_name, XA_ATOM,
32, PropModeReplace, (guchar *)atoms, n_atoms);
}
else
property_name = None;
XConvertSelection (xdisplay,
clipboard_manager, save_targets, property_name,
GDK_X11_DISPLAY (display)->leader_window, cb->timestamp);
error = gdk_x11_display_error_trap_pop (display);
if (error != Success)
{
GDK_DISPLAY_NOTE (display, CLIPBOARD,
g_printerr ("%s: X error during ConvertSelection() while storing selection: %d\n", cb->selection, error));
}
g_free (atoms);
}
static gboolean
gdk_x11_clipboard_store_finish (GdkClipboard *clipboard,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, clipboard), FALSE);
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_x11_clipboard_store_async, FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
gdk_x11_clipboard_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)
{
GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (g_task_get_source_object (task));
GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD(cb)), 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
{
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))
{
g_assert (special_targets[i].mime_type != NULL);
GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), 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_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), 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);
}
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);
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);
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_DISPLAY_NOTE (gdk_clipboard_get_display (clipboard), 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,
targets->data,
cb->timestamp,
io_priority,
cancellable,
gdk_x11_clipboard_read_got_stream,
task);
}
static GInputStream *
gdk_x11_clipboard_read_finish (GdkClipboard *clipboard,
GAsyncResult *result,
const char **out_mime_type,
GError **error)
{
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);
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_clipboard_class_init (GdkX11ClipboardClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GdkClipboardClass *clipboard_class = GDK_CLIPBOARD_CLASS (class);
object_class->finalize = gdk_x11_clipboard_finalize;
clipboard_class->claim = gdk_x11_clipboard_claim;
clipboard_class->store_async = gdk_x11_clipboard_store_async;
clipboard_class->store_finish = gdk_x11_clipboard_store_finish;
clipboard_class->read_async = gdk_x11_clipboard_read_async;
clipboard_class->read_finish = gdk_x11_clipboard_read_finish;
}
static void
gdk_x11_clipboard_init (GdkX11Clipboard *cb)
{
cb->timestamp = CurrentTime;
}
GdkClipboard *
gdk_x11_clipboard_new (GdkDisplay *display,
const char *selection)
{
GdkX11Clipboard *cb;
cb = g_object_new (GDK_TYPE_X11_CLIPBOARD,
"display", display,
NULL);
cb->selection = g_strdup (selection);
cb->xselection = gdk_x11_get_xatom_by_name_for_display (display, selection);
gdk_x11_display_request_selection_notification (display, selection);
g_signal_connect (display, "xevent", G_CALLBACK (gdk_x11_clipboard_xevent), cb);
gdk_x11_clipboard_claim_remote (cb, CurrentTime);
return GDK_CLIPBOARD (cb);
}