[gtk+/wayland-selections: 6/15] wayland: Implement drop/destination side of selections
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/wayland-selections: 6/15] wayland: Implement drop/destination side of selections
- Date: Thu, 28 Aug 2014 19:23:47 +0000 (UTC)
commit 3fe12c890406e7eff368c23ca18aa8048bf9c5c3
Author: Carlos Garnacho <carlosg gnome org>
Date: Thu Aug 21 18:34:01 2014 +0200
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 | 442 +++++++++++++++++++++++++++++++++++-
4 files changed, 469 insertions(+), 1 deletions(-)
---
diff --git a/gdk/wayland/gdkdisplay-wayland.c b/gdk/wayland/gdkdisplay-wayland.c
index 0cc7eaa..ea701c8 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 8393b23..18f38df 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 e56592e..c04d257 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 6b1b44f..e8419bb 100644
--- a/gdk/wayland/gdkselection-wayland.c
+++ b/gdk/wayland/gdkselection-wayland.c
@@ -17,16 +17,348 @@
#include "config.h"
+#include <fcntl.h>
+
+#include <gio/gunixinputstream.h>
+#include <glib-unix.h>
+
+#include "gdkwayland.h"
+#include "gdkprivate-wayland.h"
+#include "gdkdisplay-wayland.h"
#include "gdkselection.h"
#include "gdkproperty.h"
#include "gdkprivate.h"
#include <string.h>
+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)
{
+ /* On wayland we can't sanely know who's the global selection owner */
return NULL;
}
@@ -57,7 +389,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 +434,79 @@ _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;
+ }
+
+ 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
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]