mirror of
https://github.com/brl/mutter.git
synced 2024-12-27 21:32:14 +00:00
537e2dfafe
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>
484 lines
14 KiB
C
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];
|
|
}
|