[mutter/wip/carlosg/clipboard-manager: 1/11] x11: Add X11 selection input/output streams
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [mutter/wip/carlosg/clipboard-manager: 1/11] x11: Add X11 selection input/output streams
- Date: Thu, 25 Apr 2019 20:25:57 +0000 (UTC)
commit d2bc5a55f8ac34a66d8f320d646c24f31ed1a0e9
Author: Carlos Garnacho <carlosg gnome org>
Date: Sun Nov 18 10:49:36 2018 +0100
x11: Add X11 selection input/output streams
These are rip off of GTK+ ones, with some adaptions to integrate them in
mutter event dispatching code and make them easier to use in future
commits.
https://gitlab.gnome.org/GNOME/mutter/merge_requests/320
src/meson.build | 4 +
src/x11/events.c | 26 +-
src/x11/meta-x11-display-private.h | 5 +
src/x11/meta-x11-selection-input-stream-private.h | 54 ++
src/x11/meta-x11-selection-input-stream.c | 557 +++++++++++++++++++
src/x11/meta-x11-selection-output-stream-private.h | 47 ++
src/x11/meta-x11-selection-output-stream.c | 606 +++++++++++++++++++++
7 files changed, 1298 insertions(+), 1 deletion(-)
---
diff --git a/src/meson.build b/src/meson.build
index 8ed6cb0cf..b19b534be 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -379,6 +379,10 @@ mutter_sources = [
'x11/meta-x11-display.c',
'x11/meta-x11-display-private.h',
'x11/meta-x11-errors.c',
+ 'x11/meta-x11-selection-input-stream.c',
+ 'x11/meta-x11-selection-input-stream-private.h',
+ 'x11/meta-x11-selection-output-stream.c',
+ 'x11/meta-x11-selection-output-stream-private.h',
'x11/mutter-Xatomtype.h',
'x11/session.c',
'x11/session.h',
diff --git a/src/x11/events.c b/src/x11/events.c
index e363fdbb6..1eec8db0b 100644
--- a/src/x11/events.c
+++ b/src/x11/events.c
@@ -38,8 +38,10 @@
#include "core/workspace-private.h"
#include "meta/meta-backend.h"
#include "meta/meta-x11-errors.h"
-#include "x11/meta-x11-display-private.h"
#include "x11/meta-startup-notification-x11.h"
+#include "x11/meta-x11-display-private.h"
+#include "x11/meta-x11-selection-input-stream-private.h"
+#include "x11/meta-x11-selection-output-stream-private.h"
#include "x11/window-x11.h"
#include "x11/xprops.h"
@@ -1717,6 +1719,22 @@ window_has_xwindow (MetaWindow *window,
return FALSE;
}
+static gboolean
+process_selection_event (MetaX11Display *x11_display,
+ XEvent *event)
+{
+ gboolean handled = FALSE;
+ GList *l;
+
+ for (l = x11_display->selection.input_streams; l && !handled; l = l->next)
+ handled |= meta_x11_selection_input_stream_xevent (l->data, event);
+
+ for (l = x11_display->selection.output_streams; l && !handled; l = l->next)
+ handled |= meta_x11_selection_output_stream_xevent (l->data, event);
+
+ return handled;
+}
+
/**
* meta_display_handle_xevent:
* @display: The MetaDisplay that events are coming from
@@ -1760,6 +1778,12 @@ meta_x11_display_handle_xevent (MetaX11Display *x11_display,
}
#endif
+ if (process_selection_event (x11_display, event))
+ {
+ bypass_gtk = bypass_compositor = TRUE;
+ goto out;
+ }
+
display->current_time = event_get_time (x11_display, event);
if (META_IS_BACKEND_X11 (backend))
diff --git a/src/x11/meta-x11-display-private.h b/src/x11/meta-x11-display-private.h
index b10411888..4e6518092 100644
--- a/src/x11/meta-x11-display-private.h
+++ b/src/x11/meta-x11-display-private.h
@@ -123,6 +123,11 @@ struct _MetaX11Display
MetaUI *ui;
+ struct {
+ GList *input_streams;
+ GList *output_streams;
+ } selection;
+
guint keys_grabbed : 1;
int composite_event_base;
diff --git a/src/x11/meta-x11-selection-input-stream-private.h
b/src/x11/meta-x11-selection-input-stream-private.h
new file mode 100644
index 000000000..3fcf9ffd1
--- /dev/null
+++ b/src/x11/meta-x11-selection-input-stream-private.h
@@ -0,0 +1,54 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Benjamin Otte <otte gnome org>
+ * Christian Kellner <gicmo gnome org>
+ */
+
+#ifndef META_X11_SELECTION_INPUT_STREAM_H
+#define META_X11_SELECTION_INPUT_STREAM_H
+
+#include <gio/gio.h>
+
+#include "x11/meta-x11-display-private.h"
+
+#define META_TYPE_X11_SELECTION_INPUT_STREAM (meta_x11_selection_input_stream_get_type ())
+G_DECLARE_FINAL_TYPE (MetaX11SelectionInputStream,
+ meta_x11_selection_input_stream,
+ META, X11_SELECTION_INPUT_STREAM,
+ GInputStream)
+
+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);
+GInputStream * meta_x11_selection_input_stream_new_finish (GAsyncResult *result,
+ const char **type,
+ int *format,
+ GError **error);
+
+gboolean meta_x11_selection_input_stream_xevent (MetaX11SelectionInputStream *stream,
+ const XEvent *xevent);
+
+G_END_DECLS
+
+#endif /* META_X11_SELECTION_INPUT_STREAM_H */
diff --git a/src/x11/meta-x11-selection-input-stream.c b/src/x11/meta-x11-selection-input-stream.c
new file mode 100644
index 000000000..44309ba98
--- /dev/null
+++ b/src/x11/meta-x11-selection-input-stream.c
@@ -0,0 +1,557 @@
+/* -*- 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Benjamin Otte <otte gnome org>
+ * Christian Kellner <gicmo gnome org>
+ */
+
+#include "config.h"
+
+#include "meta-x11-selection-input-stream-private.h"
+
+#include <gdk/gdkx.h>
+
+#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_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->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);
+}
diff --git a/src/x11/meta-x11-selection-output-stream-private.h
b/src/x11/meta-x11-selection-output-stream-private.h
new file mode 100644
index 000000000..83390da48
--- /dev/null
+++ b/src/x11/meta-x11-selection-output-stream-private.h
@@ -0,0 +1,47 @@
+/* GIO - GLib Output, 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Benjamin Otte <otte gnome org>
+ * Christian Kellner <gicmo gnome org>
+ */
+
+#ifndef META_X11_SELECTION_OUTPUT_STREAM_H
+#define META_X11_SELECTION_OUTPUT_STREAM_H
+
+#include <gio/gio.h>
+
+#include "x11/meta-x11-display-private.h"
+
+#define META_TYPE_X11_SELECTION_OUTPUT_STREAM (meta_x11_selection_output_stream_get_type ())
+G_DECLARE_FINAL_TYPE (MetaX11SelectionOutputStream,
+ meta_x11_selection_output_stream,
+ META, X11_SELECTION_OUTPUT_STREAM,
+ GOutputStream)
+
+GOutputStream * meta_x11_selection_output_stream_new (MetaX11Display *x11_display,
+ Window window,
+ const char *selection,
+ const char *target,
+ const char *property,
+ const char *type,
+ int format,
+ gulong timestamp);
+
+gboolean meta_x11_selection_output_stream_xevent (MetaX11SelectionOutputStream *stream,
+ const XEvent *xevent);
+
+#endif /* META_X11_SELECTION_OUTPUT_STREAM_H */
diff --git a/src/x11/meta-x11-selection-output-stream.c b/src/x11/meta-x11-selection-output-stream.c
new file mode 100644
index 000000000..723903835
--- /dev/null
+++ b/src/x11/meta-x11-selection-output-stream.c
@@ -0,0 +1,606 @@
+/* -*- 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Benjamin Otte <otte gnome org>
+ * Christian Kellner <gicmo gnome org>
+ */
+
+#include "config.h"
+
+#include "meta-x11-selection-output-stream-private.h"
+
+#include "meta/meta-x11-errors.h"
+#include "x11/meta-x11-display-private.h"
+
+typedef struct _MetaX11SelectionOutputStreamPrivate MetaX11SelectionOutputStreamPrivate;
+
+struct _MetaX11SelectionOutputStream
+{
+ GOutputStream parent_instance;
+};
+
+struct _MetaX11SelectionOutputStreamPrivate
+{
+ MetaX11Display *x11_display;
+ Window xwindow;
+ char *selection;
+ Atom xselection;
+ char *target;
+ Atom xtarget;
+ char *property;
+ Atom xproperty;
+ const char *type;
+ Atom xtype;
+ int format;
+ gulong timestamp;
+
+ GMutex mutex;
+ GCond cond;
+ GByteArray *data;
+ guint flush_requested : 1;
+
+ GTask *pending_task;
+
+ guint incr : 1;
+ guint delete_pending : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (MetaX11SelectionOutputStream,
+ meta_x11_selection_output_stream,
+ G_TYPE_OUTPUT_STREAM);
+
+static void
+meta_x11_selection_output_stream_notify_selection (MetaX11SelectionOutputStream *stream)
+{
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_stream_get_instance_private (stream);
+ XSelectionEvent event;
+ Display *xdisplay;
+
+ event = (XSelectionEvent) {
+ .type = SelectionNotify,
+ .time = priv->timestamp,
+ .requestor = priv->xwindow,
+ .selection = priv->xselection,
+ .target = priv->xtarget,
+ .property = priv->xproperty,
+ };
+
+ meta_x11_error_trap_push (priv->x11_display);
+
+ xdisplay = priv->x11_display->xdisplay;
+
+ XSendEvent (xdisplay,
+ priv->xwindow, False, NoEventMask,
+ (XEvent *) &event);
+ XSync (xdisplay, False);
+
+ meta_x11_error_trap_pop (priv->x11_display);
+}
+
+static gboolean
+meta_x11_selection_output_stream_can_flush (MetaX11SelectionOutputStream *stream)
+{
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_stream_get_instance_private (stream);
+
+ if (priv->delete_pending)
+ return FALSE;
+
+ return TRUE;
+}
+
+static size_t
+get_max_request_size (MetaX11Display *display)
+{
+ size_t size;
+
+ size = XExtendedMaxRequestSize (display->xdisplay);
+ if (size <= 0)
+ size = XMaxRequestSize (display->xdisplay);
+
+ return size - 100;
+}
+
+static gboolean
+meta_x11_selection_output_stream_needs_flush_unlocked (MetaX11SelectionOutputStream *stream)
+{
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_stream_get_instance_private (stream);
+
+ if (priv->data->len == 0)
+ return FALSE;
+
+ if (g_output_stream_is_closing (G_OUTPUT_STREAM (stream)))
+ return TRUE;
+
+ if (priv->flush_requested)
+ return TRUE;
+
+ return priv->data->len >= get_max_request_size (priv->x11_display);
+}
+
+static gboolean
+meta_x11_selection_output_stream_needs_flush (MetaX11SelectionOutputStream *stream)
+{
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_stream_get_instance_private (stream);
+
+ gboolean result;
+
+ g_mutex_lock (&priv->mutex);
+
+ result = meta_x11_selection_output_stream_needs_flush_unlocked (stream);
+
+ g_mutex_unlock (&priv->mutex);
+
+ return result;
+}
+
+static size_t
+get_element_size (int format)
+{
+ switch (format)
+ {
+ case 8:
+ return 1;
+
+ case 16:
+ return sizeof (short);
+
+ case 32:
+ return sizeof (long);
+
+ default:
+ g_warning ("Unknown format %u", format);
+ return 1;
+ }
+}
+
+static void
+meta_x11_selection_output_stream_perform_flush (MetaX11SelectionOutputStream *stream)
+{
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_stream_get_instance_private (stream);
+ Display *xdisplay;
+ size_t element_size, n_elements;
+
+ g_assert (!priv->delete_pending);
+
+ xdisplay = priv->x11_display->xdisplay;
+
+ /* We operate on a foreign window, better guard against catastrophe */
+ meta_x11_error_trap_push (priv->x11_display);
+
+ g_mutex_lock (&priv->mutex);
+
+ element_size = get_element_size (priv->format);
+ n_elements = priv->data->len / element_size;
+
+ if (!g_output_stream_is_closing (G_OUTPUT_STREAM (stream)))
+ {
+ XWindowAttributes attrs;
+
+ priv->incr = TRUE;
+ XGetWindowAttributes (xdisplay,
+ priv->xwindow,
+ &attrs);
+ if (!(attrs.your_event_mask & PropertyChangeMask))
+ {
+ XSelectInput (xdisplay, priv->xwindow, attrs.your_event_mask | PropertyChangeMask);
+ }
+
+ XChangeProperty (xdisplay,
+ priv->xwindow,
+ priv->xproperty,
+ XInternAtom (priv->x11_display->xdisplay, "INCR", True),
+ 32,
+ PropModeReplace,
+ (guchar *) &(long) { n_elements },
+ 1);
+ }
+ else
+ {
+ XChangeProperty (xdisplay,
+ priv->xwindow,
+ priv->xproperty,
+ priv->xtype,
+ priv->format,
+ PropModeReplace,
+ priv->data->data,
+ n_elements);
+ g_byte_array_remove_range (priv->data, 0, n_elements * element_size);
+ if (priv->data->len < element_size)
+ priv->flush_requested = FALSE;
+ }
+
+ meta_x11_selection_output_stream_notify_selection (stream);
+
+ priv->delete_pending = TRUE;
+ g_cond_broadcast (&priv->cond);
+ g_mutex_unlock (&priv->mutex);
+
+ /* XXX: handle failure here and report EPIPE for future operations on the stream? */
+ if (meta_x11_error_trap_pop_with_return (priv->x11_display))
+ g_warning ("Failed to flush selection output stream");
+
+ if (priv->pending_task)
+ {
+ size_t result;
+
+ result = GPOINTER_TO_SIZE (g_task_get_task_data (priv->pending_task));
+ g_task_return_int (priv->pending_task, result);
+ g_object_unref (priv->pending_task);
+ priv->pending_task = NULL;
+ }
+}
+
+static gboolean
+meta_x11_selection_output_stream_invoke_flush (gpointer data)
+{
+ MetaX11SelectionOutputStream *stream =
+ META_X11_SELECTION_OUTPUT_STREAM (data);
+
+ if (meta_x11_selection_output_stream_needs_flush (stream) &&
+ meta_x11_selection_output_stream_can_flush (stream))
+ meta_x11_selection_output_stream_perform_flush (stream);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gssize
+meta_x11_selection_output_stream_write (GOutputStream *output_stream,
+ const void *buffer,
+ size_t count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ MetaX11SelectionOutputStream *stream =
+ META_X11_SELECTION_OUTPUT_STREAM (output_stream);
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_stream_get_instance_private (stream);
+
+ g_mutex_lock (&priv->mutex);
+ g_byte_array_append (priv->data, buffer, count);
+ g_mutex_unlock (&priv->mutex);
+
+ g_main_context_invoke (NULL, meta_x11_selection_output_stream_invoke_flush, stream);
+
+ g_mutex_lock (&priv->mutex);
+ if (meta_x11_selection_output_stream_needs_flush_unlocked (stream))
+ g_cond_wait (&priv->cond, &priv->mutex);
+ g_mutex_unlock (&priv->mutex);
+
+ return count;
+}
+
+static void
+meta_x11_selection_output_stream_write_async (GOutputStream *output_stream,
+ const void *buffer,
+ size_t count,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MetaX11SelectionOutputStream *stream =
+ META_X11_SELECTION_OUTPUT_STREAM (output_stream);
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_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_output_stream_write_async);
+ g_task_set_priority (task, io_priority);
+
+ g_mutex_lock (&priv->mutex);
+ g_byte_array_append (priv->data, buffer, count);
+ g_mutex_unlock (&priv->mutex);
+
+ if (!meta_x11_selection_output_stream_needs_flush (stream))
+ {
+ g_task_return_int (task, count);
+ g_object_unref (task);
+ return;
+ }
+ else if (!meta_x11_selection_output_stream_can_flush (stream))
+ {
+ g_assert (priv->pending_task == NULL);
+ priv->pending_task = task;
+ g_task_set_task_data (task, GSIZE_TO_POINTER (count), NULL);
+ return;
+ }
+ else
+ {
+ meta_x11_selection_output_stream_perform_flush (stream);
+ g_task_return_int (task, count);
+ g_object_unref (task);
+ return;
+ }
+}
+
+static gssize
+meta_x11_selection_output_stream_write_finish (GOutputStream *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_output_stream_write_async, -1);
+
+ return g_task_propagate_int (G_TASK (result), error);
+}
+
+static gboolean
+meta_x11_selection_output_request_flush (MetaX11SelectionOutputStream *stream)
+{
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_stream_get_instance_private (stream);
+ gboolean needs_flush;
+
+ g_mutex_lock (&priv->mutex);
+
+ if (priv->data->len >= get_element_size (priv->format))
+ priv->flush_requested = TRUE;
+
+ needs_flush = meta_x11_selection_output_stream_needs_flush_unlocked (stream);
+ g_mutex_unlock (&priv->mutex);
+
+ return needs_flush;
+}
+
+static gboolean
+meta_x11_selection_output_stream_flush (GOutputStream *output_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ MetaX11SelectionOutputStream *stream =
+ META_X11_SELECTION_OUTPUT_STREAM (output_stream);
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_stream_get_instance_private (stream);
+
+ if (!meta_x11_selection_output_request_flush (stream))
+ return TRUE;
+
+ g_main_context_invoke (NULL, meta_x11_selection_output_stream_invoke_flush,
+ stream);
+
+ g_mutex_lock (&priv->mutex);
+ if (meta_x11_selection_output_stream_needs_flush_unlocked (stream))
+ g_cond_wait (&priv->cond, &priv->mutex);
+ g_mutex_unlock (&priv->mutex);
+
+ return TRUE;
+}
+
+static void
+meta_x11_selection_output_stream_flush_async (GOutputStream *output_stream,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MetaX11SelectionOutputStream *stream =
+ META_X11_SELECTION_OUTPUT_STREAM (output_stream);
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_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_output_stream_flush_async);
+ g_task_set_priority (task, io_priority);
+
+ if (!meta_x11_selection_output_stream_can_flush (stream))
+ {
+ if (meta_x11_selection_output_request_flush (stream))
+ {
+ g_assert (priv->pending_task == NULL);
+ priv->pending_task = task;
+ return;
+ }
+ else
+ {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+ }
+
+ meta_x11_selection_output_stream_perform_flush (stream);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+}
+
+static gboolean
+meta_x11_selection_output_stream_flush_finish (GOutputStream *stream,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
+ g_return_val_if_fail (g_async_result_is_tagged (result, meta_x11_selection_output_stream_flush_async),
FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gboolean
+meta_x11_selection_output_stream_invoke_close (gpointer stream)
+{
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_stream_get_instance_private (stream);
+
+ priv->x11_display->selection.output_streams =
+ g_list_remove (priv->x11_display->selection.output_streams, stream);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+meta_x11_selection_output_stream_close (GOutputStream *stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_main_context_invoke (NULL, meta_x11_selection_output_stream_invoke_close, stream);
+
+ return TRUE;
+}
+
+static void
+meta_x11_selection_output_stream_close_async (GOutputStream *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_output_stream_close_async);
+ g_task_set_priority (task, io_priority);
+
+ meta_x11_selection_output_stream_invoke_close (stream);
+ g_task_return_boolean (task, TRUE);
+
+ g_object_unref (task);
+}
+
+static gboolean
+meta_x11_selection_output_stream_close_finish (GOutputStream *stream,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
+ g_return_val_if_fail (g_async_result_is_tagged (result, meta_x11_selection_output_stream_close_async),
FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+meta_x11_selection_output_stream_finalize (GObject *object)
+{
+ MetaX11SelectionOutputStream *stream =
+ META_X11_SELECTION_OUTPUT_STREAM (object);
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_stream_get_instance_private (stream);
+
+ g_byte_array_unref (priv->data);
+ g_cond_clear (&priv->cond);
+ g_mutex_clear (&priv->mutex);
+
+ g_free (priv->selection);
+ g_free (priv->target);
+ g_free (priv->property);
+
+ G_OBJECT_CLASS (meta_x11_selection_output_stream_parent_class)->finalize (object);
+}
+
+static void
+meta_x11_selection_output_stream_class_init (MetaX11SelectionOutputStreamClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GOutputStreamClass *output_stream_class = G_OUTPUT_STREAM_CLASS (klass);
+
+ object_class->finalize = meta_x11_selection_output_stream_finalize;
+
+ output_stream_class->write_fn = meta_x11_selection_output_stream_write;
+ output_stream_class->flush = meta_x11_selection_output_stream_flush;
+ output_stream_class->close_fn = meta_x11_selection_output_stream_close;
+
+ output_stream_class->write_async = meta_x11_selection_output_stream_write_async;
+ output_stream_class->write_finish = meta_x11_selection_output_stream_write_finish;
+ output_stream_class->flush_async = meta_x11_selection_output_stream_flush_async;
+ output_stream_class->flush_finish = meta_x11_selection_output_stream_flush_finish;
+ output_stream_class->close_async = meta_x11_selection_output_stream_close_async;
+ output_stream_class->close_finish = meta_x11_selection_output_stream_close_finish;
+}
+
+static void
+meta_x11_selection_output_stream_init (MetaX11SelectionOutputStream *stream)
+{
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_stream_get_instance_private (stream);
+
+ g_mutex_init (&priv->mutex);
+ g_cond_init (&priv->cond);
+ priv->data = g_byte_array_new ();
+}
+
+gboolean
+meta_x11_selection_output_stream_xevent (MetaX11SelectionOutputStream *stream,
+ const XEvent *xevent)
+{
+ MetaX11SelectionOutputStreamPrivate *priv =
+ meta_x11_selection_output_stream_get_instance_private (stream);
+ Display *xdisplay = priv->x11_display->xdisplay;
+
+ if (xevent->xany.display != xdisplay ||
+ xevent->xany.window != priv->xwindow)
+ return FALSE;
+
+ switch (xevent->type)
+ {
+ case PropertyNotify:
+ if (!priv->incr ||
+ xevent->xproperty.atom != priv->xproperty ||
+ xevent->xproperty.state != PropertyDelete)
+ return FALSE;
+
+ priv->delete_pending = FALSE;
+ if (meta_x11_selection_output_stream_needs_flush (stream) &&
+ meta_x11_selection_output_stream_can_flush (stream))
+ meta_x11_selection_output_stream_perform_flush (stream);
+ return FALSE;
+
+ default:
+ return FALSE;
+ }
+}
+
+GOutputStream *
+meta_x11_selection_output_stream_new (MetaX11Display *x11_display,
+ Window requestor,
+ const char *selection,
+ const char *target,
+ const char *property,
+ const char *type,
+ int format,
+ gulong timestamp)
+{
+ MetaX11SelectionOutputStream *stream;
+ MetaX11SelectionOutputStreamPrivate *priv;
+
+ stream = g_object_new (META_TYPE_X11_SELECTION_OUTPUT_STREAM, NULL);
+ priv = meta_x11_selection_output_stream_get_instance_private (stream);
+
+ x11_display->selection.output_streams =
+ g_list_prepend (x11_display->selection.output_streams, stream);
+
+ priv->x11_display = x11_display;
+ priv->xwindow = requestor;
+ 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 (property);
+ priv->xproperty = XInternAtom (x11_display->xdisplay, priv->property, False);
+ priv->type = g_strdup (type);
+ priv->xtype = XInternAtom (x11_display->xdisplay, priv->type, False);
+ priv->format = format;
+ priv->timestamp = timestamp;
+
+ return G_OUTPUT_STREAM (stream);
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]