[gtk+/wip/otte/clipboard: 6/7] x11: Implement claiming the X Selection with the clipboard



commit 91fa3dd4e7ee1a9d5d33192f7c03420f567f8c21
Author: Benjamin Otte <otte redhat com>
Date:   Sat Nov 25 21:59:39 2017 +0100

    x11: Implement claiming the X Selection with the clipboard
    
    ... and of course support writing to other apps.

 gdk/x11/gdkclipboard-x11.c             |  402 ++++++++++++++++++++---
 gdk/x11/gdkdisplay-x11.c               |    2 +-
 gdk/x11/gdkdisplay-x11.h               |    2 +-
 gdk/x11/gdkselectioninputstream-x11.c  |    4 +-
 gdk/x11/gdkselectionoutputstream-x11.c |  585 ++++++++++++++++++++++++++++++++
 gdk/x11/gdkselectionoutputstream-x11.h |   67 ++++
 gdk/x11/gdktextlistconverter-x11.c     |  202 ++++++++++--
 gdk/x11/gdktextlistconverter-x11.h     |    3 +
 gdk/x11/meson.build                    |    1 +
 9 files changed, 1196 insertions(+), 72 deletions(-)
---
diff --git a/gdk/x11/gdkclipboard-x11.c b/gdk/x11/gdkclipboard-x11.c
index 3b7d608..fcd63a9 100644
--- a/gdk/x11/gdkclipboard-x11.c
+++ b/gdk/x11/gdkclipboard-x11.c
@@ -23,7 +23,9 @@
 #include "gdkintl.h"
 #include "gdkprivate-x11.h"
 #include "gdkselectioninputstream-x11.h"
+#include "gdkselectionoutputstream-x11.h"
 #include "gdktextlistconverter-x11.h"
+#include "gdk/gdk-private.h"
 
 #include <string.h>
 #include <X11/Xatom.h>
@@ -44,7 +46,7 @@ struct _GdkX11Clipboard
 
   char       *selection;
   Atom        xselection;
-  guint32     timestamp;
+  gulong      timestamp;
 };
 
 struct _GdkX11ClipboardClass
@@ -54,6 +56,142 @@ struct _GdkX11ClipboardClass
 
 G_DEFINE_TYPE (GdkX11Clipboard, gdk_x11_clipboard, GDK_TYPE_CLIPBOARD)
 
