wayland: Split handling of clipboard/DnD data offers

We currently only hold the last offer received, which is wrong, as both
are independent and have different life cycles.

This means we have to store per-selection wl_data_offer and targets, and
maintain these as appropriate from the clipboard/DnD specific entry points.
This commit is contained in:
Carlos Garnacho 2015-04-07 16:40:57 +02:00
parent da395606a2
commit 1178a0ac8b
4 changed files with 148 additions and 56 deletions

View File

@ -688,8 +688,7 @@ data_device_data_offer (void *data,
g_message ("data device data offer, data device %p, offer %p", g_message ("data device data offer, data device %p, offer %p",
data_device, offer)); data_device, offer));
gdk_wayland_selection_set_offer (device->display, offer); gdk_wayland_selection_ensure_offer (device->display, offer);
emit_selection_owner_change_forall (gdk_atom_intern_static_string ("GdkWaylandSelection"));
} }
static void static void
@ -703,6 +702,7 @@ data_device_enter (void *data,
{ {
GdkWaylandDeviceData *device = (GdkWaylandDeviceData *)data; GdkWaylandDeviceData *device = (GdkWaylandDeviceData *)data;
GdkWindow *dest_window, *dnd_owner; GdkWindow *dest_window, *dnd_owner;
GdkAtom selection;
dest_window = wl_surface_get_user_data (surface); dest_window = wl_surface_get_user_data (surface);
@ -720,7 +720,8 @@ data_device_enter (void *data,
gdk_wayland_drop_context_update_targets (device->drop_context); gdk_wayland_drop_context_update_targets (device->drop_context);
dnd_owner = gdk_selection_owner_get_for_display (device->display, gdk_drag_get_selection (device->drop_context)); selection = gdk_drag_get_selection (device->drop_context);
dnd_owner = gdk_selection_owner_get_for_display (device->display, selection);
if (!dnd_owner) if (!dnd_owner)
dnd_owner = device->foreign_dnd_window; dnd_owner = device->foreign_dnd_window;
@ -734,9 +735,9 @@ data_device_enter (void *data,
wl_fixed_to_double (y)); wl_fixed_to_double (y));
_gdk_wayland_drag_context_emit_event (device->drop_context, GDK_DRAG_ENTER, _gdk_wayland_drag_context_emit_event (device->drop_context, GDK_DRAG_ENTER,
GDK_CURRENT_TIME); GDK_CURRENT_TIME);
gdk_wayland_selection_set_offer (device->display, offer);
emit_selection_owner_change (dest_window, gdk_wayland_selection_set_offer (device->display, selection, offer);
gdk_atom_intern_static_string ("GdkWaylandSelection")); emit_selection_owner_change_forall (selection);
} }
static void static void
@ -811,13 +812,15 @@ data_device_selection (void *data,
struct wl_data_offer *offer) struct wl_data_offer *offer)
{ {
GdkWaylandDeviceData *device = (GdkWaylandDeviceData *) data; GdkWaylandDeviceData *device = (GdkWaylandDeviceData *) data;
GdkAtom selection;
GDK_NOTE (EVENTS, GDK_NOTE (EVENTS,
g_message ("data device selection, data device %p, data offer %p", g_message ("data device selection, data device %p, data offer %p",
wl_data_device, offer)); wl_data_device, offer));
gdk_wayland_selection_set_offer (device->display, offer); selection = gdk_atom_intern_static_string ("CLIPBOARD");
emit_selection_owner_change_forall (gdk_atom_intern_static_string ("CLIPBOARD")); gdk_wayland_selection_set_offer (device->display, selection, offer);
emit_selection_owner_change_forall (selection);
} }
static const struct wl_data_device_listener data_device_listener = { static const struct wl_data_device_listener data_device_listener = {

View File

@ -43,7 +43,6 @@ struct _GdkWaylandDragContext
GdkWindow *dnd_window; GdkWindow *dnd_window;
struct wl_surface *dnd_surface; struct wl_surface *dnd_surface;
struct wl_data_source *data_source; struct wl_data_source *data_source;
struct wl_data_offer *offer;
uint32_t serial; uint32_t serial;
gdouble x; gdouble x;
gdouble y; gdouble y;
@ -196,8 +195,10 @@ gdk_wayland_drop_context_set_status (GdkDragContext *context,
struct wl_data_offer *wl_offer; struct wl_data_offer *wl_offer;
context_wayland = GDK_WAYLAND_DRAG_CONTEXT (context); context_wayland = GDK_WAYLAND_DRAG_CONTEXT (context);
display = gdk_window_get_display (context->source_window);
wl_offer = gdk_wayland_selection_get_offer (display); display = gdk_device_get_display (gdk_drag_context_get_device (context));
wl_offer = gdk_wayland_selection_get_offer (display,
gdk_drag_get_selection (context));
if (!wl_offer) if (!wl_offer)
return; return;
@ -247,10 +248,15 @@ gdk_wayland_drag_context_drop_finish (GdkDragContext *context,
gboolean success, gboolean success,
guint32 time) guint32 time)
{ {
GdkDisplay *display = gdk_window_get_display (context->source_window); GdkDisplay *display = gdk_device_get_display (gdk_drag_context_get_device (context));
GdkAtom selection;
if (gdk_selection_owner_get_for_display (display, gdk_drag_get_selection (context))) selection = gdk_drag_get_selection (context);
gdk_wayland_selection_unset_data_source (display, gdk_drag_get_selection (context));
if (gdk_selection_owner_get_for_display (display, selection))
gdk_wayland_selection_unset_data_source (display, selection);
gdk_wayland_selection_set_offer (display, selection, NULL);
} }
static gboolean static gboolean
@ -396,7 +402,8 @@ gdk_wayland_drop_context_update_targets (GdkDragContext *context)
device = gdk_drag_context_get_device (context); device = gdk_drag_context_get_device (context);
display = gdk_device_get_display (device); display = gdk_device_get_display (device);
g_list_free (context->targets); g_list_free (context->targets);
context->targets = g_list_copy (gdk_wayland_selection_get_targets (display)); context->targets = g_list_copy (gdk_wayland_selection_get_targets (display,
gdk_drag_get_selection (context)));
} }
void void

