mutter/src/x11/meta-x11-selection-input-stream.c
Carlos Garnacho e1fa0734a9 x11: Detach selection streams on dispose
The streams were only detached from MetaX11Display (and its event handling)
on completion. This is too much to expect, and those might be in some
circumstances replaced while operating.

Make those streams detach themselves on dispose(), so we don't trip into
freed memory later on when trying to dispatch unrelated X11 selection events.

https://gitlab.gnome.org/GNOME/mutter/merge_requests/869
2019-10-21 11:47:40 +02:00

573 lines
18 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2017 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Author: Benjamin Otte <otte@gnome.org>
* Christian Kellner <gicmo@gnome.org>
*/
#include "config.h"
#include "meta-x11-selection-input-stream-private.h"
#include <gdk/gdkx.h>
#include "meta/meta-x11-errors.h"
#include "x11/meta-x11-display-private.h"
typedef struct MetaX11SelectionInputStreamPrivate MetaX11SelectionInputStreamPrivate;
struct _MetaX11SelectionInputStream
{
GInputStream parent_instance;
};
struct MetaX11SelectionInputStreamPrivate
{
MetaX11Display *x11_display;
Window window;
GAsyncQueue *chunks;
char *selection;
Atom xselection;
char *target;
Atom xtarget;
char *property;
Atom xproperty;
const char *type;
Atom xtype;
int format;
GTask *pending_task;
uint8_t *pending_data;
size_t pending_size;
guint complete : 1;
guint incr : 1;
};
G_DEFINE_TYPE_WITH_PRIVATE (MetaX11SelectionInputStream,
meta_x11_selection_input_stream,
G_TYPE_INPUT_STREAM)
static gboolean
meta_x11_selection_input_stream_has_data (MetaX11SelectionInputStream *stream)
{
MetaX11SelectionInputStreamPrivate *priv =
meta_x11_selection_input_stream_get_instance_private (stream);
return g_async_queue_length (priv->chunks) > 0 || priv->complete;
}
/* NB: blocks when no data is in buffer */
static size_t
meta_x11_selection_input_stream_fill_buffer (MetaX11SelectionInputStream *stream,
uint8_t *buffer,
size_t count)
{
MetaX11SelectionInputStreamPrivate *priv =
meta_x11_selection_input_stream_get_instance_private (stream);
GBytes *bytes;
size_t result = 0;
g_async_queue_lock (priv->chunks);
for (bytes = g_async_queue_pop_unlocked (priv->chunks);
bytes != NULL && count > 0;
bytes = g_async_queue_try_pop_unlocked (priv->chunks))
{
size_t size = g_bytes_get_size (bytes);
if (size == 0)
{
/* EOF marker, put it back */
g_async_queue_push_front_unlocked (priv->chunks, bytes);
bytes = NULL;
break;
}
else if (size > count)
{
GBytes *subbytes;
if (buffer)
memcpy (buffer, g_bytes_get_data (bytes, NULL), count);
subbytes = g_bytes_new_from_bytes (bytes, count, size - count);
g_async_queue_push_front_unlocked (priv->chunks, subbytes);
size = count;
}
else
{
if (buffer)
memcpy (buffer, g_bytes_get_data (bytes, NULL), size);
}
g_bytes_unref (bytes);
result += size;
if (buffer)
buffer += size;
count -= size;
}
if (bytes)
g_async_queue_push_front_unlocked (priv->chunks, bytes);
g_async_queue_unlock (priv->chunks);
return result;
}
static void
meta_x11_selection_input_stream_flush (MetaX11SelectionInputStream *stream)
{
MetaX11SelectionInputStreamPrivate *priv =
meta_x11_selection_input_stream_get_instance_private (stream);
gssize written;
if (!meta_x11_selection_input_stream_has_data (stream))
return;
if (priv->pending_task == NULL)
return;
written = meta_x11_selection_input_stream_fill_buffer (stream,
priv->pending_data,
priv->pending_size);
g_task_return_int (priv->pending_task, written);
g_clear_object (&priv->pending_task);
priv->pending_data = NULL;
priv->pending_size = 0;
}
static void
meta_x11_selection_input_stream_complete (MetaX11SelectionInputStream *stream)
{
MetaX11SelectionInputStreamPrivate *priv =
meta_x11_selection_input_stream_get_instance_private (stream);
if (priv->complete)
return;
priv->complete = TRUE;
g_async_queue_push (priv->chunks, g_bytes_new (NULL, 0));
meta_x11_selection_input_stream_flush (stream);
priv->x11_display->selection.input_streams =
g_list_remove (priv->x11_display->selection.input_streams, stream);
g_object_unref (stream);
}
static gssize
meta_x11_selection_input_stream_read (GInputStream *input_stream,
void *buffer,
size_t count,
GCancellable *cancellable,
GError **error)
{
MetaX11SelectionInputStream *stream =
META_X11_SELECTION_INPUT_STREAM (input_stream);
return meta_x11_selection_input_stream_fill_buffer (stream, buffer, count);
}
static gboolean
meta_x11_selection_input_stream_close (GInputStream *stream,
GCancellable *cancellable,
GError **error)
{
return TRUE;
}
static void
meta_x11_selection_input_stream_read_async (GInputStream *input_stream,
void *buffer,
size_t count,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
MetaX11SelectionInputStream *stream =
META_X11_SELECTION_INPUT_STREAM (input_stream);
MetaX11SelectionInputStreamPrivate *priv =
meta_x11_selection_input_stream_get_instance_private (stream);
GTask *task;
task = g_task_new (stream, cancellable, callback, user_data);
g_task_set_source_tag (task, meta_x11_selection_input_stream_read_async);
g_task_set_priority (task, io_priority);
if (meta_x11_selection_input_stream_has_data (stream))
{
gssize size;
size = meta_x11_selection_input_stream_fill_buffer (stream, buffer, count);
g_task_return_int (task, size);
g_object_unref (task);
}
else
{
priv->pending_data = buffer;
priv->pending_size = count;
priv->pending_task = task;
}
}
static gssize
meta_x11_selection_input_stream_read_finish (GInputStream *stream,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, stream), -1);
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) ==
meta_x11_selection_input_stream_read_async, -1);
return g_task_propagate_int (G_TASK (result), error);
}
static void
meta_x11_selection_input_stream_close_async (GInputStream *stream,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (stream, cancellable, callback, user_data);
g_task_set_source_tag (task, meta_x11_selection_input_stream_close_async);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static gboolean
meta_x11_selection_input_stream_close_finish (GInputStream *stream,
GAsyncResult *result,
GError **error)
{
return TRUE;
}
static void
meta_x11_selection_input_stream_dispose (GObject *object)
{
MetaX11SelectionInputStream *stream =
META_X11_SELECTION_INPUT_STREAM (object);
MetaX11SelectionInputStreamPrivate *priv =
meta_x11_selection_input_stream_get_instance_private (stream);
priv->x11_display->selection.input_streams =
g_list_remove (priv->x11_display->selection.input_streams, stream);
G_OBJECT_CLASS (meta_x11_selection_input_stream_parent_class)->dispose (object);
}
static void
meta_x11_selection_input_stream_finalize (GObject *object)
{
MetaX11SelectionInputStream *stream =
META_X11_SELECTION_INPUT_STREAM (object);
MetaX11SelectionInputStreamPrivate *priv =
meta_x11_selection_input_stream_get_instance_private (stream);
g_async_queue_unref (priv->chunks);
g_free (priv->selection);
g_free (priv->target);
g_free (priv->property);
G_OBJECT_CLASS (meta_x11_selection_input_stream_parent_class)->finalize (object);
}
static void
meta_x11_selection_input_stream_class_init (MetaX11SelectionInputStreamClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GInputStreamClass *istream_class = G_INPUT_STREAM_CLASS (klass);
object_class->dispose = meta_x11_selection_input_stream_dispose;
object_class->finalize = meta_x11_selection_input_stream_finalize;
istream_class->read_fn = meta_x11_selection_input_stream_read;
istream_class->close_fn = meta_x11_selection_input_stream_close;
istream_class->read_async = meta_x11_selection_input_stream_read_async;
istream_class->read_finish = meta_x11_selection_input_stream_read_finish;
istream_class->close_async = meta_x11_selection_input_stream_close_async;
istream_class->close_finish = meta_x11_selection_input_stream_close_finish;
}
static void
meta_x11_selection_input_stream_init (MetaX11SelectionInputStream *stream)
{
MetaX11SelectionInputStreamPrivate *priv =
meta_x11_selection_input_stream_get_instance_private (stream);
priv->chunks = g_async_queue_new_full ((GDestroyNotify) g_bytes_unref);
}
static void
XFree_without_return_value (gpointer data)
{
XFree (data);
}
static GBytes *
get_selection_property (Display *xdisplay,
Window owner,
Atom property,
Atom *ret_type,
gint *ret_format)
{
gulong nitems;
gulong nbytes;
Atom prop_type;
gint prop_format;
uint8_t *data = NULL;
if (XGetWindowProperty (xdisplay, owner, property,
0, 0x1FFFFFFF, False,
AnyPropertyType, &prop_type, &prop_format,
&nitems, &nbytes, &data) != Success)
goto err;
if (prop_type != None)
{
size_t length;
switch (prop_format)
{
case 8:
length = nitems;
break;
case 16:
length = sizeof (short) * nitems;
break;
case 32:
length = sizeof (long) * nitems;
break;
default:
g_warning ("Unknown XGetWindowProperty() format %u", prop_format);
goto err;
}
*ret_type = prop_type;
*ret_format = prop_format;
return g_bytes_new_with_free_func (data,
length,
XFree_without_return_value,
data);
}
err:
if (data)
XFree (data);
*ret_type = None;
*ret_format = 0;
return NULL;
}
gboolean
meta_x11_selection_input_stream_xevent (MetaX11SelectionInputStream *stream,
const XEvent *xevent)
{
MetaX11SelectionInputStreamPrivate *priv =
meta_x11_selection_input_stream_get_instance_private (stream);
Display *xdisplay;
Window xwindow;
GBytes *bytes;
Atom type;
gint format;
xdisplay = priv->x11_display->xdisplay;
xwindow = priv->window;
if (xevent->xany.display != xdisplay ||
xevent->xany.window != xwindow)
return FALSE;
switch (xevent->type)
{
case PropertyNotify:
if (!priv->incr ||
xevent->xproperty.atom != priv->xproperty ||
xevent->xproperty.state != PropertyNewValue)
return FALSE;
bytes = get_selection_property (xdisplay, xwindow, xevent->xproperty.atom,
&type, &format);
if (bytes == NULL)
{
g_debug ("INCR request came out empty");
meta_x11_selection_input_stream_complete (stream);
}
else if (g_bytes_get_size (bytes) == 0 || type == None)
{
g_bytes_unref (bytes);
meta_x11_selection_input_stream_complete (stream);
}
else
{
g_async_queue_push (priv->chunks, bytes);
meta_x11_selection_input_stream_flush (stream);
}
XDeleteProperty (xdisplay, xwindow, xevent->xproperty.atom);
return FALSE;
case SelectionNotify:
{
GTask *task;
/* selection is not for us */
if (priv->xselection != xevent->xselection.selection ||
priv->xtarget != xevent->xselection.target)
return FALSE;
/* We already received a selectionNotify before */
if (priv->pending_task == NULL ||
g_task_get_source_tag (priv->pending_task) != meta_x11_selection_input_stream_new_async)
{
g_debug ("Misbehaving client sent a reentrant SelectionNotify");
return FALSE;
}
task = g_steal_pointer (&priv->pending_task);
if (xevent->xselection.property == None)
{
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
_("Format %s not supported"), priv->target);
meta_x11_selection_input_stream_complete (stream);
}
else
{
bytes = get_selection_property (xdisplay, xwindow,
xevent->xselection.property,
&priv->xtype, &priv->format);
priv->type = gdk_x11_get_xatom_name (priv->xtype);
g_task_return_pointer (task, g_object_ref (stream), g_object_unref);
if (bytes == NULL)
{
meta_x11_selection_input_stream_complete (stream);
}
else
{
if (priv->xtype == XInternAtom (priv->x11_display->xdisplay, "INCR", False))
{
/* The remainder of the selection will come through PropertyNotify
events on xwindow */
priv->incr = TRUE;
meta_x11_selection_input_stream_flush (stream);
}
else
{
g_async_queue_push (priv->chunks, bytes);
meta_x11_selection_input_stream_complete (stream);
}
}
XDeleteProperty (xdisplay, xwindow, xevent->xselection.property);
}
g_object_unref (task);
}
return TRUE;
default:
return FALSE;
}
}
void
meta_x11_selection_input_stream_new_async (MetaX11Display *x11_display,
Window window,
const char *selection,
const char *target,
guint32 timestamp,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
MetaX11SelectionInputStream *stream;
MetaX11SelectionInputStreamPrivate *priv;
stream = g_object_new (META_TYPE_X11_SELECTION_INPUT_STREAM, NULL);
priv = meta_x11_selection_input_stream_get_instance_private (stream);
priv->x11_display = x11_display;
x11_display->selection.input_streams =
g_list_prepend (x11_display->selection.input_streams, stream);
priv->selection = g_strdup (selection);
priv->xselection = XInternAtom (x11_display->xdisplay, priv->selection, False);
priv->target = g_strdup (target);
priv->xtarget = XInternAtom (x11_display->xdisplay, priv->target, False);
priv->property = g_strdup_printf ("META_SELECTION_%p", stream);
priv->xproperty = XInternAtom (x11_display->xdisplay, priv->property, False);
priv->window = window;
XConvertSelection (x11_display->xdisplay,
priv->xselection,
priv->xtarget,
priv->xproperty,
window,
timestamp);
priv->pending_task = g_task_new (NULL, cancellable, callback, user_data);
g_task_set_source_tag (priv->pending_task, meta_x11_selection_input_stream_new_async);
g_task_set_priority (priv->pending_task, io_priority);
}
GInputStream *
meta_x11_selection_input_stream_new_finish (GAsyncResult *result,
const char **type,
int *format,
GError **error)
{
MetaX11SelectionInputStream *stream;
MetaX11SelectionInputStreamPrivate *priv;
GTask *task;
g_return_val_if_fail (g_task_is_valid (result, NULL), NULL);
task = G_TASK (result);
g_return_val_if_fail (g_task_get_source_tag (task) ==
meta_x11_selection_input_stream_new_async, NULL);
stream = g_task_propagate_pointer (task, error);
if (!stream)
return NULL;
priv = meta_x11_selection_input_stream_get_instance_private (stream);
if (type)
*type = priv->type;
if (format)
*format = priv->format;
return G_INPUT_STREAM (stream);
}