mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-25 13:11:13 +00:00
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:
parent
32bf03c053
commit
3b953041a9
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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__ */
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user