+static void
+print_atoms (GdkX11Clipboard *cb,
+             const char      *prefix,
+             const Atom      *atoms,
+             gsize            n_atoms)
+{
+  GDK_NOTE(CLIPBOARD,
+           GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
+           gsize i;
+            
+           g_printerr ("%s: %s [ ", cb->selection, prefix);
+           for (i = 0; i < n_atoms; i++)
+             {
+               g_printerr ("%s%s", i > 0 ? ", " : "", gdk_x11_get_xatom_name_for_display (display , 
atoms[i]));
+             }
+           g_printerr (" ]\n");
+          ); 
+}
+
+static void
+gdk_x11_clipboard_default_output_done (GObject      *clipboard,
+                                       GAsyncResult *result,
+                                       gpointer      user_data)
+{
+  GError *error = NULL;
+
+  if (!gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, &error))
+    {
+      GDK_NOTE(CLIPBOARD, g_printerr ("%s: failed to write stream: %s\n", GDK_X11_CLIPBOARD 
(clipboard)->selection, error->message));
+      g_error_free (error);
+    }
+}
+
+static void
+gdk_x11_clipboard_default_output_handler (GdkX11Clipboard *cb,
+                                          const char      *target,
+                                          GOutputStream   *stream)
+{
+  gdk_clipboard_write_async (GDK_CLIPBOARD (cb),
+                             g_intern_string (target),
+                             stream,
+                             G_PRIORITY_DEFAULT,
+                             NULL,
+                             gdk_x11_clipboard_default_output_done,
+                             NULL);
+  g_object_unref (stream);
+}
+
+static void
+handle_targets_done (GObject      *stream,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+  GError *error = NULL;
+  gsize bytes_written;
+
+  if (!g_output_stream_write_all_finish (G_OUTPUT_STREAM (stream), result, &bytes_written, &error))
+    {
+      GDK_NOTE(CLIPBOARD, g_printerr ("---: failed to send targets after %zu bytes: %s\n",
+                                      bytes_written, error->message));
+      g_error_free (error);
+    }
+
+  g_free (user_data);
+}
+
+static Atom *
+gdk_x11_clipboard_formats_to_atoms (GdkDisplay        *display,
+                                    GdkContentFormats *formats,
+                                    gsize             *n_atoms);
+
+static void
+handle_targets (GdkX11Clipboard *cb,
+                const char      *target,
+                const char      *encoding,
+                int              format,
+                GOutputStream   *stream)
+{
+  GdkClipboard *clipboard = GDK_CLIPBOARD (cb);
+  Atom *atoms;
+  gsize n_atoms;
+
+  atoms = gdk_x11_clipboard_formats_to_atoms (gdk_clipboard_get_display (clipboard),
+                                              gdk_clipboard_get_formats (clipboard),
+                                              &n_atoms);
+  print_atoms (cb, "sending targets", atoms, n_atoms);
+  g_output_stream_write_all_async (stream,
+                                     atoms,
+                                     n_atoms * sizeof (Atom),
+                                     G_PRIORITY_DEFAULT,
+                                     NULL,
+                                     handle_targets_done,
+                                     atoms);
+  g_object_unref (stream);
+}
+
+static void
+handle_timestamp_done (GObject      *stream,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  GError *error = NULL;
+  gsize bytes_written;
+
+  if (!g_output_stream_write_all_finish (G_OUTPUT_STREAM (stream), result, &bytes_written, &error))
+    {
+      GDK_NOTE(CLIPBOARD, g_printerr ("---: failed to send timestamp after %zu bytes: %s\n",
+                                      bytes_written, error->message));
+      g_error_free (error);
+    }
+
+  g_slice_free (gulong, user_data);
+}
+
+static void
+handle_timestamp (GdkX11Clipboard *cb,
+                  const char      *target,
+                  const char      *encoding,
+                  int              format,
+                  GOutputStream   *stream)
+{
+  gulong *timestamp;
+
+  timestamp = g_slice_new (gulong);
+  *timestamp = cb->timestamp;
+
+  g_output_stream_write_all_async (stream,
+                                   timestamp,
+                                   sizeof (gulong),
+                                   G_PRIORITY_DEFAULT,
+                                   NULL,
+                                   handle_timestamp_done,
+                                   timestamp);
+  g_object_unref (stream);
+}
+
 static GInputStream * 
 text_list_convert (GdkX11Clipboard *cb,
                    GInputStream    *stream,
@@ -74,6 +212,37 @@ text_list_convert (GdkX11Clipboard *cb,
   return converter_stream;
 }
 
+static void
+handle_text_list (GdkX11Clipboard *cb,
+                  const char      *target,
+                  const char      *encoding,
+                  int              format,
+                  GOutputStream   *stream)
+{
+  GOutputStream *converter_stream;
+  GConverter *converter;
+
+  converter = gdk_x11_text_list_converter_to_utf8_new (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)),
+                                                       encoding,
+                                                       format);
+  converter_stream = g_converter_output_stream_new (stream, converter);
+
+  g_object_unref (converter);
+  g_object_unref (stream);
+
+  gdk_x11_clipboard_default_output_handler (cb, "text/plain;charset=utf-8", converter_stream);
+}
+
+static void
+handle_utf8 (GdkX11Clipboard *cb,
+             const char      *target,
+             const char      *encoding,
+             int              format,
+             GOutputStream   *stream)
+{
+  gdk_x11_clipboard_default_output_handler (cb, "text/plain;charset=utf-8", stream);
+}
+
 static GInputStream * 
 no_convert (GdkX11Clipboard *cb,
             GInputStream    *stream,
@@ -87,32 +256,18 @@ static const struct {
   const char *x_target;
   const char *mime_type;
   GInputStream * (* convert) (GdkX11Clipboard *, GInputStream *, const char *, int);
+  const char *type;
+  gint format;
+  void (* handler) (GdkX11Clipboard *, const char *, const char *, int, GOutputStream *);
 } special_targets[] = {
-  { "UTF8_STRING", "text/plain;charset=utf-8",   no_convert },
-  { "COMPOUND_TEXT", "text/plain;charset=utf-8", text_list_convert },
-  { "TEXT", "text/plain;charset=utf-8",          text_list_convert },
-  { "STRING", "text/plain;charset=utf-8",        text_list_convert }
+  { "UTF8_STRING",   "text/plain;charset=utf-8", no_convert,        "UTF8_STRING",  0,  handle_utf8 },
+  { "COMPOUND_TEXT", "text/plain;charset=utf-8", text_list_convert, "COMPUND_TEXT", 0,  handle_text_list },
+  { "TEXT",          "text/plain;charset=utf-8", text_list_convert, "STRING",       0,  handle_text_list },
+  { "STRING",        "text/plain;charset=utf-8", text_list_convert, "STRING",       0,  handle_text_list },
+  { "TARGETS",       NULL,                       NULL,              "ATOM",         32, handle_targets },
+  { "TIMESTAMP",     NULL,                       NULL,              "INTEGER",      32, handle_timestamp }
 };
 
-static void
-print_atoms (GdkX11Clipboard *cb,
-             const char      *prefix,
-             const Atom      *atoms,
-             gsize            n_atoms)
-{
-  GDK_NOTE(CLIPBOARD,
-           GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
-           gsize i;
-            
-           g_printerr ("%s: %s [ ", cb->selection, prefix);
-           for (i = 0; i < n_atoms; i++)
-             {
-               g_printerr ("%s%s", i > 0 ? ", " : "", gdk_x11_get_xatom_name_for_display (display , 
atoms[i]));
-             }
-           g_printerr (" ]\n");
-          ); 
-}
-
 static GSList *
 gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats)
 {
@@ -127,8 +282,11 @@ gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats)
     {
       for (j = 0; j < G_N_ELEMENTS (special_targets); j++)
         {
+          if (special_targets[j].mime_type == NULL)
+            continue;
+
           if (g_str_equal (mime_types[i], special_targets[j].mime_type))
-            targets = g_slist_prepend (targets, (gpointer) g_intern_string (special_targets[i].x_target));
+            targets = g_slist_prepend (targets, (gpointer) g_intern_string (special_targets[j].x_target));
         }
       targets = g_slist_prepend (targets, (gpointer) mime_types[i]);
     }
@@ -136,6 +294,35 @@ gdk_x11_clipboard_formats_to_targets (GdkContentFormats *formats)
   return g_slist_reverse (targets);
 }
 
+static Atom *
+gdk_x11_clipboard_formats_to_atoms (GdkDisplay        *display,
+                                    GdkContentFormats *formats,
+                                    gsize             *n_atoms)
+{
+  GSList *l, *targets;
+  Atom *atoms;
+  gsize i;
+
+  targets = gdk_x11_clipboard_formats_to_targets (formats);
+
+  for (i = 0; i < G_N_ELEMENTS (special_targets); i++)
+    {
+      if (special_targets[i].mime_type != NULL)
+        continue;
+
+      if (special_targets[i].handler)
+        targets = g_slist_prepend (targets, (gpointer) g_intern_string (special_targets[i].x_target));
+    }
+
+  *n_atoms = g_slist_length (targets);
+  atoms = g_new (Atom, *n_atoms);
+  i = 0;
+  for (l = targets; l; l = l->next)
+    atoms[i++] = gdk_x11_get_xatom_by_name_for_display (display, l->data);
+
+  return atoms;
+}
+
 static GdkContentFormats *
 gdk_x11_clipboard_formats_from_atoms (GdkDisplay *display,
                                       const Atom *atoms,
@@ -160,7 +347,8 @@ gdk_x11_clipboard_formats_from_atoms (GdkDisplay *display,
         {
           if (g_str_equal (name, special_targets[j].x_target))
             {
-              gdk_content_formats_builder_add_mime_type (builder, special_targets[j].mime_type);
+              if (special_targets[j].mime_type)
+                gdk_content_formats_builder_add_mime_type (builder, special_targets[j].mime_type);
               break;
             }
         }
@@ -269,6 +457,19 @@ gdk_x11_clipboard_request_targets (GdkX11Clipboard *cb)
                                             g_object_ref (cb));
 }
 
