/* 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 . */ #include "config.h" #include "gdkclipboardprivate.h" #include "gdkclipboard-x11.h" #include #include "gdkdisplay-x11.h" #include "gdkprivate-x11.h" #include "gdkselectioninputstream-x11.h" #include "gdkselectionoutputstream-x11.h" #include "gdktextlistconverter-x11.h" #include "gdk/gdkprivate.h" #include #include #ifdef HAVE_XFIXES #include #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) { #ifdef G_ENABLE_DEBUG GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb)); if (GDK_DISPLAY_DEBUG_CHECK (display, CLIPBOARD)) { gsize i; GString *str; str = g_string_new (""); g_string_append_printf (str, "%s: %s [ ", cb->selection, prefix); for (i = 0; i < n_atoms; i++) g_string_append_printf (str, "%s%s", i > 0 ? ", " : "", gdk_x11_get_xatom_name_for_display (display , atoms[i])); g_string_append (str, " ]"); gdk_debug_message ("%s", str->str); g_string_free (str, TRUE); } #endif } 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_DEBUG (CLIPBOARD, "-------: failed to close stream: %s", 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_DEBUG (gdk_clipboard_get_display (GDK_CLIPBOARD (clipboard)), CLIPBOARD, "%s: failed to write stream: %s", 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 (!name) { continue; } 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_DEBUG (display, CLIPBOARD, "%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)); #ifdef G_ENABLE_DEBUG if (GDK_DISPLAY_DEBUG_CHECK (display, CLIPBOARD)) { char *s = gdk_content_formats_to_string (formats); gdk_debug_message ("%s: got formats: %s", cb->selection, s); g_free (s); } #endif /* 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_DEBUG(display, CLIPBOARD, "%s: can't request TARGETS: %s", cb->selection, error->message); g_object_unref (cb); g_error_free (error); return; } else if (g_strcmp0 (type, "ATOM") != 0 || format != 32) { GDK_DISPLAY_DEBUG (display, CLIPBOARD, "%s: Wrong reply type to TARGETS: type %s != ATOM or format %d != 32", cb->selection, type ? type : "NULL", 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_DEBUG (display, CLIPBOARD, "%s: ignoring SelectionClear with too old timestamp (%lu vs %lu)", cb->selection, xevent->xselectionclear.time, cb->timestamp); return FALSE; } GDK_DISPLAY_DEBUG (display, CLIPBOARD, "%s: got SelectionClear", 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_DEBUG (display, CLIPBOARD, "%s: got SelectionNotify for nonexisting task?!", cb->selection); return FALSE; } GDK_DISPLAY_DEBUG (display, CLIPBOARD, "%s: got SelectionNotify for store task", 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_DEBUG (display, CLIPBOARD, "%s: got SelectionRequest for %s @ %s even though we don't own the selection, huh?", cb->selection, target, property); return TRUE; } if (xevent->xselectionrequest.requestor == None) { GDK_DISPLAY_DEBUG (display, CLIPBOARD, "%s: got SelectionRequest for %s @ %s with NULL window, ignoring", cb->selection, target, property); return TRUE; } GDK_DISPLAY_DEBUG (display, CLIPBOARD, "%s: got SelectionRequest for %s @ %s", 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_DEBUG (display, CLIPBOARD, "%s: Ignoring XFixesSelectionNotify with too old timestamp (%lu vs %lu)", cb->selection, sn->selection_timestamp, cb->timestamp); return FALSE; } if (sn->owner == GDK_X11_DISPLAY (display)->leader_window) { GDK_DISPLAY_DEBUG (display, CLIPBOARD, "%s: Ignoring XFixesSelectionNotify for ourselves", cb->selection); return FALSE; } GDK_DISPLAY_DEBUG (display, CLIPBOARD, "%s: Received XFixesSelectionNotify, claiming selection", 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_DEBUG (display, CLIPBOARD, "%s: failed XSetSelectionOwner()", cb->selection); return FALSE; } } else { XSetSelectionOwner (xdisplay, cb->xselection, None, time); } cb->timestamp = time; GDK_DISPLAY_DEBUG (display, CLIPBOARD, "%s: claimed via XSetSelectionOwner()", 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_DEBUG (display, CLIPBOARD, "%s: can only store on CLIPBOARD", 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_DEBUG (display, CLIPBOARD, "%s: XGetSelectionOwner (CLIPBOARD_MANAGER) returned None, aborting.", 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_DEBUG (display, CLIPBOARD, "%s: storing empty clipboard: SUCCESS!", 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_DEBUG (display, CLIPBOARD, "%s: X error during ConvertSelection() while storing selection: %d", 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_DEBUG (gdk_clipboard_get_display (GDK_CLIPBOARD(cb)), CLIPBOARD, "%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_DEBUG (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), CLIPBOARD, "%s: reading with converter from %s to %s", 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_DEBUG (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), CLIPBOARD, "%s: reading clipboard as %s now", 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_DEBUG (gdk_clipboard_get_display (clipboard), CLIPBOARD, "%s: new read for %s (%u other options)", 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); }