/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * 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 * Christian Kellner */ #include "config.h" #include "meta-x11-selection-input-stream-private.h" #include #include "meta/meta-x11-errors.h" #include "x11/meta-x11-display-private.h" typedef struct MetaX11SelectionInputStreamPrivate MetaX11SelectionInputStreamPrivate; struct _MetaX11SelectionInputStream { GInputStream parent_instance; }; struct MetaX11SelectionInputStreamPrivate { MetaX11Display *x11_display; Window window; GAsyncQueue *chunks; char *selection; Atom xselection; char *target; Atom xtarget; char *property; Atom xproperty; const char *type; Atom xtype; int format; GTask *pending_task; uint8_t *pending_data; size_t pending_size; guint complete : 1; guint incr : 1; }; G_DEFINE_TYPE_WITH_PRIVATE (MetaX11SelectionInputStream, meta_x11_selection_input_stream, G_TYPE_INPUT_STREAM) static gboolean meta_x11_selection_input_stream_has_data (MetaX11SelectionInputStream *stream) { MetaX11SelectionInputStreamPrivate *priv = meta_x11_selection_input_stream_get_instance_private (stream); return g_async_queue_length (priv->chunks) > 0 || priv->complete; } /* NB: blocks when no data is in buffer */ static size_t meta_x11_selection_input_stream_fill_buffer (MetaX11SelectionInputStream *stream, uint8_t *buffer, size_t count) { MetaX11SelectionInputStreamPrivate *priv = meta_x11_selection_input_stream_get_instance_private (stream); GBytes *bytes; size_t result = 0; g_async_queue_lock (priv->chunks); for (bytes = g_async_queue_pop_unlocked (priv->chunks); bytes != NULL && count > 0; bytes = g_async_queue_try_pop_unlocked (priv->chunks)) { size_t size = g_bytes_get_size (bytes); if (size == 0) { /* EOF marker, put it back */ g_async_queue_push_front_unlocked (priv->chunks, bytes); bytes = NULL; break; } else if (size > count) { GBytes *subbytes; if (buffer) memcpy (buffer, g_bytes_get_data (bytes, NULL), count); subbytes = g_bytes_new_from_bytes (bytes, count, size - count); g_async_queue_push_front_unlocked (priv->chunks, subbytes); size = count; } else { if (buffer) memcpy (buffer, g_bytes_get_data (bytes, NULL), size); } g_bytes_unref (bytes); result += size; if (buffer) buffer += size; count -= size; } if (bytes) g_async_queue_push_front_unlocked (priv->chunks, bytes); g_async_queue_unlock (priv->chunks); return result; } static void meta_x11_selection_input_stream_flush (MetaX11SelectionInputStream *stream) { MetaX11SelectionInputStreamPrivate *priv = meta_x11_selection_input_stream_get_instance_private (stream); gssize written; if (!meta_x11_selection_input_stream_has_data (stream)) return; if (priv->pending_task == NULL) return; written = meta_x11_selection_input_stream_fill_buffer (stream, priv->pending_data, priv->pending_size); g_task_return_int (priv->pending_task, written); g_clear_object (&priv->pending_task); priv->pending_data = NULL; priv->pending_size = 0; } static void meta_x11_selection_input_stream_complete (MetaX11SelectionInputStream *stream) { MetaX11SelectionInputStreamPrivate *priv = meta_x11_selection_input_stream_get_instance_private (stream); if (priv->complete) return; priv->complete = TRUE; g_async_queue_push (priv->chunks, g_bytes_new (NULL, 0)); meta_x11_selection_input_stream_flush (stream); priv->x11_display->selection.input_streams = g_list_remove (priv->x11_display->selection.input_streams, stream); g_object_unref (stream); } static gssize meta_x11_selection_input_stream_read (GInputStream *input_stream, void *buffer, size_t count, GCancellable *cancellable, GError **error) { MetaX11SelectionInputStream *stream = META_X11_SELECTION_INPUT_STREAM (input_stream); return meta_x11_selection_input_stream_fill_buffer (stream, buffer, count); } static gboolean meta_x11_selection_input_stream_close (GInputStream *stream, GCancellable *cancellable, GError **error) { return TRUE; } static void meta_x11_selection_input_stream_read_async (GInputStream *input_stream, void *buffer, size_t count, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { MetaX11SelectionInputStream *stream = META_X11_SELECTION_INPUT_STREAM (input_stream); MetaX11SelectionInputStreamPrivate *priv = meta_x11_selection_input_stream_get_instance_private (stream); GTask *task; task = g_task_new (stream, cancellable, callback, user_data); g_task_set_source_tag (task, meta_x11_selection_input_stream_read_async); g_task_set_priority (task, io_priority); if (meta_x11_selection_input_stream_has_data (stream)) { gssize size; size = meta_x11_selection_input_stream_fill_buffer (stream, buffer, count); g_task_return_int (task, size); g_object_unref (task); } else { priv->pending_data = buffer; priv->pending_size = count; priv->pending_task = task; } } static gssize meta_x11_selection_input_stream_read_finish (GInputStream *stream, GAsyncResult *result, GError **error) { g_return_val_if_fail (g_task_is_valid (result, stream), -1); g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == meta_x11_selection_input_stream_read_async, -1); return g_task_propagate_int (G_TASK (result), error); } static void meta_x11_selection_input_stream_close_async (GInputStream *stream, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; task = g_task_new (stream, cancellable, callback, user_data); g_task_set_source_tag (task, meta_x11_selection_input_stream_close_async); g_task_return_boolean (task, TRUE); g_object_unref (task); } static gboolean meta_x11_selection_input_stream_close_finish (GInputStream *stream, GAsyncResult *result, GError **error) { return TRUE; } static void meta_x11_selection_input_stream_dispose (GObject *object) { MetaX11SelectionInputStream *stream = META_X11_SELECTION_INPUT_STREAM (object); MetaX11SelectionInputStreamPrivate *priv = meta_x11_selection_input_stream_get_instance_private (stream); priv->x11_display->selection.input_streams = g_list_remove (priv->x11_display->selection.input_streams, stream); G_OBJECT_CLASS (meta_x11_selection_input_stream_parent_class)->dispose (object); } static void meta_x11_selection_input_stream_finalize (GObject *object) { MetaX11SelectionInputStream *stream = META_X11_SELECTION_INPUT_STREAM (object); MetaX11SelectionInputStreamPrivate *priv = meta_x11_selection_input_stream_get_instance_private (stream); g_async_queue_unref (priv->chunks); g_free (priv->selection); g_free (priv->target); g_free (priv->property); G_OBJECT_CLASS (meta_x11_selection_input_stream_parent_class)->finalize (object); } static void meta_x11_selection_input_stream_class_init (MetaX11SelectionInputStreamClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GInputStreamClass *istream_class = G_INPUT_STREAM_CLASS (klass); object_class->dispose = meta_x11_selection_input_stream_dispose; object_class->finalize = meta_x11_selection_input_stream_finalize; istream_class->read_fn = meta_x11_selection_input_stream_read; istream_class->close_fn = meta_x11_selection_input_stream_close; istream_class->read_async = meta_x11_selection_input_stream_read_async; istream_class->read_finish = meta_x11_selection_input_stream_read_finish; istream_class->close_async = meta_x11_selection_input_stream_close_async; istream_class->close_finish = meta_x11_selection_input_stream_close_finish; } static void meta_x11_selection_input_stream_init (MetaX11SelectionInputStream *stream) { MetaX11SelectionInputStreamPrivate *priv = meta_x11_selection_input_stream_get_instance_private (stream); priv->chunks = g_async_queue_new_full ((GDestroyNotify) g_bytes_unref); } static void XFree_without_return_value (gpointer data) { XFree (data); } static GBytes * get_selection_property (Display *xdisplay, Window owner, Atom property, Atom *ret_type, gint *ret_format) { gulong nitems; gulong nbytes; Atom prop_type; gint prop_format; uint8_t *data = NULL; if (XGetWindowProperty (xdisplay, owner, property, 0, 0x1FFFFFFF, False, AnyPropertyType, &prop_type, &prop_format, &nitems, &nbytes, &data) != Success) goto err; if (prop_type != None) { size_t length; switch (prop_format) { case 8: length = nitems; break; case 16: length = sizeof (short) * nitems; break; case 32: length = sizeof (long) * nitems; break; default: g_warning ("Unknown XGetWindowProperty() format %u", prop_format); goto err; } *ret_type = prop_type; *ret_format = prop_format; return g_bytes_new_with_free_func (data, length, XFree_without_return_value, data); } err: if (data) XFree (data); *ret_type = None; *ret_format = 0; return NULL; } gboolean meta_x11_selection_input_stream_xevent (MetaX11SelectionInputStream *stream, const XEvent *xevent) { MetaX11SelectionInputStreamPrivate *priv = meta_x11_selection_input_stream_get_instance_private (stream); Display *xdisplay; Window xwindow; GBytes *bytes; Atom type; gint format; xdisplay = priv->x11_display->xdisplay; xwindow = priv->window; if (xevent->xany.display != xdisplay || xevent->xany.window != xwindow) return FALSE; switch (xevent->type) { case PropertyNotify: if (!priv->incr || xevent->xproperty.atom != priv->xproperty || xevent->xproperty.state != PropertyNewValue) return FALSE; bytes = get_selection_property (xdisplay, xwindow, xevent->xproperty.atom, &type, &format); if (bytes == NULL) { g_debug ("INCR request came out empty"); meta_x11_selection_input_stream_complete (stream); } else if (g_bytes_get_size (bytes) == 0 || type == None) { g_bytes_unref (bytes); meta_x11_selection_input_stream_complete (stream); } else { g_async_queue_push (priv->chunks, bytes); meta_x11_selection_input_stream_flush (stream); } XDeleteProperty (xdisplay, xwindow, xevent->xproperty.atom); return FALSE; case SelectionNotify: { GTask *task; /* selection is not for us */ if (priv->xselection != xevent->xselection.selection || priv->xtarget != xevent->xselection.target) return FALSE; /* We already received a selectionNotify before */ if (priv->pending_task == NULL || g_task_get_source_tag (priv->pending_task) != meta_x11_selection_input_stream_new_async) { g_debug ("Misbehaving client sent a reentrant SelectionNotify"); return FALSE; } task = g_steal_pointer (&priv->pending_task); 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); meta_x11_selection_input_stream_complete (stream); } else { bytes = get_selection_property (xdisplay, xwindow, xevent->xselection.property, &priv->xtype, &priv->format); priv->type = gdk_x11_get_xatom_name (priv->xtype); g_task_return_pointer (task, g_object_ref (stream), g_object_unref); if (bytes == NULL) { meta_x11_selection_input_stream_complete (stream); } else { if (priv->xtype == XInternAtom (priv->x11_display->xdisplay, "INCR", False)) { /* The remainder of the selection will come through PropertyNotify events on xwindow */ priv->incr = TRUE; meta_x11_selection_input_stream_flush (stream); } else { g_async_queue_push (priv->chunks, bytes); meta_x11_selection_input_stream_complete (stream); } } XDeleteProperty (xdisplay, xwindow, xevent->xselection.property); } g_object_unref (task); } return TRUE; default: return FALSE; } } void meta_x11_selection_input_stream_new_async (MetaX11Display *x11_display, Window window, const char *selection, const char *target, guint32 timestamp, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { MetaX11SelectionInputStream *stream; MetaX11SelectionInputStreamPrivate *priv; stream = g_object_new (META_TYPE_X11_SELECTION_INPUT_STREAM, NULL); priv = meta_x11_selection_input_stream_get_instance_private (stream); priv->x11_display = x11_display; x11_display->selection.input_streams = g_list_prepend (x11_display->selection.input_streams, stream); priv->selection = g_strdup (selection); priv->xselection = XInternAtom (x11_display->xdisplay, priv->selection, False); priv->target = g_strdup (target); priv->xtarget = XInternAtom (x11_display->xdisplay, priv->target, False); priv->property = g_strdup_printf ("META_SELECTION_%p", stream); priv->xproperty = XInternAtom (x11_display->xdisplay, priv->property, False); priv->window = window; XConvertSelection (x11_display->xdisplay, priv->xselection, priv->xtarget, priv->xproperty, window, timestamp); priv->pending_task = g_task_new (NULL, cancellable, callback, user_data); g_task_set_source_tag (priv->pending_task, meta_x11_selection_input_stream_new_async); g_task_set_priority (priv->pending_task, io_priority); } GInputStream * meta_x11_selection_input_stream_new_finish (GAsyncResult *result, const char **type, int *format, GError **error) { MetaX11SelectionInputStream *stream; MetaX11SelectionInputStreamPrivate *priv; 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) == meta_x11_selection_input_stream_new_async, NULL); stream = g_task_propagate_pointer (task, error); if (!stream) return NULL; priv = meta_x11_selection_input_stream_get_instance_private (stream); if (type) *type = priv->type; if (format) *format = priv->format; return G_INPUT_STREAM (stream); }