Merge branch 'wip/carlosg/simultaneous-clipboard-access-3.24' into 'gtk-3-24'
gdk/wayland: Handle simultaneous selection requests See merge request GNOME/gtk!1438
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