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 . + * + * Author: Benjamin Otte + * Christian Kellner + */ + +#ifndef META_X11_SELECTION_INPUT_STREAM_H +#define META_X11_SELECTION_INPUT_STREAM_H + +#include + +#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 . + * + * Author: Benjamin Otte + * Christian Kellner + */ + +#include "config.h" + +#include "meta-x11-selection-input-stream-private.h" + +#include + +#include "meta/meta-x11-errors.h" +#include "x11/meta-x11-display-private.h" + +typedef struct MetaX11SelectionInputStreamPrivate MetaX11SelectionInputStreamPrivate; + +struct _MetaX11SelectionInputStream +{ + GInputStream parent_instance; +}; + +struct MetaX11SelectionInputStreamPrivate +{ + MetaX11Display *x11_display; + Window window; + GAsyncQueue *chunks; + char *selection; + Atom xselection; + char *target; + Atom xtarget; + char *property; + Atom xproperty; + const char *type; + Atom xtype; + int format; + + GTask *pending_task; + uint8_t *pending_data; + size_t pending_size; + + guint complete : 1; + guint incr : 1; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (MetaX11SelectionInputStream, + meta_x11_selection_input_stream, + G_TYPE_INPUT_STREAM) + +static gboolean +meta_x11_selection_input_stream_has_data (MetaX11SelectionInputStream *stream) +{ + MetaX11SelectionInputStreamPrivate *priv = + meta_x11_selection_input_stream_get_instance_private (stream); + + return g_async_queue_length (priv->chunks) > 0 || priv->complete; +} + +/* NB: blocks when no data is in buffer */ +static size_t +meta_x11_selection_input_stream_fill_buffer (MetaX11SelectionInputStream *stream, + uint8_t *buffer, + size_t count) +{ + MetaX11SelectionInputStreamPrivate *priv = + meta_x11_selection_input_stream_get_instance_private (stream); + GBytes *bytes; + size_t result = 0; + + g_async_queue_lock (priv->chunks); + + for (bytes = g_async_queue_pop_unlocked (priv->chunks); + bytes != NULL && count > 0; + bytes = g_async_queue_try_pop_unlocked (priv->chunks)) + { + size_t size = g_bytes_get_size (bytes); + + if (size == 0) + { + /* EOF marker, put it back */ + g_async_queue_push_front_unlocked (priv->chunks, bytes); + bytes = NULL; + break; + } + else if (size > count) + { + GBytes *subbytes; + if (buffer) + memcpy (buffer, g_bytes_get_data (bytes, NULL), count); + subbytes = g_bytes_new_from_bytes (bytes, count, size - count); + g_async_queue_push_front_unlocked (priv->chunks, subbytes); + size = count; + } + else + { + if (buffer) + memcpy (buffer, g_bytes_get_data (bytes, NULL), size); + } + + g_bytes_unref (bytes); + result += size; + if (buffer) + buffer += size; + count -= size; + } + + if (bytes) + g_async_queue_push_front_unlocked (priv->chunks, bytes); + + g_async_queue_unlock (priv->chunks); + + return result; +} + +static void +meta_x11_selection_input_stream_flush (MetaX11SelectionInputStream *stream) +{ + MetaX11SelectionInputStreamPrivate *priv = + meta_x11_selection_input_stream_get_instance_private (stream); + gssize written; + + if (!meta_x11_selection_input_stream_has_data (stream)) + return; + + if (priv->pending_task == NULL) + return; + + written = meta_x11_selection_input_stream_fill_buffer (stream, + priv->pending_data, + priv->pending_size); + g_task_return_int (priv->pending_task, written); + + g_clear_object (&priv->pending_task); + priv->pending_data = NULL; + priv->pending_size = 0; +} + +static void +meta_x11_selection_input_stream_complete (MetaX11SelectionInputStream *stream) +{ + MetaX11SelectionInputStreamPrivate *priv = + meta_x11_selection_input_stream_get_instance_private (stream); + + if (priv->complete) + return; + + priv->complete = TRUE; + + g_async_queue_push (priv->chunks, g_bytes_new (NULL, 0)); + meta_x11_selection_input_stream_flush (stream); + + priv->x11_display->selection.input_streams = + g_list_remove (priv->x11_display->selection.input_streams, stream); + + g_object_unref (stream); +} + +static gssize +meta_x11_selection_input_stream_read (GInputStream *input_stream, + void *buffer, + size_t count, + GCancellable *cancellable, + GError **error) +{ + MetaX11SelectionInputStream *stream = + META_X11_SELECTION_INPUT_STREAM (input_stream); + + return meta_x11_selection_input_stream_fill_buffer (stream, buffer, count); +} + +static gboolean +meta_x11_selection_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + return TRUE; +} + +static void +meta_x11_selection_input_stream_read_async (GInputStream *input_stream, + void *buffer, + size_t count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MetaX11SelectionInputStream *stream = + META_X11_SELECTION_INPUT_STREAM (input_stream); + MetaX11SelectionInputStreamPrivate *priv = + meta_x11_selection_input_stream_get_instance_private (stream); + GTask *task; + + task = g_task_new (stream, cancellable, callback, user_data); + g_task_set_source_tag (task, meta_x11_selection_input_stream_read_async); + g_task_set_priority (task, io_priority); + + if (meta_x11_selection_input_stream_has_data (stream)) + { + gssize size; + + size = meta_x11_selection_input_stream_fill_buffer (stream, buffer, count); + g_task_return_int (task, size); + g_object_unref (task); + } + else + { + priv->pending_data = buffer; + priv->pending_size = count; + priv->pending_task = task; + } +} + +static gssize +meta_x11_selection_input_stream_read_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, stream), -1); + g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == + meta_x11_selection_input_stream_read_async, -1); + + return g_task_propagate_int (G_TASK (result), error); +} + +static void +meta_x11_selection_input_stream_close_async (GInputStream *stream, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (stream, cancellable, callback, user_data); + g_task_set_source_tag (task, meta_x11_selection_input_stream_close_async); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static gboolean +meta_x11_selection_input_stream_close_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + return TRUE; +} + +static void +meta_x11_selection_input_stream_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 . + * + * Author: Benjamin Otte + * Christian Kellner + */ + +#ifndef META_X11_SELECTION_OUTPUT_STREAM_H +#define META_X11_SELECTION_OUTPUT_STREAM_H + +#include + +#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 . + * + * Author: Benjamin Otte + * Christian Kellner + */ + +#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); +}