+static void
+gdk_x11_clipboard_claim_remote (GdkX11Clipboard *cb,
+                                guint32          timestamp)
+{
+  GdkContentFormats *empty;
+
+  empty = gdk_content_formats_new (NULL, 0);
+  gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb), empty);
+  gdk_content_formats_unref (empty);
+  cb->timestamp = timestamp;
+  gdk_x11_clipboard_request_targets (cb);
+}
+
 static GdkFilterReturn
 gdk_x11_clipboard_filter_event (GdkXEvent *xev,
                                 GdkEvent  *gdkevent,
@@ -287,23 +488,99 @@ gdk_x11_clipboard_filter_event (GdkXEvent *xev,
 
   switch (xevent->type)
   {
+    case SelectionClear:
+      if (xevent->xselectionclear.selection != cb->xselection)
+        return GDK_FILTER_CONTINUE;
+
+      if (xevent->xselectionclear.time < cb->timestamp)
+        {
+          GDK_NOTE(CLIPBOARD, g_printerr ("%s: ignoring SelectionClear with too old timestamp (%lu vs 
%lu)\n",
+                                          cb->selection, xevent->xselectionclear.time, cb->timestamp));
+          return GDK_FILTER_CONTINUE;
+        }
+
+      GDK_NOTE(CLIPBOARD, g_printerr ("%s: got SelectionClear\n", cb->selection));
+      gdk_x11_clipboard_claim_remote (cb, xevent->xselectionclear.time);
+      return GDK_FILTER_REMOVE;
+
+    case SelectionRequest:
+      {
+        GOutputStream *stream;
+        const char *target, *property;
+        gsize i;
+
+        if (xevent->xselectionrequest.selection != cb->xselection)
+          return GDK_FILTER_CONTINUE;
+
+        target = gdk_x11_get_xatom_name_for_display (display, xevent->xselectionrequest.target);
+        if (xevent->xselectionrequest.property == None)
+          property = target;
+        else
+          property = gdk_x11_get_xatom_name_for_display (display, xevent->xselectionrequest.property);
+
+        if (!gdk_clipboard_is_local (GDK_CLIPBOARD (cb)))
+          {
+            GDK_NOTE(CLIPBOARD, g_printerr ("%s: got SelectionRequest for %s @ %s even though we don't own 
the selection, huh?\n",
+                                            cb->selection, target, property));
+            return GDK_FILTER_REMOVE;
+          }
+        if (xevent->xselectionrequest.requestor == None)
+          {
+            GDK_NOTE(CLIPBOARD, g_printerr ("%s: got SelectionRequest for %s @ %s with NULL window, 
ignoring\n",
+                                            cb->selection, target, property));
+            return GDK_FILTER_REMOVE;
+          }
+        
+        GDK_NOTE(CLIPBOARD, g_printerr ("%s: got SelectionRequest for %s @ %s\n",
+                                        cb->selection, target, property));
+        for (i = 0; i < G_N_ELEMENTS (special_targets); i++)
+          {
+            if (g_str_equal (target, special_targets[i].x_target))
+              {
+                if (special_targets[i].handler)
+                  {
+                    stream = gdk_x11_selection_output_stream_new (display,
+                                                                  xevent->xselectionrequest.requestor,
+                                                                  cb->selection,
+                                                                  target,
+                                                                  property,
+                                                                  special_targets[i].type,
+                                                                  special_targets[i].format,
+                                                                  xevent->xselectionrequest.time);
+                    special_targets[i].handler (cb, target, special_targets[i].type, 
special_targets[i].format,stream);
+                  }
+                break;
+              }
+          }
+        if (i == G_N_ELEMENTS (special_targets))
+          {
+            stream = gdk_x11_selection_output_stream_new (display,
+                                                          xevent->xselectionrequest.requestor,
+                                                          cb->selection,
+                                                          target,
+                                                          property,
+                                                          target,
+                                                          8,
+                                                          xevent->xselectionrequest.time);
+            gdk_x11_clipboard_default_output_handler (cb, target, stream);
+          }
+
+        return GDK_FILTER_REMOVE;
+      }
+
     default:
 #ifdef HAVE_XFIXES
       if (xevent->type - GDK_X11_DISPLAY (display)->xfixes_event_base == XFixesSelectionNotify)
-       {
-         XFixesSelectionNotifyEvent *sn = (XFixesSelectionNotifyEvent *) xevent;
+        {
+          XFixesSelectionNotifyEvent *sn = (XFixesSelectionNotifyEvent *) xevent;
 
-          if (sn->selection == cb->xselection)
-            {
-              GdkContentFormats *empty;
-              
-              GDK_NOTE(CLIPBOARD, g_printerr ("%s: got FixesSelectionNotify\n", cb->selection));
-              empty = gdk_content_formats_new (NULL, 0);
-              gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb), empty);
-              gdk_content_formats_unref (empty);
-              cb->timestamp = sn->selection_timestamp;
-              gdk_x11_clipboard_request_targets (cb);
-            }
+          if (sn->selection != cb->xselection)
+            return GDK_FILTER_CONTINUE;
+
+          if (sn->owner == GDK_X11_DISPLAY (display)->leader_window)
+            return GDK_FILTER_CONTINUE;
+
+          gdk_x11_clipboard_claim_remote (cb, sn->selection_timestamp);
         }
 #endif
       return GDK_FILTER_CONTINUE;
@@ -321,6 +598,50 @@ gdk_x11_clipboard_finalize (GObject *object)
   G_OBJECT_CLASS (gdk_x11_clipboard_parent_class)->finalize (object);
 }
 
