[mutter/wip/carlosg/clipboard-manager: 1/11] x11: Add X11 selection input/output streams



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]