mutter/src/core/meta-selection.c
Pascal Nowack 537e2dfafe core/selection: Cancel selection transfer requests after a timeout
When a selection owner advertises a mime type, but does not provide the
content upon a request for the mime type content, the requesting side
might wait indefinitely on the content.
To avoid this situation, add a timeout source, which will cancel the
selection transfer request after a certain timeout (15 seconds) passed.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1874>
2021-07-28 15:26:04 +00:00

484 lines
14 KiB
C

/*
* Copyright (C) 2018 Red Hat
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Author: Carlos Garnacho <carlosg@gnome.org>
*/
#include "config.h"
#include "core/meta-selection-private.h"
#include "meta/meta-selection.h"
typedef struct TransferRequest TransferRequest;
struct _MetaSelection
{
GObject parent_instance;
MetaDisplay *display;
MetaSelectionSource *owners[META_N_SELECTION_TYPES];
};
struct TransferRequest
{
MetaSelectionType selection_type;
GInputStream *istream;
GOutputStream *ostream;
gssize len;
GSource *timeout_source;
GCancellable *cancellable;
GCancellable *external_cancellable;
gulong cancellable_signal_handler;
};
enum
{
OWNER_CHANGED,
N_SIGNALS
};
static guint signals[N_SIGNALS] = { 0 };
G_DEFINE_TYPE (MetaSelection, meta_selection, G_TYPE_OBJECT)
static void read_selection_source_async (GTask *task,
TransferRequest *request);
static void
meta_selection_dispose (GObject *object)
{
MetaSelection *selection = META_SELECTION (object);
guint i;
for (i = 0; i < META_N_SELECTION_TYPES; i++)
{
g_clear_object (&selection->owners[i]);
}
G_OBJECT_CLASS (meta_selection_parent_class)->dispose (object);
}
static void
meta_selection_class_init (MetaSelectionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = meta_selection_dispose;
signals[OWNER_CHANGED] =
g_signal_new ("owner-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2,
G_TYPE_UINT,
META_TYPE_SELECTION_SOURCE);
}
static void
meta_selection_init (MetaSelection *selection)
{
}
MetaSelection *
meta_selection_new (MetaDisplay *display)
{
return g_object_new (META_TYPE_SELECTION,
NULL);
}
/**
* meta_selection_set_owner:
* @selection: The selection manager
* @selection_type: Selection type
* @owner: New selection owner
*
* Sets @owner as the owner of the selection given by @selection_type,
* unsets any previous owner there was.
**/
void
meta_selection_set_owner (MetaSelection *selection,
MetaSelectionType selection_type,
MetaSelectionSource *owner)
{
g_return_if_fail (META_IS_SELECTION (selection));
g_return_if_fail (selection_type < META_N_SELECTION_TYPES);
if (selection->owners[selection_type] == owner)
return;
if (selection->owners[selection_type])
g_signal_emit_by_name (selection->owners[selection_type], "deactivated");
g_set_object (&selection->owners[selection_type], owner);
g_signal_emit_by_name (owner, "activated");
g_signal_emit (selection, signals[OWNER_CHANGED], 0, selection_type, owner);
}
/**
* meta_selection_unset_owner:
* @selection: The selection manager
* @selection_type: Selection type
* @owner: Owner to unset
*
* Unsets @owner as the owner the selection given by @selection_type. If
* @owner does not own the selection, nothing is done.
**/
void
meta_selection_unset_owner (MetaSelection *selection,
MetaSelectionType selection_type,
MetaSelectionSource *owner)
{
g_return_if_fail (META_IS_SELECTION (selection));
g_return_if_fail (selection_type < META_N_SELECTION_TYPES);
if (selection->owners[selection_type] == owner)
{
g_signal_emit_by_name (owner, "deactivated");
g_clear_object (&selection->owners[selection_type]);
g_signal_emit (selection, signals[OWNER_CHANGED], 0,
selection_type, NULL);
}
}
/**
* meta_selection_get_mimetypes:
* @selection: The selection manager
* @selection_type: Selection to query
*
* Returns the list of supported mimetypes for the given selection type.
*
* Returns: (element-type utf8) (transfer full): The supported mimetypes
*/
GList *
meta_selection_get_mimetypes (MetaSelection *selection,
MetaSelectionType selection_type)
{
g_return_val_if_fail (META_IS_SELECTION (selection), NULL);
g_return_val_if_fail (selection_type < META_N_SELECTION_TYPES, NULL);
if (!selection->owners[selection_type])
return NULL;
return meta_selection_source_get_mimetypes (selection->owners[selection_type]);
}
static gboolean
cancel_transfer_request (gpointer user_data)
{
TransferRequest *request = user_data;
g_cancellable_cancel (request->cancellable);
if (request->cancellable_signal_handler)
{
g_assert (request->external_cancellable);
g_cancellable_disconnect (request->external_cancellable,
request->cancellable_signal_handler);
request->cancellable_signal_handler = 0;
g_object_unref (request->external_cancellable);
}
return G_SOURCE_REMOVE;
}
static void
on_external_cancellable_cancelled (GCancellable *external_cancellable,
TransferRequest *request)
{
g_cancellable_cancel (request->cancellable);
g_source_destroy (request->timeout_source);
g_clear_pointer (&request->timeout_source, g_source_unref);
}
static TransferRequest *
transfer_request_new (GOutputStream *ostream,
MetaSelectionType selection_type,
ssize_t len,
GCancellable *external_cancellable)
{
TransferRequest *request;
request = g_new0 (TransferRequest, 1);
request->ostream = g_object_ref (ostream);
request->selection_type = selection_type;
request->len = len;
request->cancellable = g_cancellable_new ();
request->timeout_source = g_timeout_source_new_seconds (15);
g_source_set_callback (request->timeout_source, cancel_transfer_request,
request, NULL);
g_source_attach (request->timeout_source, NULL);
if (external_cancellable)
{
request->external_cancellable = g_object_ref (external_cancellable);
request->cancellable_signal_handler =
g_cancellable_connect (external_cancellable,
G_CALLBACK (on_external_cancellable_cancelled),
request, NULL);
}
return request;
}
static void
transfer_request_free (TransferRequest *request)
{
if (request->cancellable_signal_handler)
{
g_assert (request->external_cancellable);
g_cancellable_disconnect (request->external_cancellable,
request->cancellable_signal_handler);
request->cancellable_signal_handler = 0;
g_object_unref (request->external_cancellable);
}
if (request->timeout_source)
{
g_source_destroy (request->timeout_source);
g_clear_pointer (&request->timeout_source, g_source_unref);
}
g_clear_object (&request->cancellable);
g_clear_object (&request->istream);
g_clear_object (&request->ostream);
g_free (request);
}
static void
splice_cb (GOutputStream *stream,
GAsyncResult *result,
GTask *task)
{
GError *error = NULL;
g_output_stream_splice_finish (stream, result, &error);
if (error)
{
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
write_cb (GOutputStream *stream,
GAsyncResult *result,
GTask *task)
{
TransferRequest *request;
GError *error = NULL;
g_output_stream_write_bytes_finish (stream, result, &error);
if (error)
{
g_task_return_error (task, error);
g_object_unref (task);
return;
}
request = g_task_get_task_data (task);
if (request->len > 0)
{
read_selection_source_async (task, request);
}
else
{
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
}
static void
read_cb (GInputStream *stream,
GAsyncResult *result,
GTask *task)
{
TransferRequest *request;
GError *error = NULL;
GBytes *bytes;
bytes = g_input_stream_read_bytes_finish (stream, result, &error);
if (error)
{
g_task_return_error (task, error);
g_object_unref (task);
return;
}
else if (g_bytes_get_size (bytes) == 0)
{
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
request = g_task_get_task_data (task);
if (request->len < g_bytes_get_size (bytes))
{
GBytes *copy;
/* Trim content */
copy = g_bytes_new_from_bytes (bytes, 0, request->len);
g_bytes_unref (bytes);
bytes = copy;
}
request->len -= g_bytes_get_size (bytes);
g_output_stream_write_bytes_async (request->ostream,
bytes,
G_PRIORITY_DEFAULT,
g_task_get_cancellable (task),
(GAsyncReadyCallback) write_cb,
task);
g_bytes_unref (bytes);
}
static void
read_selection_source_async (GTask *task,
TransferRequest *request)
{
g_input_stream_read_bytes_async (request->istream,
(gsize) request->len,
G_PRIORITY_DEFAULT,
g_task_get_cancellable (task),
(GAsyncReadyCallback) read_cb,
task);
}
static void
source_read_cb (MetaSelectionSource *source,
GAsyncResult *result,
GTask *task)
{
TransferRequest *request;
GInputStream *stream;
GError *error = NULL;
stream = meta_selection_source_read_finish (source, result, &error);
if (!stream)
{
g_task_return_error (task, error);
g_object_unref (task);
return;
}
request = g_task_get_task_data (task);
request->istream = stream;
if (request->len < 0)
{
g_output_stream_splice_async (request->ostream,
request->istream,
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
G_PRIORITY_DEFAULT,
g_task_get_cancellable (task),
(GAsyncReadyCallback) splice_cb,
task);
}
else
{
read_selection_source_async (task, request);
}
}
/**
* meta_selection_transfer_async:
* @selection: The selection manager
* @selection_type: Selection type
* @mimetype: Mimetype to transfer
* @size: Maximum size to transfer, -1 for unlimited
* @output: Output stream to write contents to
* @cancellable: Cancellable
* @callback: User callback
* @user_data: User data
*
* Requests a transfer of @mimetype on the selection given by
* @selection_type.
**/
void
meta_selection_transfer_async (MetaSelection *selection,
MetaSelectionType selection_type,
const char *mimetype,
gssize size,
GOutputStream *output,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
TransferRequest *transfer_request;
GTask *task;
g_return_if_fail (META_IS_SELECTION (selection));
g_return_if_fail (selection_type < META_N_SELECTION_TYPES);
g_return_if_fail (G_IS_OUTPUT_STREAM (output));
g_return_if_fail (mimetype != NULL);
task = g_task_new (selection, cancellable, callback, user_data);
g_task_set_source_tag (task, meta_selection_transfer_async);
transfer_request = transfer_request_new (output, selection_type, size,
cancellable);
g_task_set_task_data (task, transfer_request,
(GDestroyNotify) transfer_request_free);
meta_selection_source_read_async (selection->owners[selection_type],
mimetype,
transfer_request->cancellable,
(GAsyncReadyCallback) source_read_cb,
task);
}
/**
* meta_selection_transfer_finish:
* @selection: The selection manager
* @result: The async result
* @error: Location for returned error, or %NULL
*
* Finishes the transfer of a queried mimetype.
*
* Returns: #TRUE if the transfer was successful.
**/
gboolean
meta_selection_transfer_finish (MetaSelection *selection,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, selection), FALSE);
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) ==
meta_selection_transfer_async, FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
MetaSelectionSource *
meta_selection_get_current_owner (MetaSelection *selection,
MetaSelectionType selection_type)
{
g_return_val_if_fail (META_IS_SELECTION (selection), NULL);
g_return_val_if_fail (selection_type < META_N_SELECTION_TYPES, NULL);
return selection->owners[selection_type];
}