View File

@ -237,10 +237,15 @@ GdkWaylandSelection * gdk_wayland_display_get_selection (GdkDisplay *display);
GdkWaylandSelection * gdk_wayland_selection_new (void); GdkWaylandSelection * gdk_wayland_selection_new (void);
void gdk_wayland_selection_free (GdkWaylandSelection *selection); void gdk_wayland_selection_free (GdkWaylandSelection *selection);
void gdk_wayland_selection_ensure_offer (GdkDisplay *display,
struct wl_data_offer *wl_offer);
void gdk_wayland_selection_set_offer (GdkDisplay *display, void gdk_wayland_selection_set_offer (GdkDisplay *display,
GdkAtom selection,
struct wl_data_offer *wl_offer); struct wl_data_offer *wl_offer);
struct wl_data_offer * gdk_wayland_selection_get_offer (GdkDisplay *display); struct wl_data_offer * gdk_wayland_selection_get_offer (GdkDisplay *display,
GList * gdk_wayland_selection_get_targets (GdkDisplay *display); GdkAtom selection);
GList * gdk_wayland_selection_get_targets (GdkDisplay *display,
GdkAtom selection);
void gdk_wayland_selection_store (GdkWindow *window, void gdk_wayland_selection_store (GdkWindow *window,
GdkAtom type, GdkAtom type,

View File

@ -36,6 +36,7 @@
typedef struct _SelectionBuffer SelectionBuffer; typedef struct _SelectionBuffer SelectionBuffer;
typedef struct _StoredSelection StoredSelection; typedef struct _StoredSelection StoredSelection;
typedef struct _AsyncWriteData AsyncWriteData; typedef struct _AsyncWriteData AsyncWriteData;
typedef struct _DataOfferData DataOfferData;
struct _SelectionBuffer struct _SelectionBuffer
{ {
@ -64,6 +65,13 @@ struct _DataSourceData
GdkAtom selection; GdkAtom selection;
}; };
struct _DataOfferData
{
struct wl_data_offer *offer;
GList *targets; /* List of GdkAtom */
GdkAtom requested_target;
};
struct _AsyncWriteData struct _AsyncWriteData
{ {
GOutputStream *stream; GOutputStream *stream;
@ -81,11 +89,10 @@ static GdkAtom atoms[2] = { 0 };
struct _GdkWaylandSelection struct _GdkWaylandSelection
{ {
/* Destination-side data */ /* Destination-side data */
struct wl_data_offer *offer; DataOfferData *dnd_offer;
GdkAtom source_requested_target; DataOfferData *clipboard_offer;
GHashTable *offers; /* Currently alive offers, Hashtable of wl_data_offer->DataOfferData */
GHashTable *selection_buffers; /* Hashtable of target_atom->SelectionBuffer */ GHashTable *selection_buffers; /* Hashtable of target_atom->SelectionBuffer */
GList *targets; /* List of GdkAtom */
/* Source-side data */ /* Source-side data */
StoredSelection stored_selection; StoredSelection stored_selection;
@ -260,6 +267,25 @@ selection_buffer_read (SelectionBuffer *buffer)
buffer); buffer);
} }
static DataOfferData *
data_offer_data_new (struct wl_data_offer *offer)
{
DataOfferData *info;
info = g_slice_new0 (DataOfferData);
info->offer = offer;
return info;
}
static void
data_offer_data_free (DataOfferData *info)
{
wl_data_offer_destroy (info->offer);
g_list_free (info->targets);
g_slice_free (DataOfferData, info);
}
GdkWaylandSelection * GdkWaylandSelection *
gdk_wayland_selection_new (void) gdk_wayland_selection_new (void)
{ {
@ -271,8 +297,11 @@ gdk_wayland_selection_new (void)
selection = g_new0 (GdkWaylandSelection, 1); selection = g_new0 (GdkWaylandSelection, 1);
selection->selection_buffers = selection->selection_buffers =
g_hash_table_new_full (NULL, NULL, NULL, g_hash_table_new_full (NULL, NULL, NULL,
(GDestroyNotify) selection_buffer_cancel_and_unref); (GDestroyNotify) selection_buffer_cancel_and_unref);
selection->offers =
g_hash_table_new_full (NULL, NULL, NULL,
(GDestroyNotify) data_offer_data_free);
selection->stored_selection.fd = -1; selection->stored_selection.fd = -1;
selection->source_targets = g_array_new (FALSE, FALSE, sizeof (GdkAtom)); selection->source_targets = g_array_new (FALSE, FALSE, sizeof (GdkAtom));
return selection; return selection;
@ -284,9 +313,7 @@ gdk_wayland_selection_free (GdkWaylandSelection *selection)
g_hash_table_destroy (selection->selection_buffers); g_hash_table_destroy (selection->selection_buffers);
g_array_unref (selection->source_targets); g_array_unref (selection->source_targets);
if (selection->targets) g_hash_table_destroy (selection->offers);
g_list_free (selection->targets);
g_free (selection->stored_selection.data); g_free (selection->stored_selection.data);
if (selection->stored_selection.cancellable) if (selection->stored_selection.cancellable)
@ -298,8 +325,6 @@ gdk_wayland_selection_free (GdkWaylandSelection *selection)
if (selection->stored_selection.fd > 0) if (selection->stored_selection.fd > 0)
close (selection->stored_selection.fd); close (selection->stored_selection.fd);
if (selection->offer)
wl_data_offer_destroy (selection->offer);
if (selection->clipboard_source) if (selection->clipboard_source)
wl_data_source_destroy (selection->clipboard_source); wl_data_source_destroy (selection->clipboard_source);
if (selection->dnd_source) if (selection->dnd_source)
@ -314,57 +339,105 @@ data_offer_offer (void *data,
const char *type) const char *type)
{ {
GdkWaylandSelection *selection = data; GdkWaylandSelection *selection = data;
DataOfferData *info;
GdkAtom atom = gdk_atom_intern (type, FALSE); GdkAtom atom = gdk_atom_intern (type, FALSE);
if (g_list_find (selection->targets, atom)) info = g_hash_table_lookup (selection->offers, wl_data_offer);
if (!info || g_list_find (info->targets, atom))
return; return;
selection->targets = g_list_prepend (selection->targets, atom); info->targets = g_list_prepend (info->targets, atom);
} }
static const struct wl_data_offer_listener data_offer_listener = { static const struct wl_data_offer_listener data_offer_listener = {
data_offer_offer, data_offer_offer,
}; };
DataOfferData *
selection_lookup_offer_by_atom (GdkWaylandSelection *selection,
GdkAtom selection_atom)
{
if (selection_atom == atoms[ATOM_CLIPBOARD])
return selection->clipboard_offer;
else if (selection_atom == atoms[ATOM_DND])
return selection->dnd_offer;
else
return NULL;
}
void
gdk_wayland_selection_ensure_offer (GdkDisplay *display,
struct wl_data_offer *wl_offer)
{
GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
DataOfferData *info;
info = g_hash_table_lookup (selection->offers, wl_offer);
if (!info)
{
info = data_offer_data_new (wl_offer);
g_hash_table_insert (selection->offers, wl_offer, info);
wl_data_offer_add_listener (wl_offer,
&data_offer_listener,
selection);
}
}
void void
gdk_wayland_selection_set_offer (GdkDisplay *display, gdk_wayland_selection_set_offer (GdkDisplay *display,
GdkAtom selection_atom,
struct wl_data_offer *wl_offer) struct wl_data_offer *wl_offer)
{ {
GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display); GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
struct wl_data_offer *prev_offer;
DataOfferData *info;
if (selection->offer == wl_offer) info = g_hash_table_lookup (selection->offers, wl_offer);
return;
if (selection->offer) prev_offer = gdk_wayland_selection_get_offer (display, selection_atom);
wl_data_offer_destroy (selection->offer);
selection->offer = wl_offer; if (prev_offer)
g_hash_table_remove (selection->offers, prev_offer);
if (wl_offer) if (selection_atom == atoms[ATOM_CLIPBOARD])
wl_data_offer_add_listener (wl_offer, selection->clipboard_offer = info;
&data_offer_listener, else if (selection_atom == atoms[ATOM_DND])
selection); selection->dnd_offer = info;
/* Clear all buffers */ /* Clear all buffers */
g_hash_table_remove_all (selection->selection_buffers); g_hash_table_remove_all (selection->selection_buffers);
g_list_free (selection->targets);
selection->targets = NULL;
} }
struct wl_data_offer * struct wl_data_offer *
gdk_wayland_selection_get_offer (GdkDisplay *display) gdk_wayland_selection_get_offer (GdkDisplay *display,
GdkAtom selection_atom)
{ {
GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display); GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
const DataOfferData *info;
return selection->offer; info = selection_lookup_offer_by_atom (selection, selection_atom);
if (info)
return info->offer;
return NULL;
} }
GList * GList *
gdk_wayland_selection_get_targets (GdkDisplay *display) gdk_wayland_selection_get_targets (GdkDisplay *display,
GdkAtom selection_atom)
{ {
GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display); GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
const DataOfferData *info;
return selection->targets; info = selection_lookup_offer_by_atom (selection, selection_atom);
if (info)
return info->targets;
return NULL;
} }
static void static void
@ -584,6 +657,7 @@ gdk_wayland_selection_request_target (GdkWaylandSelection *wayland_selection,
GdkAtom target, GdkAtom target,
gint fd) gint fd)
{ {
DataOfferData *offer;
GdkAtom selection; GdkAtom selection;
if (wayland_selection->clipboard_owner == window) if (wayland_selection->clipboard_owner == window)
@ -593,8 +667,10 @@ gdk_wayland_selection_request_target (GdkWaylandSelection *wayland_selection,
else else
return FALSE; return FALSE;
offer = selection_lookup_offer_by_atom (wayland_selection, selection);
if (wayland_selection->stored_selection.fd == fd && if (wayland_selection->stored_selection.fd == fd &&
wayland_selection->source_requested_target == target) offer->requested_target == target)
return FALSE; return FALSE;
/* If we didn't issue gdk_wayland_selection_check_write() yet /* If we didn't issue gdk_wayland_selection_check_write() yet
@ -605,8 +681,7 @@ gdk_wayland_selection_request_target (GdkWaylandSelection *wayland_selection,
close (wayland_selection->stored_selection.fd); close (wayland_selection->stored_selection.fd);
wayland_selection->stored_selection.fd = fd; wayland_selection->stored_selection.fd = fd;
offer->requested_target = target;
wayland_selection->source_requested_target = target;
if (window && if (window &&
gdk_wayland_selection_source_handles_target (wayland_selection, target)) gdk_wayland_selection_source_handles_target (wayland_selection, target))
@ -707,8 +782,6 @@ data_source_send (void *data,
_gdk_wayland_drag_context_emit_event (context, GDK_DROP_FINISHED, _gdk_wayland_drag_context_emit_event (context, GDK_DROP_FINISHED,
GDK_CURRENT_TIME); GDK_CURRENT_TIME);
} }
wayland_selection->source_requested_target = GDK_NONE;
} }
static void static void
@ -931,9 +1004,14 @@ _gdk_wayland_display_convert_selection (GdkDisplay *display,
{ {
GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display); GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display);
SelectionBuffer *buffer_data; SelectionBuffer *buffer_data;
struct wl_data_offer *offer;
gchar *mimetype; gchar *mimetype;
GList *target_list;
if (!wayland_selection->offer) offer = gdk_wayland_selection_get_offer (display, selection);
target_list = gdk_wayland_selection_get_targets (display, selection);
if (!offer)
{ {
GdkEvent *event; GdkEvent *event;
@ -954,7 +1032,7 @@ _gdk_wayland_display_convert_selection (GdkDisplay *display,
mimetype = gdk_atom_name (target); mimetype = gdk_atom_name (target);
if (target != gdk_atom_intern_static_string ("TARGETS")) if (target != gdk_atom_intern_static_string ("TARGETS"))
wl_data_offer_accept (wayland_selection->offer, wl_data_offer_accept (offer,
_gdk_wayland_display_get_serial (GDK_WAYLAND_DISPLAY (display)), _gdk_wayland_display_get_serial (GDK_WAYLAND_DISPLAY (display)),
mimetype); mimetype);
@ -974,17 +1052,16 @@ _gdk_wayland_display_convert_selection (GdkDisplay *display,
gint i = 0; gint i = 0;
GList *l; GList *l;
natoms = g_list_length (wayland_selection->targets); natoms = g_list_length (target_list);
targets = g_new0 (GdkAtom, natoms); targets = g_new0 (GdkAtom, natoms);
for (l = wayland_selection->targets; l; l = l->next) for (l = target_list; l; l = l->next)
targets[i++] = l->data; targets[i++] = l->data;
} }
else else
{ {
g_unix_open_pipe (pipe_fd, FD_CLOEXEC, NULL); g_unix_open_pipe (pipe_fd, FD_CLOEXEC, NULL);
wl_data_offer_receive (wayland_selection->offer, wl_data_offer_receive (offer, mimetype, pipe_fd[1]);
mimetype, pipe_fd[1]);
stream = g_unix_input_stream_new (pipe_fd[0], TRUE); stream = g_unix_input_stream_new (pipe_fd[0], TRUE);
close (pipe_fd[1]); close (pipe_fd[1]);
} }