gdk/wayland: Handle simultaneous selection requests
Cache separately the selection contents for each given window/selection/atom combination, and keep the requestors separate for each of those. This allows us to incrementally request multiple mimetypes, and dispatch the requestors as soon as the data is up. This stored selection content is cached until the selection owner changes, at which point all pending readers could get their transfers cancelled, and the stored content for the selection forgotten.
This commit is contained in:
@ -53,12 +53,14 @@ struct _SelectionBuffer
|
||||
|
||||
struct _StoredSelection
|
||||
{
|
||||
GdkWaylandSelection *selection;
|
||||
GdkWindow *source;
|
||||
GCancellable *cancellable;
|
||||
guchar *data;
|
||||
gsize data_len;
|
||||
GdkAtom type;
|
||||
gint fd;
|
||||
GdkAtom selection_atom;
|
||||
GPtrArray *pending_writes; /* Array of AsyncWriteData */
|
||||
};
|
||||
|
||||
struct _DataOfferData
|
||||
@ -71,7 +73,7 @@ struct _DataOfferData
|
||||
struct _AsyncWriteData
|
||||
{
|
||||
GOutputStream *stream;
|
||||
GdkWaylandSelection *selection;
|
||||
StoredSelection *stored_selection;
|
||||
gsize index;
|
||||
};
|
||||
|
||||
@ -97,7 +99,8 @@ struct _GdkWaylandSelection
|
||||
GHashTable *offers; /* Currently alive offers, Hashtable of wl_data_offer->DataOfferData */
|
||||
|
||||
/* Source-side data */
|
||||
StoredSelection stored_selection;
|
||||
GPtrArray *stored_selections; /* Array of StoredSelection */
|
||||
GdkAtom current_request_selection;
|
||||
GArray *source_targets;
|
||||
GdkAtom requested_target;
|
||||
|
||||
@ -113,10 +116,12 @@ struct _GdkWaylandSelection
|
||||
|
||||
static void selection_buffer_read (SelectionBuffer *buffer);
|
||||
static void async_write_data_write (AsyncWriteData *write_data);
|
||||
static void async_write_data_free (AsyncWriteData *write_data);
|
||||
static void emit_selection_clear (GdkDisplay *display, GdkAtom selection);
|
||||
static void emit_empty_selection_notify (GdkWindow *requestor,
|
||||
GdkAtom selection,
|
||||
GdkAtom target);
|
||||
static void gdk_wayland_selection_handle_next_request (GdkWaylandSelection *wayland_selection);
|
||||
|
||||
static void
|
||||
selection_buffer_notify (SelectionBuffer *buffer)
|
||||
@ -317,6 +322,89 @@ data_offer_data_free (DataOfferData *info)
|
||||
g_slice_free (DataOfferData, info);
|
||||
}
|
||||
|
||||
static StoredSelection *
|
||||
stored_selection_new (GdkWaylandSelection *wayland_selection,
|
||||
GdkWindow *source,
|
||||
GdkAtom selection,
|
||||
GdkAtom type)
|
||||
{
|
||||
StoredSelection *stored_selection;
|
||||
|
||||
stored_selection = g_new0 (StoredSelection, 1);
|
||||
stored_selection->source = source;
|
||||
stored_selection->type = type;
|
||||
stored_selection->selection_atom = selection;
|
||||
stored_selection->selection = wayland_selection;
|
||||
stored_selection->cancellable = g_cancellable_new ();
|
||||
stored_selection->pending_writes =
|
||||
g_ptr_array_new_with_free_func ((GDestroyNotify) async_write_data_free);
|
||||
|
||||
return stored_selection;
|
||||
}
|
||||
|
||||
static void
|
||||
stored_selection_add_data (StoredSelection *stored_selection,
|
||||
GdkPropMode mode,
|
||||
guchar *data,
|
||||
gsize data_len)
|
||||
{
|
||||
if (mode == GDK_PROP_MODE_REPLACE)
|
||||
{
|
||||
g_free (stored_selection->data);
|
||||
stored_selection->data = g_memdup (data, data_len);
|
||||
stored_selection->data_len = data_len;
|
||||
}
|
||||
else
|
||||
{
|
||||
GArray *array;
|
||||
|
||||
array = g_array_new (TRUE, TRUE, sizeof (guchar));
|
||||
g_array_append_vals (array, stored_selection->data, stored_selection->data_len);
|
||||
|
||||
if (mode == GDK_PROP_MODE_APPEND)
|
||||
g_array_append_vals (array, data, data_len);
|
||||
else if (mode == GDK_PROP_MODE_PREPEND)
|
||||
g_array_prepend_vals (array, data, data_len);
|
||||
|
||||
g_free (stored_selection->data);
|
||||
stored_selection->data_len = array->len;
|
||||
stored_selection->data = (guchar *) g_array_free (array, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
stored_selection_free (StoredSelection *stored_selection)
|
||||
{
|
||||
g_cancellable_cancel (stored_selection->cancellable);
|
||||
g_object_unref (stored_selection->cancellable);
|
||||
g_ptr_array_unref (stored_selection->pending_writes);
|
||||
g_free (stored_selection->data);
|
||||
g_free (stored_selection);
|
||||
}
|
||||
|
||||
static void
|
||||
stored_selection_notify_write (StoredSelection *stored_selection)
|
||||
{
|
||||
gint i;
|
||||
|
||||
for (i = 0; i < stored_selection->pending_writes->len; i++)
|
||||
{
|
||||
AsyncWriteData *write_data;
|
||||
|
||||
write_data = g_ptr_array_index (stored_selection->pending_writes, i);
|
||||
async_write_data_write (write_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
stored_selection_cancel_write (StoredSelection *stored_selection)
|
||||
{
|
||||
g_cancellable_cancel (stored_selection->cancellable);
|
||||
g_object_unref (stored_selection->cancellable);
|
||||
stored_selection->cancellable = g_cancellable_new ();
|
||||
g_ptr_array_set_size (stored_selection->pending_writes, 0);
|
||||
}
|
||||
|
||||
GdkWaylandSelection *
|
||||
gdk_wayland_selection_new (void)
|
||||
{
|
||||
@ -339,7 +427,9 @@ gdk_wayland_selection_new (void)
|
||||
selection->offers =
|
||||
g_hash_table_new_full (NULL, NULL, NULL,
|
||||
(GDestroyNotify) data_offer_data_free);
|
||||
selection->stored_selection.fd = -1;
|
||||
selection->stored_selections =
|
||||
g_ptr_array_new_with_free_func ((GDestroyNotify) stored_selection_free);
|
||||
|
||||
selection->source_targets = g_array_new (FALSE, FALSE, sizeof (GdkAtom));
|
||||
return selection;
|
||||
}
|
||||
@ -355,16 +445,7 @@ gdk_wayland_selection_free (GdkWaylandSelection *selection)
|
||||
g_array_unref (selection->source_targets);
|
||||
|
||||
g_hash_table_destroy (selection->offers);
|
||||
g_free (selection->stored_selection.data);
|
||||
|
||||
if (selection->stored_selection.cancellable)
|
||||
{
|
||||
g_cancellable_cancel (selection->stored_selection.cancellable);
|
||||
g_object_unref (selection->stored_selection.cancellable);
|
||||
}
|
||||
|
||||
if (selection->stored_selection.fd > 0)
|
||||
close (selection->stored_selection.fd);
|
||||
g_ptr_array_unref (selection->stored_selections);
|
||||
|
||||
if (selection->primary_source)
|
||||
gtk_primary_selection_source_destroy (selection->primary_source);
|
||||
@ -620,16 +701,15 @@ gdk_wayland_selection_emit_request (GdkWindow *window,
|
||||
}
|
||||
|
||||
static AsyncWriteData *
|
||||
async_write_data_new (GdkWaylandSelection *selection)
|
||||
async_write_data_new (StoredSelection *stored_selection,
|
||||
gint fd)
|
||||
{
|
||||
AsyncWriteData *write_data;
|
||||
|
||||
write_data = g_slice_new0 (AsyncWriteData);
|
||||
write_data->selection = selection;
|
||||
write_data->stream =
|
||||
g_unix_output_stream_new (selection->stored_selection.fd, TRUE);
|
||||
|
||||
selection->stored_selection.fd = -1;
|
||||
write_data->stored_selection = stored_selection;
|
||||
write_data->stream = g_unix_output_stream_new (fd, TRUE);
|
||||
g_ptr_array_add (stored_selection->pending_writes, write_data);
|
||||
|
||||
return write_data;
|
||||
}
|
||||
@ -659,63 +739,83 @@ async_write_data_cb (GObject *object,
|
||||
g_warning ("Error writing selection data: %s", error->message);
|
||||
|
||||
g_error_free (error);
|
||||
async_write_data_free (write_data);
|
||||
g_ptr_array_remove_fast (write_data->stored_selection->pending_writes,
|
||||
write_data);
|
||||
return;
|
||||
}
|
||||
|
||||
write_data->index += bytes_written;
|
||||
|
||||
if (write_data->index <
|
||||
write_data->selection->stored_selection.data_len)
|
||||
if (write_data->index < write_data->stored_selection->data_len)
|
||||
{
|
||||
/* Write the next chunk */
|
||||
async_write_data_write (write_data);
|
||||
}
|
||||
else
|
||||
async_write_data_free (write_data);
|
||||
{
|
||||
g_ptr_array_remove_fast (write_data->stored_selection->pending_writes,
|
||||
write_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
async_write_data_write (AsyncWriteData *write_data)
|
||||
{
|
||||
GdkWaylandSelection *selection = write_data->selection;
|
||||
gsize buf_len;
|
||||
guchar *buf;
|
||||
|
||||
buf = selection->stored_selection.data;
|
||||
buf_len = selection->stored_selection.data_len;
|
||||
buf = write_data->stored_selection->data;
|
||||
buf_len = write_data->stored_selection->data_len;
|
||||
|
||||
g_output_stream_write_async (write_data->stream,
|
||||
&buf[write_data->index],
|
||||
buf_len - write_data->index,
|
||||
G_PRIORITY_DEFAULT,
|
||||
selection->stored_selection.cancellable,
|
||||
write_data->stored_selection->cancellable,
|
||||
async_write_data_cb,
|
||||
write_data);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gdk_wayland_selection_check_write (GdkWaylandSelection *selection)
|
||||
static StoredSelection *
|
||||
gdk_wayland_selection_find_stored_selection (GdkWaylandSelection *wayland_selection,
|
||||
GdkWindow *window,
|
||||
GdkAtom selection,
|
||||
GdkAtom type)
|
||||
{
|
||||
AsyncWriteData *write_data;
|
||||
gint i;
|
||||
|
||||
if (selection->stored_selection.fd < 0)
|
||||
return FALSE;
|
||||
|
||||
/* Cancel any previous ongoing async write */
|
||||
if (selection->stored_selection.cancellable)
|
||||
for (i = 0; i < wayland_selection->stored_selections->len; i++)
|
||||
{
|
||||
g_cancellable_cancel (selection->stored_selection.cancellable);
|
||||
g_object_unref (selection->stored_selection.cancellable);
|
||||
StoredSelection *stored_selection;
|
||||
|
||||
stored_selection = g_ptr_array_index (wayland_selection->stored_selections, i);
|
||||
|
||||
if (stored_selection->source == window &&
|
||||
stored_selection->selection_atom == selection &&
|
||||
stored_selection->type == type)
|
||||
return stored_selection;
|
||||
}
|
||||
|
||||
selection->stored_selection.cancellable = g_cancellable_new ();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
write_data = async_write_data_new (selection);
|
||||
async_write_data_write (write_data);
|
||||
selection->stored_selection.fd = -1;
|
||||
static void
|
||||
gdk_wayland_selection_reset_selection (GdkWaylandSelection *wayland_selection,
|
||||
GdkAtom selection)
|
||||
{
|
||||
gint i = 0;
|
||||
|
||||
return TRUE;
|
||||
while (i < wayland_selection->stored_selections->len)
|
||||
{
|
||||
StoredSelection *stored_selection;
|
||||
|
||||
stored_selection = g_ptr_array_index (wayland_selection->stored_selections, i);
|
||||
|
||||
if (stored_selection->selection_atom == selection)
|
||||
g_ptr_array_remove_index_fast (wayland_selection->stored_selections, i);
|
||||
else
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -727,51 +827,43 @@ gdk_wayland_selection_store (GdkWindow *window,
|
||||
{
|
||||
GdkDisplay *display = gdk_window_get_display (window);
|
||||
GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
|
||||
GArray *array;
|
||||
StoredSelection *stored_selection;
|
||||
|
||||
if (type == gdk_atom_intern_static_string ("NULL"))
|
||||
return;
|
||||
if (selection->current_request_selection == GDK_NONE)
|
||||
return;
|
||||
|
||||
array = g_array_new (TRUE, FALSE, sizeof (guchar));
|
||||
g_array_append_vals (array, data, len);
|
||||
stored_selection =
|
||||
gdk_wayland_selection_find_stored_selection (selection, window,
|
||||
selection->current_request_selection,
|
||||
type);
|
||||
|
||||
if (selection->stored_selection.data)
|
||||
if (!stored_selection)
|
||||
{
|
||||
if (mode != GDK_PROP_MODE_REPLACE &&
|
||||
type != selection->stored_selection.type)
|
||||
{
|
||||
gchar *type_str, *stored_str;
|
||||
|
||||
type_str = gdk_atom_name (type);
|
||||
stored_str = gdk_atom_name (selection->stored_selection.type);
|
||||
|
||||
g_warning (G_STRLOC ": Attempted to append/prepend selection data with "
|
||||
"type %s into the current selection with type %s",
|
||||
type_str, stored_str);
|
||||
g_free (type_str);
|
||||
g_free (stored_str);
|
||||
return;
|
||||
}
|
||||
|
||||
/* In these cases we also replace the stored data, so we
|
||||
* apply the inverse operation into the just given data.
|
||||
*/
|
||||
if (mode == GDK_PROP_MODE_APPEND)
|
||||
g_array_prepend_vals (array, selection->stored_selection.data,
|
||||
selection->stored_selection.data_len - 1);
|
||||
else if (mode == GDK_PROP_MODE_PREPEND)
|
||||
g_array_append_vals (array, selection->stored_selection.data,
|
||||
selection->stored_selection.data_len - 1);
|
||||
|
||||
g_free (selection->stored_selection.data);
|
||||
stored_selection = stored_selection_new (selection, window,
|
||||
selection->current_request_selection,
|
||||
type);
|
||||
g_ptr_array_add (selection->stored_selections, stored_selection);
|
||||
}
|
||||
|
||||
selection->stored_selection.source = window;
|
||||
selection->stored_selection.data_len = array->len;
|
||||
selection->stored_selection.data = (guchar *) g_array_free (array, FALSE);
|
||||
selection->stored_selection.type = type;
|
||||
if ((mode == GDK_PROP_MODE_PREPEND ||
|
||||
mode == GDK_PROP_MODE_REPLACE) &&
|
||||
stored_selection->data &&
|
||||
stored_selection->pending_writes->len > 0)
|
||||
{
|
||||
/* If a prepend/replace action happens, all current readers are
|
||||
* pretty much stale.
|
||||
*/
|
||||
stored_selection_cancel_write (stored_selection);
|
||||
}
|
||||
|
||||
gdk_wayland_selection_check_write (selection);
|
||||
stored_selection_add_data (stored_selection, mode, data, len);
|
||||
stored_selection_notify_write (stored_selection);
|
||||
|
||||
/* Handle the next GDK_SELECTION_REQUEST / store, if any */
|
||||
selection->current_request_selection = GDK_NONE;
|
||||
gdk_wayland_selection_handle_next_request (selection);
|
||||
}
|
||||
|
||||
static SelectionBuffer *
|
||||
@ -818,6 +910,28 @@ gdk_wayland_selection_source_handles_target (GdkWaylandSelection *wayland_select
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gdk_wayland_selection_handle_next_request (GdkWaylandSelection *wayland_selection)
|
||||
{
|
||||
gint i;
|
||||
|
||||
for (i = 0; i < wayland_selection->stored_selections->len; i++)
|
||||
{
|
||||
StoredSelection *stored_selection;
|
||||
|
||||
stored_selection = g_ptr_array_index (wayland_selection->stored_selections, i);
|
||||
|
||||
if (!stored_selection->data)
|
||||
{
|
||||
gdk_wayland_selection_emit_request (stored_selection->source,
|
||||
stored_selection->selection_atom,
|
||||
stored_selection->type);
|
||||
wayland_selection->current_request_selection = stored_selection->selection_atom;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gdk_wayland_selection_request_target (GdkWaylandSelection *wayland_selection,
|
||||
GdkWindow *window,
|
||||
@ -825,33 +939,41 @@ gdk_wayland_selection_request_target (GdkWaylandSelection *wayland_selection,
|
||||
GdkAtom target,
|
||||
gint fd)
|
||||
{
|
||||
if (wayland_selection->stored_selection.fd == fd &&
|
||||
wayland_selection->requested_target == target)
|
||||
return FALSE;
|
||||
StoredSelection *stored_selection;
|
||||
AsyncWriteData *write_data;
|
||||
|
||||
/* If we didn't issue gdk_wayland_selection_check_write() yet
|
||||
* on a previous fd, it will still linger here. Just close it,
|
||||
* as we can't have more than one fd on the fly.
|
||||
*/
|
||||
if (wayland_selection->stored_selection.fd >= 0)
|
||||
close (wayland_selection->stored_selection.fd);
|
||||
|
||||
wayland_selection->stored_selection.fd = fd;
|
||||
wayland_selection->requested_target = target;
|
||||
|
||||
if (window &&
|
||||
gdk_wayland_selection_source_handles_target (wayland_selection, target))
|
||||
{
|
||||
gdk_wayland_selection_emit_request (window, selection, target);
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
if (!window ||
|
||||
!gdk_wayland_selection_source_handles_target (wayland_selection, target))
|
||||
{
|
||||
close (fd);
|
||||
wayland_selection->stored_selection.fd = -1;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
stored_selection =
|
||||
gdk_wayland_selection_find_stored_selection (wayland_selection, window,
|
||||
selection, target);
|
||||
|
||||
if (stored_selection && stored_selection->data)
|
||||
{
|
||||
/* Fast path, we already have the type cached */
|
||||
write_data = async_write_data_new (stored_selection, fd);
|
||||
async_write_data_write (write_data);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!stored_selection)
|
||||
{
|
||||
stored_selection = stored_selection_new (wayland_selection, window,
|
||||
selection, target);
|
||||
g_ptr_array_add (wayland_selection->stored_selections, stored_selection);
|
||||
}
|
||||
|
||||
write_data = async_write_data_new (stored_selection, fd);
|
||||
|
||||
if (wayland_selection->current_request_selection == GDK_NONE)
|
||||
gdk_wayland_selection_handle_next_request (wayland_selection);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -903,11 +1025,10 @@ data_source_send (void *data,
|
||||
if (!window)
|
||||
return;
|
||||
|
||||
if (!gdk_wayland_selection_request_target (wayland_selection, window,
|
||||
selection,
|
||||
gdk_atom_intern (mime_type, FALSE),
|
||||
fd))
|
||||
gdk_wayland_selection_check_write (wayland_selection);
|
||||
gdk_wayland_selection_request_target (wayland_selection, window,
|
||||
selection,
|
||||
gdk_atom_intern (mime_type, FALSE),
|
||||
fd);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1026,12 +1147,11 @@ primary_source_send (void *data,
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gdk_wayland_selection_request_target (wayland_selection,
|
||||
wayland_selection->primary_owner,
|
||||
atoms[ATOM_PRIMARY],
|
||||
gdk_atom_intern (mime_type, FALSE),
|
||||
fd))
|
||||
gdk_wayland_selection_check_write (wayland_selection);
|
||||
gdk_wayland_selection_request_target (wayland_selection,
|
||||
wayland_selection->primary_owner,
|
||||
atoms[ATOM_PRIMARY],
|
||||
gdk_atom_intern (mime_type, FALSE),
|
||||
fd);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1186,6 +1306,8 @@ _gdk_wayland_display_set_selection_owner (GdkDisplay *display,
|
||||
GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display);
|
||||
GdkSeat *seat = gdk_display_get_default_seat (display);
|
||||
|
||||
gdk_wayland_selection_reset_selection (wayland_selection, selection);
|
||||
|
||||
if (selection == atoms[ATOM_CLIPBOARD])
|
||||
{
|
||||
wayland_selection->clipboard_owner = owner;
|
||||
|
||||
Reference in New Issue
Block a user