From 3b953041a9043d12238d8ca1b2d06a2ade702b1e Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Thu, 21 Aug 2014 18:34:01 +0200 Subject: [PATCH] wayland: Implement drop/destination side of selections This implementation makes the destination side of selections work similarly to X11's, gdk_selection_convert() triggers data fetching, which is notified through GDK_SELECTION_NOTIFY events on arrival, the buffered data is then available through gdk_selection_property_get(). https://bugzilla.gnome.org/show_bug.cgi?id=697855 --- gdk/wayland/gdkdisplay-wayland.c | 16 + gdk/wayland/gdkdisplay-wayland.h | 4 + gdk/wayland/gdkprivate-wayland.h | 8 + gdk/wayland/gdkselection-wayland.c | 452 ++++++++++++++++++++++++++++- 4 files changed, 479 insertions(+), 1 deletion(-) diff --git a/gdk/wayland/gdkdisplay-wayland.c b/gdk/wayland/gdkdisplay-wayland.c index 0cc7eaa75f..ea701c852b 100644 --- a/gdk/wayland/gdkdisplay-wayland.c +++ b/gdk/wayland/gdkdisplay-wayland.c @@ -239,6 +239,8 @@ _gdk_wayland_display_open (const gchar *display_name) gdk_input_init (display); + display_wayland->selection = gdk_wayland_selection_new (); + g_signal_emit_by_name (display, "opened"); return display; @@ -261,6 +263,12 @@ gdk_wayland_display_dispose (GObject *object) display_wayland->event_source = NULL; } + if (display_wayland->selection) + { + gdk_wayland_selection_free (display_wayland->selection); + display_wayland->selection = NULL; + } + G_OBJECT_CLASS (gdk_wayland_display_parent_class)->dispose (object); } @@ -894,3 +902,11 @@ _gdk_wayland_shm_surface_get_busy (cairo_surface_t *surface) GdkWaylandCairoSurfaceData *data = cairo_surface_get_user_data (surface, &gdk_wayland_cairo_key); return data->busy; } + +GdkWaylandSelection * +gdk_wayland_display_get_selection (GdkDisplay *display) +{ + GdkWaylandDisplay *wayland_display = GDK_WAYLAND_DISPLAY (display); + + return wayland_display->selection; +} diff --git a/gdk/wayland/gdkdisplay-wayland.h b/gdk/wayland/gdkdisplay-wayland.h index 8393b231bc..18f38df090 100644 --- a/gdk/wayland/gdkdisplay-wayland.h +++ b/gdk/wayland/gdkdisplay-wayland.h @@ -39,6 +39,8 @@ G_BEGIN_DECLS +typedef struct _GdkWaylandSelection GdkWaylandSelection; + struct _GdkWaylandDisplay { GdkDisplay parent_instance; @@ -72,6 +74,8 @@ struct _GdkWaylandDisplay int compositor_version; struct xkb_context *xkb_context; + + GdkWaylandSelection *selection; }; struct _GdkWaylandDisplayClass diff --git a/gdk/wayland/gdkprivate-wayland.h b/gdk/wayland/gdkprivate-wayland.h index e56592eb8d..c04d2579f1 100644 --- a/gdk/wayland/gdkprivate-wayland.h +++ b/gdk/wayland/gdkprivate-wayland.h @@ -193,4 +193,12 @@ struct wl_buffer *_gdk_wayland_shm_surface_get_wl_buffer (cairo_surface_t *surfa void _gdk_wayland_shm_surface_set_busy (cairo_surface_t *surface); gboolean _gdk_wayland_shm_surface_get_busy (cairo_surface_t *surface); +GdkWaylandSelection * gdk_wayland_display_get_selection (GdkDisplay *display); +GdkWaylandSelection * gdk_wayland_selection_new (void); +void gdk_wayland_selection_free (GdkWaylandSelection *selection); + +void gdk_wayland_selection_set_offer (struct wl_data_offer *offer); +struct wl_data_offer * gdk_wayland_selection_get_offer (void); +GList * gdk_wayland_selection_get_targets (void); + #endif /* __GDK_PRIVATE_WAYLAND_H__ */ diff --git a/gdk/wayland/gdkselection-wayland.c b/gdk/wayland/gdkselection-wayland.c index 44cbee6cde..b3383305e5 100644 --- a/gdk/wayland/gdkselection-wayland.c +++ b/gdk/wayland/gdkselection-wayland.c @@ -17,16 +17,354 @@ #include "config.h" +#include + +#include +#include + +#include "gdkwayland.h" +#include "gdkprivate-wayland.h" +#include "gdkdisplay-wayland.h" #include "gdkselection.h" #include "gdkproperty.h" #include "gdkprivate.h" #include +typedef struct _SelectionBuffer SelectionBuffer; +typedef struct _StoredSelection StoredSelection; + +struct _SelectionBuffer +{ + GInputStream *stream; + GCancellable *cancellable; + GByteArray *data; + GList *requestors; + GdkAtom selection; + GdkAtom target; + gint ref_count; +}; + +struct _StoredSelection +{ + GdkWindow *source; + guchar *data; + gsize data_len; + GdkAtom type; + gint fd; +}; + +struct _DataSourceData +{ + GdkWindow *window; + GdkAtom selection; +}; + +struct _GdkWaylandSelection +{ + /* Destination-side data */ + struct wl_data_offer *offer; + GdkAtom source_requested_target; + + GHashTable *selection_buffers; /* Hashtable of target_atom->SelectionBuffer */ + GList *targets; /* List of GdkAtom */ + + /* Source-side data */ + StoredSelection stored_selection; + + struct wl_data_source *clipboard_source; + GdkWindow *clipboard_owner; + + struct wl_data_source *dnd_source; /* Owned by the GdkDragContext */ + GdkWindow *dnd_owner; +}; + +static void selection_buffer_read (SelectionBuffer *buffer); + +static void +selection_buffer_notify (SelectionBuffer *buffer) +{ + GdkEvent *event; + GList *l; + + for (l = buffer->requestors; l; l = l->next) + { + event = gdk_event_new (GDK_SELECTION_NOTIFY); + event->selection.window = g_object_ref (l->data); + event->selection.send_event = FALSE; + event->selection.selection = buffer->selection; + event->selection.target = buffer->target; + event->selection.property = gdk_atom_intern_static_string ("GDK_SELECTION"); + event->selection.time = GDK_CURRENT_TIME; + event->selection.requestor = g_object_ref (l->data); + + gdk_event_put (event); + gdk_event_free (event); + } +} + +static SelectionBuffer * +selection_buffer_new (GInputStream *stream, + GdkAtom selection, + GdkAtom target) +{ + SelectionBuffer *buffer; + + buffer = g_new0 (SelectionBuffer, 1); + buffer->stream = (stream) ? g_object_ref (stream) : NULL; + buffer->cancellable = g_cancellable_new (); + buffer->data = g_byte_array_new (); + buffer->selection = selection; + buffer->target = target; + buffer->ref_count = 1; + + if (stream) + selection_buffer_read (buffer); + + return buffer; +} + +static SelectionBuffer * +selection_buffer_ref (SelectionBuffer *buffer) +{ + buffer->ref_count++; + return buffer; +} + +static void +selection_buffer_unref (SelectionBuffer *buffer_data) +{ + buffer_data->ref_count--; + + if (buffer_data->ref_count != 0) + return; + + if (buffer_data->cancellable) + g_object_unref (buffer_data->cancellable); + + if (buffer_data->stream) + g_object_unref (buffer_data->stream); + + if (buffer_data->data) + g_byte_array_unref (buffer_data->data); + + g_free (buffer_data); +} + +static void +selection_buffer_append_data (SelectionBuffer *buffer, + gconstpointer data, + gsize len) +{ + g_byte_array_append (buffer->data, data, len); +} + +static void +selection_buffer_cancel_and_unref (SelectionBuffer *buffer_data) +{ + if (buffer_data->cancellable) + g_cancellable_cancel (buffer_data->cancellable); + + selection_buffer_unref (buffer_data); +} + +static void +selection_buffer_add_requestor (SelectionBuffer *buffer, + GdkWindow *requestor) +{ + if (!g_list_find (buffer->requestors, requestor)) + buffer->requestors = g_list_prepend (buffer->requestors, + g_object_ref (requestor)); +} + +static gboolean +selection_buffer_remove_requestor (SelectionBuffer *buffer, + GdkWindow *requestor) +{ + GList *link = g_list_find (buffer->requestors, requestor); + + if (!link) + return FALSE; + + g_object_unref (link->data); + buffer->requestors = g_list_delete_link (buffer->requestors, link); + return TRUE; +} + +static void +selection_buffer_read_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SelectionBuffer *buffer = user_data; + GError *error = NULL; + GBytes *bytes; + + bytes = g_input_stream_read_bytes_finish (buffer->stream, result, &error); + + if (bytes && g_bytes_get_size (bytes) > 0) + { + selection_buffer_append_data (buffer, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes)); + selection_buffer_read (buffer); + g_bytes_unref (bytes); + } + else + { + if (error) + { + g_warning (G_STRLOC ": error reading selection buffer: %s\n", error->message); + g_error_free (error); + } + else + selection_buffer_notify (buffer); + + g_input_stream_close (buffer->stream, NULL, NULL); + g_clear_object (&buffer->stream); + g_clear_object (&buffer->cancellable); + + if (bytes) + g_bytes_unref (bytes); + } + + selection_buffer_unref (buffer); +} + +static void +selection_buffer_read (SelectionBuffer *buffer) +{ + selection_buffer_ref (buffer); + g_input_stream_read_bytes_async (buffer->stream, 1000, G_PRIORITY_DEFAULT, + buffer->cancellable, selection_buffer_read_cb, + buffer); +} + +GdkWaylandSelection * +gdk_wayland_selection_new (void) +{ + GdkWaylandSelection *selection; + + selection = g_new0 (GdkWaylandSelection, 1); + selection->selection_buffers = g_hash_table_new_full (NULL, NULL, NULL, + (GDestroyNotify) selection_buffer_cancel_and_unref); + return selection; +} + +void +gdk_wayland_selection_free (GdkWaylandSelection *selection) +{ + g_hash_table_destroy (selection->selection_buffers); + + if (selection->targets) + g_list_free (selection->targets); + + g_free (selection->stored_selection.data); + + if (selection->stored_selection.fd > 0) + close (selection->stored_selection.fd); + + if (selection->offer) + wl_data_offer_destroy (selection->offer); + if (selection->clipboard_source) + wl_data_source_destroy (selection->clipboard_source); + if (selection->dnd_source) + wl_data_source_destroy (selection->dnd_source); + + g_free (selection); +} + +static void +data_offer_offer (void *data, + struct wl_data_offer *wl_data_offer, + const char *type) +{ + GdkWaylandSelection *selection = data; + GdkAtom atom = gdk_atom_intern (type, FALSE); + + if (g_list_find (selection->targets, atom)) + return; + + selection->targets = g_list_prepend (selection->targets, atom); +} + +static const struct wl_data_offer_listener data_offer_listener = { + data_offer_offer, +}; + +void +gdk_wayland_selection_set_offer (struct wl_data_offer *wl_offer) +{ + GdkDisplay *display = gdk_display_get_default (); + GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display); + + if (selection->offer == wl_offer) + return; + + if (selection->offer) + wl_data_offer_destroy (selection->offer); + + selection->offer = wl_offer; + + if (wl_offer) + wl_data_offer_add_listener (wl_offer, + &data_offer_listener, + selection); + + /* Clear all buffers */ + g_hash_table_remove_all (selection->selection_buffers); + g_list_free (selection->targets); + selection->targets = NULL; +} + +struct wl_data_offer * +gdk_wayland_selection_get_offer (void) +{ + GdkDisplay *display = gdk_display_get_default (); + GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display); + + return selection->offer; +} + +GList * +gdk_wayland_selection_get_targets (void) +{ + GdkDisplay *display = gdk_display_get_default (); + GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display); + + return selection->targets; +} + +static SelectionBuffer * +gdk_wayland_selection_lookup_requestor_buffer (GdkWindow *requestor) +{ + GdkDisplay *display = gdk_display_get_default (); + GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display); + SelectionBuffer *buffer_data; + GHashTableIter iter; + + g_hash_table_iter_init (&iter, selection->selection_buffers); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &buffer_data)) + { + if (g_list_find (buffer_data->requestors, requestor)) + return buffer_data; + } + + return NULL; +} + GdkWindow * _gdk_wayland_display_get_selection_owner (GdkDisplay *display, GdkAtom selection) { + GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display); + + if (selection == atoms[ATOM_CLIPBOARD]) + return wayland_selection->clipboard_owner; + else if (selection == atoms[ATOM_DND]) + return wayland_selection->dnd_owner; + return NULL; } @@ -57,7 +395,42 @@ _gdk_wayland_display_get_selection_property (GdkDisplay *display, GdkAtom *ret_type, gint *ret_format) { - return 0; + SelectionBuffer *buffer_data; + gsize len; + + buffer_data = gdk_wayland_selection_lookup_requestor_buffer (requestor); + + if (!buffer_data) + return 0; + + selection_buffer_remove_requestor (buffer_data, requestor); + len = buffer_data->data->len; + + if (data) + { + guchar *buffer; + + buffer = g_new0 (guchar, len + 1); + memcpy (buffer, buffer_data->data->data, len); + *data = buffer; + } + + if (buffer_data->target == gdk_atom_intern_static_string ("TARGETS")) + { + if (ret_type) + *ret_type = GDK_SELECTION_TYPE_ATOM; + if (ret_format) + *ret_format = 32; + } + else + { + if (ret_type) + *ret_type = GDK_SELECTION_TYPE_STRING; + if (ret_format) + *ret_format = 8; + } + + return len; } void @@ -67,6 +440,83 @@ _gdk_wayland_display_convert_selection (GdkDisplay *display, GdkAtom target, guint32 time) { + GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display); + SelectionBuffer *buffer_data; + + if (!wayland_selection->offer) + { + GdkEvent *event; + + event = gdk_event_new (GDK_SELECTION_NOTIFY); + event->selection.window = g_object_ref (requestor); + event->selection.send_event = FALSE; + event->selection.selection = selection; + event->selection.target = target; + event->selection.property = GDK_NONE; + event->selection.time = GDK_CURRENT_TIME; + event->selection.requestor = g_object_ref (requestor); + + gdk_event_put (event); + gdk_event_free (event); + return; + } + + wl_data_offer_accept (wayland_selection->offer, + _gdk_wayland_display_get_serial (GDK_WAYLAND_DISPLAY (display)), + gdk_atom_name (target)); + + buffer_data = g_hash_table_lookup (wayland_selection->selection_buffers, + target); + + if (buffer_data) + selection_buffer_add_requestor (buffer_data, requestor); + else + { + GInputStream *stream = NULL; + int pipe_fd[2], natoms = 0; + GdkAtom *atoms = NULL; + + if (target == gdk_atom_intern_static_string ("TARGETS")) + { + gint i = 0; + GList *l; + + natoms = g_list_length (wayland_selection->targets); + atoms = g_new0 (GdkAtom, natoms); + + for (l = wayland_selection->targets; l; l = l->next) + atoms[i++] = l->data; + } + else + { + g_unix_open_pipe (pipe_fd, FD_CLOEXEC, NULL); + wl_data_offer_receive (wayland_selection->offer, + gdk_atom_name (target), + pipe_fd[1]); + stream = g_unix_input_stream_new (pipe_fd[0], TRUE); + close (pipe_fd[1]); + } + + buffer_data = selection_buffer_new (stream, selection, target); + selection_buffer_add_requestor (buffer_data, requestor); + + if (stream) + g_object_unref (stream); + + if (atoms) + { + /* Store directly the local atoms */ + selection_buffer_append_data (buffer_data, atoms, natoms * sizeof (GdkAtom)); + g_free (atoms); + } + + g_hash_table_insert (wayland_selection->selection_buffers, + GDK_ATOM_TO_POINTER (target), + buffer_data); + } + + if (!buffer_data->stream) + selection_buffer_notify (buffer_data); } gint