+static gboolean
+gdk_x11_clipboard_claim (GdkClipboard       *clipboard,
+                         GdkContentFormats  *formats,
+                         gboolean            local,
+                         GdkContentProvider *content)
+{
+  if (local)
+    {
+      GdkX11Clipboard *cb = GDK_X11_CLIPBOARD (clipboard);
+      GdkDisplay *display = gdk_clipboard_get_display (GDK_CLIPBOARD (cb));
+      Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
+      Window xwindow = GDK_X11_DISPLAY (display)->leader_window;
+      guint32 time;
+
+      time = gdk_display_get_last_seen_time (display);
+
+#if 0
+      /* calling it twice ensures X sends out a XFixesSelectionNotify.
+       * Otherwise clients might not realize that the contents changed. */
+      XSetSelectionOwner (xdisplay, cb->xselection, None, time - 1);
+#endif
+
+      if (content)
+        {
+          XSetSelectionOwner (xdisplay, cb->xselection, xwindow, time);
+
+          if (XGetSelectionOwner (xdisplay, cb->xselection) != xwindow)
+            {
+              GDK_NOTE(CLIPBOARD, g_printerr ("%s: failed XSetSelectionOwner()\n", cb->selection));
+              return FALSE;
+            }
+        }
+      else
+        {
+          XSetSelectionOwner (xdisplay, cb->xselection, None, time);
+        }
+
+      cb->timestamp = time;
+      GDK_NOTE(CLIPBOARD, g_printerr ("%s: claimed via XSetSelectionOwner()\n", cb->selection));
+    }
+
+  return GDK_CLIPBOARD_CLASS (gdk_x11_clipboard_parent_class)->claim (clipboard, formats, local, content);
+}
+
 static void
 gdk_x11_clipboard_read_got_stream (GObject      *source,
                                    GAsyncResult *res,
@@ -371,6 +692,8 @@ gdk_x11_clipboard_read_got_stream (GObject      *source,
         {
           if (g_str_equal (mime_type, special_targets[i].x_target))
             {
+              g_assert (special_targets[i].mime_type != NULL);
+
               GDK_NOTE(CLIPBOARD, g_printerr ("%s: reading with converter from %s to %s\n",
                                               cb->selection, mime_type, special_targets[i].mime_type));
               mime_type = g_intern_string (special_targets[i].mime_type);
@@ -468,6 +791,7 @@ gdk_x11_clipboard_class_init (GdkX11ClipboardClass *class)
 
   object_class->finalize = gdk_x11_clipboard_finalize;
 
+  clipboard_class->claim = gdk_x11_clipboard_claim;
   clipboard_class->read_async = gdk_x11_clipboard_read_async;
   clipboard_class->read_finish = gdk_x11_clipboard_read_finish;
 }
diff --git a/gdk/x11/gdkdisplay-x11.c b/gdk/x11/gdkdisplay-x11.c
index b1e5738..c54ed13 100644
--- a/gdk/x11/gdkdisplay-x11.c
+++ b/gdk/x11/gdkdisplay-x11.c
@@ -2059,7 +2059,7 @@ gdk_x11_display_finalize (GObject *object)
   _gdk_x11_display_free_translate_queue (GDK_DISPLAY (display_x11));
 
   /* Get rid of pending streams */
-  g_slist_free_full (display_x11->input_streams, g_object_unref);
+  g_slist_free_full (display_x11->streams, g_object_unref);
 
   /* Atom Hashtable */
   g_hash_table_destroy (display_x11->atom_from_virtual);
diff --git a/gdk/x11/gdkdisplay-x11.h b/gdk/x11/gdkdisplay-x11.h
index 9be2bde..bf7ad27 100644
--- a/gdk/x11/gdkdisplay-x11.h
+++ b/gdk/x11/gdkdisplay-x11.h
@@ -105,7 +105,7 @@ struct _GdkX11Display
   GQueue *translate_queue;
 
   /* streams reading selections */
-  GSList *input_streams;
+  GSList *streams;
 
   /* input GdkWindow list */
   GList *input_windows;
diff --git a/gdk/x11/gdkselectioninputstream-x11.c b/gdk/x11/gdkselectioninputstream-x11.c
index e62db50..6083f75 100644
--- a/gdk/x11/gdkselectioninputstream-x11.c
+++ b/gdk/x11/gdkselectioninputstream-x11.c
@@ -160,7 +160,7 @@ gdk_x11_selection_input_stream_complete (GdkX11SelectionInputStream *stream)
   g_async_queue_push (priv->chunks, g_bytes_new (NULL, 0));
   gdk_x11_selection_input_stream_flush (stream);
 
-  GDK_X11_DISPLAY (priv->display)->input_streams = g_slist_remove (GDK_X11_DISPLAY 
(priv->display)->input_streams, stream);
+  GDK_X11_DISPLAY (priv->display)->streams = g_slist_remove (GDK_X11_DISPLAY (priv->display)->streams, 
stream);
   gdk_window_remove_filter (NULL, gdk_x11_selection_input_stream_filter_event, stream);
 
   g_object_unref (stream);
@@ -521,7 +521,7 @@ gdk_x11_selection_input_stream_new_async (GdkDisplay          *display,
   priv = gdk_x11_selection_input_stream_get_instance_private (stream);
 
   priv->display = display;
-  GDK_X11_DISPLAY (display)->input_streams = g_slist_prepend (GDK_X11_DISPLAY (display)->input_streams, 
stream);
+  GDK_X11_DISPLAY (display)->streams = g_slist_prepend (GDK_X11_DISPLAY (display)->streams, stream);
   priv->selection = g_strdup (selection);
   priv->xselection = gdk_x11_get_xatom_by_name_for_display (display, priv->selection);
   priv->target = g_strdup (target);
diff --git a/gdk/x11/gdkselectionoutputstream-x11.c b/gdk/x11/gdkselectionoutputstream-x11.c
new file mode 100644
index 0000000..6426384
--- /dev/null
+++ b/gdk/x11/gdkselectionoutputstream-x11.c
@@ -0,0 +1,585 @@
+/* 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> 
+ */
+
+#include "config.h"
+
+#include "gdkselectionoutputstream-x11.h"
+
+#include "gdkdisplay-x11.h"
+#include "gdkintl.h"
+#include "gdkx11display.h"
+#include "gdkx11property.h"
+#include "gdkx11window.h"
+
+typedef struct GdkX11SelectionOutputStreamPrivate  GdkX11SelectionOutputStreamPrivate;
+
+struct GdkX11SelectionOutputStreamPrivate {
+  GdkDisplay *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 started : 1;
+  guint incr : 1;
+  guint delete_pending : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GdkX11SelectionOutputStream, gdk_x11_selection_output_stream, 
G_TYPE_OUTPUT_STREAM);
+
+static GdkFilterReturn
+gdk_x11_selection_output_stream_filter_event (GdkXEvent *xevent,
+                                              GdkEvent  *gdkevent,
+                                              gpointer   data);
+
+static gboolean
+gdk_x11_selection_output_stream_can_flush (GdkX11SelectionOutputStream *stream)
+{
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
+
+  if (priv->delete_pending)
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+gdk_x11_selection_output_stream_needs_flush_unlocked (GdkX11SelectionOutputStream *stream)
+{
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_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 >= gdk_x11_display_get_max_request_size (priv->display);
+}
+
+static gboolean
+gdk_x11_selection_output_stream_needs_flush (GdkX11SelectionOutputStream *stream)
+{
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
+
+  gboolean result;
+
+  g_mutex_lock (&priv->mutex);
+
+  result = gdk_x11_selection_output_stream_needs_flush_unlocked (stream);
+
+  g_mutex_unlock (&priv->mutex);
+
+  return result;
+}
+
+static gsize
+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
+gdk_x11_selection_output_stream_perform_flush (GdkX11SelectionOutputStream *stream)
+{
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
+  Display *xdisplay;
+  gsize element_size, n_elements;
+  int error;
+
+  g_assert (!priv->delete_pending);
+
+  xdisplay = gdk_x11_display_get_xdisplay (priv->display);
+
+  /* We operate on a foreign window, better guard against catastrophe */
+  gdk_x11_display_error_trap_push (priv->display);
+
+  g_mutex_lock (&priv->mutex);
+
+  element_size = get_element_size (priv->format);
+  n_elements = priv->data->len / element_size;
+
+  if (!priv->started && !g_output_stream_is_closing (G_OUTPUT_STREAM (stream)))
+    {
+      XWindowAttributes attrs;
+
+      priv->incr = TRUE;
+      GDK_NOTE(SELECTION, g_printerr ("%s:%s: initiating INCR transfer\n",
+                                      priv->selection, priv->target));
+
+      XGetWindowAttributes (xdisplay,
+                           priv->xwindow,
+                           &attrs);
+      if (!(attrs.your_event_mask & PropertyChangeMask))
+        {
+          XSelectInput (xdisplay, priv->xwindow, attrs.your_event_mask | PropertyChangeMask);
+        }
+
+      XChangeProperty (GDK_DISPLAY_XDISPLAY (priv->display),
+                       priv->xwindow,
+                       priv->xproperty, 
+                       gdk_x11_get_xatom_by_name_for_display (priv->display, "INCR"),
+                       32,
+                       PropModeReplace,
+                       (guchar *) &(long) { n_elements },
+                       1);
+    }
+  else
+    {
+      XChangeProperty (GDK_DISPLAY_XDISPLAY (priv->display),
+                       priv->xwindow,
+                       priv->xproperty, 
+                       priv->xtype,
+                       priv->format,
+                       PropModeReplace,
+                       priv->data->data,
+                       n_elements);
+      GDK_NOTE(SELECTION, g_printerr ("%s:%s: wrote %zu/%u bytes\n",
+                                      priv->selection, priv->target, n_elements * element_size, 
priv->data->len));
+      g_byte_array_remove_range (priv->data, 0, n_elements * element_size);
+      if (priv->data->len < element_size)
+        priv->flush_requested = FALSE;
+    }
+
+  if (!priv->started)
+    {
+      XSelectionEvent xevent;
+
+      xevent.type = SelectionNotify;
+      xevent.serial = 0;
+      xevent.send_event = True;
+      xevent.requestor = priv->xwindow;
+      xevent.selection = priv->xselection;
+      xevent.target = priv->xtarget;
+      xevent.property = priv->xproperty;
+      xevent.time = priv->timestamp;
+
+      if (XSendEvent (xdisplay, priv->xwindow, False, NoEventMask, (XEvent*) & xevent) == 0)
+        {
+          GDK_NOTE(SELECTION, g_printerr ("%s:%s: failed to XSendEvent()\n",
+                                          priv->selection, priv->target));
+          g_warning ("failed to XSendEvent()");
+        }
+      XSync (xdisplay, False);
+
+      GDK_NOTE(SELECTION, g_printerr ("%s:%s: sent SelectionNotify for %s on %s\n",
+                                      priv->selection, priv->target, priv->target, priv->property));
+      priv->started = TRUE;
+    }
+
+  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? */
+  error = gdk_x11_display_error_trap_pop (priv->display);
+  if (error != Success)
+    {
+      GDK_NOTE(SELECTION, g_printerr ("%s:%s: X error during write: %d\n",
+                                      priv->selection, priv->target, error));
+    }
+
+  if (priv->pending_task)
+    {
+      g_task_return_int (priv->pending_task, GPOINTER_TO_SIZE (g_task_get_task_data (priv->pending_task)));
+      g_object_unref (priv->pending_task);
+      priv->pending_task = NULL;
+    }
+}
+
+static gboolean
+gdk_x11_selection_output_stream_invoke_flush (gpointer data)
+{
+  GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (data);
+
+  if (gdk_x11_selection_output_stream_needs_flush (stream) &
+      gdk_x11_selection_output_stream_can_flush (stream))
+    gdk_x11_selection_output_stream_perform_flush (stream);
+
+  return G_SOURCE_REMOVE;
+}
+
+static gssize
+gdk_x11_selection_output_stream_write (GOutputStream  *output_stream,
+                                       const void     *buffer,
+                                       gsize           count,
+                                       GCancellable   *cancellable,
+                                       GError        **error)
+{
+  GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (output_stream);
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
+
+  g_mutex_lock (&priv->mutex);
+  g_byte_array_append (priv->data, buffer, count);
+  GDK_NOTE(SELECTION, g_printerr ("%s:%s: wrote %zu bytes, %u total now\n",
+                                  priv->selection, priv->target, count, priv->data->len));
+  g_mutex_unlock (&priv->mutex);
+
+  g_main_context_invoke (NULL, gdk_x11_selection_output_stream_invoke_flush, stream);
+
+  g_mutex_lock (&priv->mutex);
+  if (gdk_x11_selection_output_stream_needs_flush_unlocked (stream))
+    g_cond_wait (&priv->cond, &priv->mutex);
+  g_mutex_unlock (&priv->mutex);
+
+  return count;
+}
+
+static void
+gdk_x11_selection_output_stream_write_async (GOutputStream        *output_stream,
+                                             const void           *buffer,
+                                             gsize                 count,
+                                             int                   io_priority,
+                                             GCancellable         *cancellable,
+                                             GAsyncReadyCallback   callback,
+                                             gpointer              user_data)
+{
+  GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (output_stream);
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_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, gdk_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);
+  GDK_NOTE(SELECTION, g_printerr ("%s:%s: async wrote %zu bytes, %u total now\n",
+                                  priv->selection, priv->target, count, priv->data->len));
+  g_mutex_unlock (&priv->mutex);
+
+  if (!gdk_x11_selection_output_stream_needs_flush (stream))
+    {
+      g_task_return_int (task, count);
+      g_object_unref (task);
+      return;
+    }
+  else if (!gdk_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
+    {
+      gdk_x11_selection_output_stream_perform_flush (stream);
+      g_task_return_int (task, count);
+      g_object_unref (task);
+      return;
+    }
+}
+
+static gssize
+gdk_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)) == 
gdk_x11_selection_output_stream_write_async, -1);
+
+  return g_task_propagate_int (G_TASK (result), error);
+}
+
+static gboolean
+gdk_x11_selection_output_request_flush (GdkX11SelectionOutputStream *stream)
+{
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
+  gboolean needs_flush;
+
+  g_mutex_lock (&priv->mutex);
+
+  needs_flush = priv->data->len >= get_element_size (priv->format);
+  if (needs_flush)
+    priv->flush_requested = TRUE;
+
+  g_mutex_unlock (&priv->mutex);
+
+  GDK_NOTE(SELECTION, g_printerr ("%s:%s: requested flush%s\n",
+                                  priv->selection, priv->target, needs_flush ? "" : ", but not needed"));
+  return needs_flush;
+}
+
+static gboolean
+gdk_x11_selection_output_stream_flush (GOutputStream  *output_stream,
+                                       GCancellable   *cancellable,
+                                       GError        **error)
+{
+  GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (output_stream);
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
+
+  if (!gdk_x11_selection_output_request_flush (stream))
+    return TRUE;
+
+  g_main_context_invoke (NULL, gdk_x11_selection_output_stream_invoke_flush, stream);
+
+  g_mutex_lock (&priv->mutex);
+  if (gdk_x11_selection_output_stream_needs_flush_unlocked (stream))
+    g_cond_wait (&priv->cond, &priv->mutex);
+  g_mutex_unlock (&priv->mutex);
+
+  return TRUE;
+}
+
+static void
+gdk_x11_selection_output_stream_flush_async (GOutputStream       *output_stream,
+                                             int                  io_priority,
+                                             GCancellable        *cancellable,
+                                             GAsyncReadyCallback  callback,
+                                             gpointer             user_data)
+{
+  GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (output_stream);
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_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, gdk_x11_selection_output_stream_flush_async);
+  g_task_set_priority (task, io_priority);
+
+  if (!gdk_x11_selection_output_stream_can_flush (stream))
+    {
+      if (gdk_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;
+        }
+    }
+
+  gdk_x11_selection_output_stream_perform_flush (stream);
+  g_task_return_boolean (task, TRUE);
+  g_object_unref (task);
+  return;
+}
+
+static gboolean
+gdk_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, gdk_x11_selection_output_stream_flush_async), 
FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gboolean
+gdk_x11_selection_output_stream_invoke_close (gpointer stream)
+{
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
+
+  GDK_X11_DISPLAY (priv->display)->streams = g_slist_remove (GDK_X11_DISPLAY (priv->display)->streams, 
stream);
+  gdk_window_remove_filter (NULL, gdk_x11_selection_output_stream_filter_event, stream);
+
+  return G_SOURCE_REMOVE;
+}
+
+static gboolean
+gdk_x11_selection_output_stream_close (GOutputStream  *stream,
+                                       GCancellable   *cancellable,
+                                       GError        **error)
+{
+  g_main_context_invoke (NULL, gdk_x11_selection_output_stream_invoke_close, stream);
+
+  return TRUE;
+}
+
+static void
+gdk_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, gdk_x11_selection_output_stream_close_async);
+  g_task_set_priority (task, io_priority);
+
+  gdk_x11_selection_output_stream_invoke_close (stream);
+  g_task_return_boolean (task, TRUE);
+
+  g_object_unref (task);
+}
+
+static gboolean
+gdk_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, gdk_x11_selection_output_stream_close_async), 
FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+gdk_x11_selection_output_stream_finalize (GObject *object)
+{
+  GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (object);
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_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 (gdk_x11_selection_output_stream_parent_class)->finalize (object);
+}
+
+static void
+gdk_x11_selection_output_stream_class_init (GdkX11SelectionOutputStreamClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GOutputStreamClass *output_stream_class = G_OUTPUT_STREAM_CLASS (klass);
+
+  object_class->finalize = gdk_x11_selection_output_stream_finalize;
+  
+  output_stream_class->write_fn = gdk_x11_selection_output_stream_write;
+  output_stream_class->flush = gdk_x11_selection_output_stream_flush;
+  output_stream_class->close_fn = gdk_x11_selection_output_stream_close;
+
+  output_stream_class->write_async = gdk_x11_selection_output_stream_write_async;
+  output_stream_class->write_finish = gdk_x11_selection_output_stream_write_finish;
+  output_stream_class->flush_async = gdk_x11_selection_output_stream_flush_async;
+  output_stream_class->flush_finish = gdk_x11_selection_output_stream_flush_finish;
+  output_stream_class->close_async = gdk_x11_selection_output_stream_close_async;
+  output_stream_class->close_finish = gdk_x11_selection_output_stream_close_finish;
+}
+
+static void
+gdk_x11_selection_output_stream_init (GdkX11SelectionOutputStream *stream)
+{
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
+
+  g_mutex_init (&priv->mutex);
+  g_cond_init (&priv->cond);
+  priv->data = g_byte_array_new ();
+}
+
+static GdkFilterReturn
+gdk_x11_selection_output_stream_filter_event (GdkXEvent *xev,
+                                             GdkEvent  *gdkevent,
+                                             gpointer   data)
+{
+  GdkX11SelectionOutputStream *stream = GDK_X11_SELECTION_OUTPUT_STREAM (data);
+  GdkX11SelectionOutputStreamPrivate *priv = gdk_x11_selection_output_stream_get_instance_private (stream);
+  XEvent *xevent = xev;
+  Display *xdisplay;
+
+  xdisplay = gdk_x11_display_get_xdisplay (priv->display);
+
+  if (xevent->xany.display != xdisplay ||
+      xevent->xany.window != priv->xwindow)
+    return GDK_FILTER_CONTINUE;
+
+  switch (xevent->type)
+  {
+    case PropertyNotify:
+      if (!priv->incr ||
+          xevent->xproperty.atom != priv->xproperty ||
+          xevent->xproperty.state != PropertyDelete)
+        return GDK_FILTER_CONTINUE;
+
+      GDK_NOTE(SELECTION, g_printerr ("%s:%s: got PropertyNotify Delete during INCR\n",
+                                      priv->selection, priv->target));
+      return GDK_FILTER_CONTINUE;
+
+    default:
+      return GDK_FILTER_CONTINUE;
+    }
+}
+
+GOutputStream *
+gdk_x11_selection_output_stream_new (GdkDisplay *display,
+                                     Window      window,
+                                     const char *selection,
+                                     const char *target,
+                                     const char *property,
+                                     const char *type,
+                                     int         format,
+                                     gulong      timestamp)
+{
+  GdkX11SelectionOutputStream *stream;
+  GdkX11SelectionOutputStreamPrivate *priv;
+
+  stream = g_object_new (GDK_TYPE_X11_SELECTION_OUTPUT_STREAM, NULL);
+  priv = gdk_x11_selection_output_stream_get_instance_private (stream);
+
+  priv->display = display;
+  GDK_X11_DISPLAY (display)->streams = g_slist_prepend (GDK_X11_DISPLAY (display)->streams, stream);
+  priv->xwindow = window;
+  priv->selection = g_strdup (selection);
+  priv->xselection = gdk_x11_get_xatom_by_name_for_display (display, priv->selection);
+  priv->target = g_strdup (target);
+  priv->xtarget = gdk_x11_get_xatom_by_name_for_display (display, priv->target);
+  priv->property = g_strdup (property);
+  priv->xproperty = gdk_x11_get_xatom_by_name_for_display (display, priv->property);
+  priv->type = g_strdup (type);
+  priv->xtype = gdk_x11_get_xatom_by_name_for_display (display, priv->type);
+  priv->format = format;
+  priv->timestamp = timestamp;
+
+  gdk_window_add_filter (NULL, gdk_x11_selection_output_stream_filter_event, stream);
+
+  return G_OUTPUT_STREAM (stream);
+}
diff --git a/gdk/x11/gdkselectionoutputstream-x11.h b/gdk/x11/gdkselectionoutputstream-x11.h
new file mode 100644
index 0000000..874d35b
--- /dev/null
+++ b/gdk/x11/gdkselectionoutputstream-x11.h
@@ -0,0 +1,67 @@
+/* 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 __GDK_X11_SELECTION_OUTPUT_STREAM_H__
+#define __GDK_X11_SELECTION_OUTPUT_STREAM_H__
+
+#include <gio/gio.h>
+#include "gdktypes.h"
+
+#include <X11/Xlib.h>
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_X11_SELECTION_OUTPUT_STREAM         (gdk_x11_selection_output_stream_get_type ())
+#define GDK_X11_SELECTION_OUTPUT_STREAM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
GDK_TYPE_X11_SELECTION_OUTPUT_STREAM, GdkX11SelectionOutputStream))
+#define GDK_X11_SELECTION_OUTPUT_STREAM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), 
GDK_TYPE_X11_SELECTION_OUTPUT_STREAM, GdkX11SelectionOutputStreamClass))
+#define GDK_IS_X11_SELECTION_OUTPUT_STREAM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
GDK_TYPE_X11_SELECTION_OUTPUT_STREAM))
+#define GDK_IS_X11_SELECTION_OUTPUT_STREAM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), 
GDK_TYPE_X11_SELECTION_OUTPUT_STREAM))
+#define GDK_X11_SELECTION_OUTPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
GDK_TYPE_X11_SELECTION_OUTPUT_STREAM, GdkX11SelectionOutputStreamClass))
+
+typedef struct GdkX11SelectionOutputStream         GdkX11SelectionOutputStream;
+typedef struct GdkX11SelectionOutputStreamClass    GdkX11SelectionOutputStreamClass;
+
+struct GdkX11SelectionOutputStream
+{
+  GOutputStream parent_instance;
+};
+
+struct GdkX11SelectionOutputStreamClass
+{
+  GOutputStreamClass parent_class;
+};
+
+
+GType           gdk_x11_selection_output_stream_get_type        (void) G_GNUC_CONST;
+
+GOutputStream * gdk_x11_selection_output_stream_new             (GdkDisplay             *display,
+                                                                 Window                  window,
+                                                                 const char             *selection,
+                                                                 const char             *target,
+                                                                 const char             *property,
+                                                                 const char             *type,
+                                                                 int                     format,
+                                                                 gulong                  timestamp);
+
+
+G_END_DECLS
+
+#endif /* __GDK_X11_SELECTION_OUTPUT_STREAM_H__ */
diff --git a/gdk/x11/gdktextlistconverter-x11.c b/gdk/x11/gdktextlistconverter-x11.c
index 7fe56d1..f39dc6c 100644
--- a/gdk/x11/gdktextlistconverter-x11.c
+++ b/gdk/x11/gdktextlistconverter-x11.c
@@ -39,6 +39,8 @@ struct _GdkX11TextListConverter
   
   const char *encoding; /* interned */
   gint format;
