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
This commit is contained in:
Carlos Garnacho 2014-08-21 18:34:01 +02:00
parent 32bf03c053
commit 3b953041a9
4 changed files with 479 additions and 1 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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__ */

View File

@ -17,16 +17,354 @@
#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)
{
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