mutter/src/x11/meta-x11-selection-input-stream.c
Sebastian Keller 986d3043ab x11: Avoid memory waste and work when creating selection input stream
Since every input stream now uses its own window, the X property used to
transfer the data no longer has to be unique, so we can stop generating
those unique names. This avoids creating a new atom for every transfer
since those are never freed, neither on the shell nor on the server
side. Also don't unnecessarily duplicate other strings that are
(almost) never used and get them from the atom in the rare case when
they are needed.

Fixes https://gitlab.gnome.org/GNOME/mutter/-/issues/1328

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1812>
2022-01-07 02:21:28 +01:00

580 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;
Atom xselection;
Atom xtarget;
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);
Display *xdisplay = priv->x11_display->xdisplay;
gssize written;
meta_x11_error_trap_push (priv->x11_display);
XDeleteProperty (xdisplay, priv->window, priv->xproperty);
meta_x11_error_trap_pop (priv->x11_display);
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);
Display *xdisplay = priv->x11_display->xdisplay;
g_async_queue_unref (priv->chunks);
XDestroyWindow (xdisplay, priv->window);
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;
char *target;
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);
}
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)
{
target = XGetAtomName (xdisplay, priv->xtarget);
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
_("Format %s not supported"), target);
meta_x11_selection_input_stream_complete (stream);
XFree (target);
}
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);
}
}
}
g_object_unref (task);
}
return TRUE;
default:
return FALSE;
}
}
void
meta_x11_selection_input_stream_new_async (MetaX11Display *x11_display,
const char *selection,
const char *target,
guint32 timestamp,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
MetaX11SelectionInputStream *stream;
MetaX11SelectionInputStreamPrivate *priv;
XSetWindowAttributes attributes = { 0 };
stream = g_object_new (META_TYPE_X11_SELECTION_INPUT_STREAM, NULL);
priv = meta_x11_selection_input_stream_get_instance_private (stream);
attributes.event_mask = PropertyChangeMask;
attributes.override_redirect = True;
priv->x11_display = x11_display;
x11_display->selection.input_streams =
g_list_prepend (x11_display->selection.input_streams, stream);
priv->xselection = XInternAtom (x11_display->xdisplay, selection, False);
priv->xtarget = XInternAtom (x11_display->xdisplay, target, False);
priv->xproperty = XInternAtom (x11_display->xdisplay, "META_SELECTION", False);
priv->window = XCreateWindow (x11_display->xdisplay,
x11_display->xroot,
-1, -1, 1, 1,
0, /* border width */
0, /* depth */
InputOnly, /* class */
CopyFromParent, /* visual */
CWEventMask | CWOverrideRedirect,
&attributes);
XConvertSelection (x11_display->xdisplay,
priv->xselection,
priv->xtarget,
priv->xproperty,
priv->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);
}