+
+  guint encoder : 1;
 };
 
 struct _GdkX11TextListConverterClass
@@ -47,17 +49,39 @@ struct _GdkX11TextListConverterClass
 };
 
 static GConverterResult
-gdk_x11_text_list_converter_convert (GConverter       *converter,
-                                     const void       *inbuf,
-                                     gsize             inbuf_size,
-                                     void             *outbuf,
-                                     gsize             outbuf_size,
-                                     GConverterFlags   flags,
-                                     gsize            *bytes_read,
-                                     gsize            *bytes_written,
-                                     GError          **error)
+write_output (void        *outbuf,
+              gsize        outbuf_size,
+              gsize       *bytes_written,
+              const void  *data,
+              gssize       len,
+              GError     **error)
+{
+  if (len < 0)
+    len = strlen (data) + 1;
+
+  if (outbuf_size < len)
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
+                           _("Not enough space in destination"));
+      return G_CONVERTER_ERROR;
+    }
+
+  memcpy (outbuf, data, len);
+  *bytes_written = len;
+  return G_CONVERTER_FINISHED;
+}
+
+static GConverterResult
+gdk_x11_text_list_converter_decode (GdkX11TextListConverter *conv,
+                                    const void              *inbuf,
+                                    gsize                    inbuf_size,
+                                    void                    *outbuf,
+                                    gsize                    outbuf_size,
+                                    GConverterFlags          flags,
+                                    gsize                   *bytes_read,
+                                    gsize                   *bytes_written,
+                                    GError                 **error)
 {
-  GdkX11TextListConverter *conv = GDK_X11_TEXT_LIST_CONVERTER (converter);
   gint count;
   char **list;
 
@@ -83,32 +107,135 @@ gdk_x11_text_list_converter_convert (GConverter       *converter,
     }
   else if (count == 0)
     {
-      if (outbuf_size < 1)
-        {
-          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
-                               _("Not enough space in destination"));
-          return G_CONVERTER_ERROR;
-        }
-      ((gchar *) outbuf)[0] = 0;
       *bytes_read = inbuf_size;
-      *bytes_written = 1;
-      return G_CONVERTER_FINISHED;
+      return write_output (outbuf, outbuf_size, bytes_written, "", 1, error);
     }
   else
     {
-      gsize len = strlen (list[0]) + 1;
+      GConverterResult result;
+      
+      result = write_output (outbuf, outbuf_size, bytes_written, list[0], -1, error);
+      g_strfreev (list);
+      *bytes_read = inbuf_size;
+      return result;
+    }
+}
+
+static GConverterResult
+gdk_x11_text_list_converter_encode (GdkX11TextListConverter *conv,
+                                    const void              *inbuf,
+                                    gsize                    inbuf_size,
+                                    void                    *outbuf,
+                                    gsize                    outbuf_size,
+                                    GConverterFlags          flags,
+                                    gsize                   *bytes_read,
+                                    gsize                   *bytes_written,
+                                    GError                 **error)
+{
+  if (!(flags & G_CONVERTER_INPUT_AT_END))
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
+                           _("Need complete input to do conversion"));
+      return G_CONVERTER_ERROR;
+    }
+
+  if (g_str_equal (conv->encoding, "STRING") ||
+      g_str_equal (conv->encoding, "TEXT"))
+    {
+      GConverterResult result;
+      gchar *tmp, *latin1;
 
-      if (outbuf_size < len)
+      tmp = g_strndup (inbuf, inbuf_size);
+      latin1 = gdk_utf8_to_string_target (tmp);
+      g_free (tmp);
+      if (latin1)
         {
-          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
-                               _("Not enough space in destination"));
-          return G_CONVERTER_ERROR;
+          result = write_output (outbuf, outbuf_size, bytes_written, latin1, -1, error);
+          g_free (latin1);
         }
-      memcpy (outbuf, list[0], len);
-      g_strfreev (list);
-      *bytes_read = inbuf_size;
-      *bytes_written = len;
-      return G_CONVERTER_FINISHED;
+      else
+        { 
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+                               _("Invalid byte sequence in conversion input"));
+          result = G_CONVERTER_ERROR;
+        }
+      return result;
+    }
+  else if (g_str_equal (conv->encoding, "COMPOUND_TEXT"))
+    {
+      GConverterResult result;
+      guchar *text;
+      GdkAtom encoding;
+      gint format;
+      gint new_length;
+      char *tmp;
+
+      tmp = g_strndup (inbuf, inbuf_size);
+      if (gdk_x11_display_utf8_to_compound_text (conv->display, tmp,
+                                                 &encoding, &format, &text, &new_length))
+        {
+          if (encoding == gdk_atom_intern (conv->encoding, FALSE) &&
+              format == conv->format)
+            {
+              result = write_output (outbuf, outbuf_size, bytes_written, text, new_length, error);
+            }
+          else
+            {
+              g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                                   _("Invalid formats in compound text conversion."));
+              result = G_CONVERTER_ERROR;
+            }
+          gdk_x11_free_compound_text (text);
+        }
+      else
+        {
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+                               _("Invalid byte sequence in conversion input"));
+          result = G_CONVERTER_ERROR;
+        }
+      g_free (tmp);
+      return result;
+    }
+  else
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   _("Unsupported encoding \"%s\""), conv->encoding);
+      return G_CONVERTER_ERROR;
+    }
+
+  return FALSE;
+}
+
+static GConverterResult
+gdk_x11_text_list_converter_convert (GConverter       *converter,
+                                     const void       *inbuf,
+                                     gsize             inbuf_size,
+                                     void             *outbuf,
+                                     gsize             outbuf_size,
+                                     GConverterFlags   flags,
+                                     gsize            *bytes_read,
+                                     gsize            *bytes_written,
+                                     GError          **error)
+{
+  GdkX11TextListConverter *conv = GDK_X11_TEXT_LIST_CONVERTER (converter);
+
+  if (conv->encoder)
+    {
+      return gdk_x11_text_list_converter_encode (conv,
+                                                 inbuf, inbuf_size,
+                                                 outbuf, outbuf_size,
+                                                 flags,
+                                                 bytes_read, bytes_written,
+                                                 error);
+    }
+  else
+    {
+      return gdk_x11_text_list_converter_decode (conv,
+                                                 inbuf, inbuf_size,
+                                                 outbuf, outbuf_size,
+                                                 flags,
+                                                 bytes_read, bytes_written,
+                                                 error);
     }
 }
 
@@ -167,3 +294,20 @@ gdk_x11_text_list_converter_to_utf8_new (GdkDisplay *display,
   return G_CONVERTER (conv);
 }
 
+GConverter *
+gdk_x11_text_list_converter_from_utf8_new (GdkDisplay *display,
+                                           const char *encoding,
+                                           int         format)
+{
+  GdkX11TextListConverter *conv;
+
+  conv = g_object_new (GDK_TYPE_X11_TEXT_LIST_CONVERTER, NULL);
+
+  conv->display = g_object_ref (display);
+  conv->encoding = g_intern_string (encoding);
+  conv->format = format;
+  conv->encoder = TRUE;
+
+  return G_CONVERTER (conv);
+}
+
diff --git a/gdk/x11/gdktextlistconverter-x11.h b/gdk/x11/gdktextlistconverter-x11.h
index 9017a8b..473c518 100644
--- a/gdk/x11/gdktextlistconverter-x11.h
+++ b/gdk/x11/gdktextlistconverter-x11.h
@@ -37,6 +37,9 @@ GType              gdk_x11_text_list_converter_get_type         (void) G_GNUC_CO
 GConverter *       gdk_x11_text_list_converter_to_utf8_new      (GdkDisplay     *display,
                                                                  const char     *encoding,
                                                                  int             format);
+GConverter *       gdk_x11_text_list_converter_from_utf8_new    (GdkDisplay     *display,
+                                                                 const char     *encoding,
+                                                                 int             format);
 
 
 G_END_DECLS
diff --git a/gdk/x11/meson.build b/gdk/x11/meson.build
index db74295..5321381 100644
--- a/gdk/x11/meson.build
+++ b/gdk/x11/meson.build
@@ -22,6 +22,7 @@ gdk_x11_sources = files([
   'gdkscreen-x11.c',
   'gdkselection-x11.c',
   'gdkselectioninputstream-x11.c',
+  'gdkselectionoutputstream-x11.c',
   'gdktextlistconverter-x11.c',
   'gdkvisual-x11.c',
   'gdkvulkancontext-x11.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]