forked from AuroraMiddleware/gtk
a097ca0906
If NULL is returned, probably the client shouldn't advertise the mimetype. Make it sure we forget entirely about the attempt to cache this mimetype, as it'll be mistaken as pending otherwise. Dropping this cached selection will in consequence close the fd of all pending readers, which seems appropriate for NULL content. https://gitlab.gnome.org/GNOME/gtk/issues/2456
1733 lines
50 KiB
C
1733 lines
50 KiB
C
/*
|
|
* Copyright © 2010 Intel Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include <gio/gunixinputstream.h>
|
|
#include <gio/gunixoutputstream.h>
|
|
#include <glib-unix.h>
|
|
|
|
#include "gdkwayland.h"
|
|
#include "gdkprivate-wayland.h"
|
|
#include "gdkdisplay-wayland.h"
|
|
#include "gdkdndprivate.h"
|
|
#include "gdkselection.h"
|
|
#include "gdkproperty.h"
|
|
#include "gdkprivate.h"
|
|
|
|
#include <string.h>
|
|
|
|
typedef struct _SelectionBuffer SelectionBuffer;
|
|
typedef struct _SelectionData SelectionData;
|
|
typedef struct _StoredSelection StoredSelection;
|
|
typedef struct _AsyncWriteData AsyncWriteData;
|
|
typedef struct _DataOfferData DataOfferData;
|
|
|
|
struct _SelectionBuffer
|
|
{
|
|
GInputStream *stream;
|
|
GCancellable *cancellable;
|
|
GByteArray *data;
|
|
GList *requestors;
|
|
GdkAtom selection;
|
|
GdkAtom target;
|
|
gint ref_count;
|
|
};
|
|
|
|
struct _StoredSelection
|
|
{
|
|
GdkWaylandSelection *selection;
|
|
GdkWindow *source;
|
|
GCancellable *cancellable;
|
|
guchar *data;
|
|
gsize data_len;
|
|
GdkAtom type;
|
|
GdkAtom selection_atom;
|
|
GPtrArray *pending_writes; /* Array of AsyncWriteData */
|
|
};
|
|
|
|
struct _DataOfferData
|
|
{
|
|
GDestroyNotify destroy_notify;
|
|
gpointer offer_data;
|
|
GList *targets; /* List of GdkAtom */
|
|
};
|
|
|
|
struct _AsyncWriteData
|
|
{
|
|
GOutputStream *stream;
|
|
StoredSelection *stored_selection;
|
|
gsize index;
|
|
};
|
|
|
|
struct _SelectionData
|
|
{
|
|
DataOfferData *offer;
|
|
GHashTable *buffers; /* Hashtable of target_atom->SelectionBuffer */
|
|
};
|
|
|
|
enum {
|
|
ATOM_PRIMARY,
|
|
ATOM_CLIPBOARD,
|
|
ATOM_DND,
|
|
N_ATOMS
|
|
};
|
|
|
|
static GdkAtom atoms[N_ATOMS] = { 0 };
|
|
|
|
struct _GdkWaylandSelection
|
|
{
|
|
/* Destination-side data */
|
|
SelectionData selections[N_ATOMS];
|
|
GHashTable *offers; /* Currently alive offers, Hashtable of wl_data_offer->DataOfferData */
|
|
|
|
/* Source-side data */
|
|
GPtrArray *stored_selections; /* Array of StoredSelection */
|
|
GdkAtom current_request_selection;
|
|
GArray *source_targets;
|
|
GdkAtom requested_target;
|
|
|
|
struct gtk_primary_selection_source *primary_source;
|
|
GdkWindow *primary_owner;
|
|
|
|
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 async_write_data_write (AsyncWriteData *write_data);
|
|
static void async_write_data_free (AsyncWriteData *write_data);
|
|
static void emit_selection_clear (GdkDisplay *display, GdkAtom selection);
|
|
static void emit_empty_selection_notify (GdkWindow *requestor,
|
|
GdkAtom selection,
|
|
GdkAtom target);
|
|
static void gdk_wayland_selection_handle_next_request (GdkWaylandSelection *wayland_selection);
|
|
|
|
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)
|
|
{
|
|
GList *l;
|
|
|
|
buffer_data->ref_count--;
|
|
|
|
if (buffer_data->ref_count != 0)
|
|
return;
|
|
|
|
for (l = buffer_data->requestors; l; l = l->next)
|
|
{
|
|
emit_empty_selection_notify (l->data, buffer_data->selection,
|
|
buffer_data->target);
|
|
}
|
|
|
|
g_list_free (buffer_data->requestors);
|
|
buffer_data->requestors = NULL;
|
|
|
|
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 inline glong
|
|
get_buffer_size (void)
|
|
{
|
|
return sysconf (_SC_PAGESIZE);
|
|
}
|
|
|
|
static void
|
|
selection_buffer_read_cb (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
SelectionBuffer *buffer = user_data;
|
|
gboolean finished = TRUE;
|
|
GError *error = NULL;
|
|
GBytes *bytes;
|
|
|
|
bytes = g_input_stream_read_bytes_finish (buffer->stream, result, &error);
|
|
|
|
if (bytes)
|
|
{
|
|
finished = g_bytes_get_size (bytes) == 0;
|
|
selection_buffer_append_data (buffer,
|
|
g_bytes_get_data (bytes, NULL),
|
|
g_bytes_get_size (bytes));
|
|
g_bytes_unref (bytes);
|
|
}
|
|
|
|
if (!finished)
|
|
selection_buffer_read (buffer);
|
|
else
|
|
{
|
|
if (error)
|
|
{
|
|
g_warning (G_STRLOC ": error reading selection buffer: %s", 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);
|
|
}
|
|
|
|
selection_buffer_unref (buffer);
|
|
}
|
|
|
|
static void
|
|
selection_buffer_read (SelectionBuffer *buffer)
|
|
{
|
|
selection_buffer_ref (buffer);
|
|
g_input_stream_read_bytes_async (buffer->stream, get_buffer_size(),
|
|
G_PRIORITY_DEFAULT,
|
|
buffer->cancellable, selection_buffer_read_cb,
|
|
buffer);
|
|
}
|
|
|
|
static DataOfferData *
|
|
data_offer_data_new (gpointer offer,
|
|
GDestroyNotify destroy_notify)
|
|
{
|
|
DataOfferData *info;
|
|
|
|
info = g_slice_new0 (DataOfferData);
|
|
info->offer_data = offer;
|
|
info->destroy_notify = destroy_notify;
|
|
|
|
return info;
|
|
}
|
|
|
|
static void
|
|
data_offer_data_free (DataOfferData *info)
|
|
{
|
|
info->destroy_notify (info->offer_data);
|
|
g_list_free (info->targets);
|
|
g_slice_free (DataOfferData, info);
|
|
}
|
|
|
|
static StoredSelection *
|
|
stored_selection_new (GdkWaylandSelection *wayland_selection,
|
|
GdkWindow *source,
|
|
GdkAtom selection,
|
|
GdkAtom type)
|
|
{
|
|
StoredSelection *stored_selection;
|
|
|
|
stored_selection = g_new0 (StoredSelection, 1);
|
|
stored_selection->source = source;
|
|
stored_selection->type = type;
|
|
stored_selection->selection_atom = selection;
|
|
stored_selection->selection = wayland_selection;
|
|
stored_selection->cancellable = g_cancellable_new ();
|
|
stored_selection->pending_writes =
|
|
g_ptr_array_new_with_free_func ((GDestroyNotify) async_write_data_free);
|
|
|
|
return stored_selection;
|
|
}
|
|
|
|
static void
|
|
stored_selection_add_data (StoredSelection *stored_selection,
|
|
GdkPropMode mode,
|
|
guchar *data,
|
|
gsize data_len)
|
|
{
|
|
if (mode == GDK_PROP_MODE_REPLACE)
|
|
{
|
|
g_free (stored_selection->data);
|
|
stored_selection->data = g_memdup (data, data_len);
|
|
stored_selection->data_len = data_len;
|
|
}
|
|
else
|
|
{
|
|
GArray *array;
|
|
|
|
array = g_array_new (TRUE, TRUE, sizeof (guchar));
|
|
g_array_append_vals (array, stored_selection->data, stored_selection->data_len);
|
|
|
|
if (mode == GDK_PROP_MODE_APPEND)
|
|
g_array_append_vals (array, data, data_len);
|
|
else if (mode == GDK_PROP_MODE_PREPEND)
|
|
g_array_prepend_vals (array, data, data_len);
|
|
|
|
g_free (stored_selection->data);
|
|
stored_selection->data_len = array->len;
|
|
stored_selection->data = (guchar *) g_array_free (array, FALSE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
stored_selection_free (StoredSelection *stored_selection)
|
|
{
|
|
g_cancellable_cancel (stored_selection->cancellable);
|
|
g_object_unref (stored_selection->cancellable);
|
|
g_ptr_array_unref (stored_selection->pending_writes);
|
|
g_free (stored_selection->data);
|
|
g_free (stored_selection);
|
|
}
|
|
|
|
static void
|
|
stored_selection_notify_write (StoredSelection *stored_selection)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < stored_selection->pending_writes->len; i++)
|
|
{
|
|
AsyncWriteData *write_data;
|
|
|
|
write_data = g_ptr_array_index (stored_selection->pending_writes, i);
|
|
async_write_data_write (write_data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
stored_selection_cancel_write (StoredSelection *stored_selection)
|
|
{
|
|
g_cancellable_cancel (stored_selection->cancellable);
|
|
g_object_unref (stored_selection->cancellable);
|
|
stored_selection->cancellable = g_cancellable_new ();
|
|
g_ptr_array_set_size (stored_selection->pending_writes, 0);
|
|
}
|
|
|
|
GdkWaylandSelection *
|
|
gdk_wayland_selection_new (void)
|
|
{
|
|
GdkWaylandSelection *selection;
|
|
gint i;
|
|
|
|
/* init atoms */
|
|
atoms[ATOM_PRIMARY] = gdk_atom_intern_static_string ("PRIMARY");
|
|
atoms[ATOM_CLIPBOARD] = gdk_atom_intern_static_string ("CLIPBOARD");
|
|
atoms[ATOM_DND] = gdk_atom_intern_static_string ("GdkWaylandSelection");
|
|
|
|
selection = g_new0 (GdkWaylandSelection, 1);
|
|
for (i = 0; i < G_N_ELEMENTS (selection->selections); i++)
|
|
{
|
|
selection->selections[i].buffers =
|
|
g_hash_table_new_full (NULL, NULL, NULL,
|
|
(GDestroyNotify) selection_buffer_cancel_and_unref);
|
|
}
|
|
|
|
selection->offers =
|
|
g_hash_table_new_full (NULL, NULL, NULL,
|
|
(GDestroyNotify) data_offer_data_free);
|
|
selection->stored_selections =
|
|
g_ptr_array_new_with_free_func ((GDestroyNotify) stored_selection_free);
|
|
|
|
selection->source_targets = g_array_new (FALSE, FALSE, sizeof (GdkAtom));
|
|
return selection;
|
|
}
|
|
|
|
void
|
|
gdk_wayland_selection_free (GdkWaylandSelection *selection)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (selection->selections); i++)
|
|
g_hash_table_destroy (selection->selections[i].buffers);
|
|
|
|
g_array_unref (selection->source_targets);
|
|
|
|
g_hash_table_destroy (selection->offers);
|
|
g_ptr_array_unref (selection->stored_selections);
|
|
|
|
if (selection->primary_source)
|
|
gtk_primary_selection_source_destroy (selection->primary_source);
|
|
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;
|
|
DataOfferData *info;
|
|
GdkAtom atom = gdk_atom_intern (type, FALSE);
|
|
|
|
info = g_hash_table_lookup (selection->offers, wl_data_offer);
|
|
|
|
if (!info || g_list_find (info->targets, atom))
|
|
return;
|
|
|
|
GDK_NOTE (EVENTS,
|
|
g_message ("data offer offer, offer %p, type = %s", wl_data_offer, type));
|
|
|
|
info->targets = g_list_prepend (info->targets, atom);
|
|
}
|
|
|
|
static inline GdkDragAction
|
|
_wl_to_gdk_actions (uint32_t dnd_actions)
|
|
{
|
|
GdkDragAction actions = 0;
|
|
|
|
if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
|
|
actions |= GDK_ACTION_COPY;
|
|
if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
|
|
actions |= GDK_ACTION_MOVE;
|
|
if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
|
|
actions |= GDK_ACTION_ASK;
|
|
|
|
return actions;
|
|
}
|
|
|
|
static void
|
|
data_offer_source_actions (void *data,
|
|
struct wl_data_offer *wl_data_offer,
|
|
uint32_t source_actions)
|
|
{
|
|
GdkDragContext *drop_context;
|
|
GdkDisplay *display;
|
|
GdkDevice *device;
|
|
GdkSeat *seat;
|
|
|
|
display = gdk_display_get_default ();
|
|
seat = gdk_display_get_default_seat (display);
|
|
device = gdk_seat_get_pointer (seat);
|
|
drop_context = gdk_wayland_device_get_drop_context (device);
|
|
|
|
drop_context->actions = _wl_to_gdk_actions (source_actions);
|
|
|
|
GDK_NOTE (EVENTS,
|
|
g_message ("data offer source actions, offer %p, actions %d", wl_data_offer, source_actions));
|
|
|
|
if (gdk_drag_context_get_dest_window (drop_context))
|
|
_gdk_wayland_drag_context_emit_event (drop_context, GDK_DRAG_MOTION,
|
|
GDK_CURRENT_TIME);
|
|
}
|
|
|
|
static void
|
|
data_offer_action (void *data,
|
|
struct wl_data_offer *wl_data_offer,
|
|
uint32_t action)
|
|
{
|
|
GdkDragContext *drop_context;
|
|
GdkDisplay *display;
|
|
GdkDevice *device;
|
|
GdkSeat *seat;
|
|
|
|
display = gdk_display_get_default ();
|
|
seat = gdk_display_get_default_seat (display);
|
|
device = gdk_seat_get_pointer (seat);
|
|
drop_context = gdk_wayland_device_get_drop_context (device);
|
|
|
|
drop_context->action = _wl_to_gdk_actions (action);
|
|
|
|
if (gdk_drag_context_get_dest_window (drop_context))
|
|
_gdk_wayland_drag_context_emit_event (drop_context, GDK_DRAG_MOTION,
|
|
GDK_CURRENT_TIME);
|
|
}
|
|
|
|
static const struct wl_data_offer_listener data_offer_listener = {
|
|
data_offer_offer,
|
|
data_offer_source_actions,
|
|
data_offer_action
|
|
};
|
|
|
|
static void
|
|
primary_offer_offer (void *data,
|
|
struct gtk_primary_selection_offer *gtk_offer,
|
|
const char *type)
|
|
{
|
|
GdkWaylandSelection *selection = data;
|
|
DataOfferData *info;
|
|
GdkAtom atom = gdk_atom_intern (type, FALSE);
|
|
|
|
info = g_hash_table_lookup (selection->offers, gtk_offer);
|
|
|
|
if (!info || g_list_find (info->targets, atom))
|
|
return;
|
|
|
|
GDK_NOTE (EVENTS,
|
|
g_message ("primary offer offer, offer %p, type = %s", gtk_offer, type));
|
|
|
|
info->targets = g_list_prepend (info->targets, atom);
|
|
}
|
|
|
|
static const struct gtk_primary_selection_offer_listener primary_offer_listener = {
|
|
primary_offer_offer,
|
|
};
|
|
|
|
SelectionData *
|
|
selection_lookup_offer_by_atom (GdkWaylandSelection *selection,
|
|
GdkAtom selection_atom)
|
|
{
|
|
if (selection_atom == atoms[ATOM_PRIMARY])
|
|
return &selection->selections[ATOM_PRIMARY];
|
|
else if (selection_atom == atoms[ATOM_CLIPBOARD])
|
|
return &selection->selections[ATOM_CLIPBOARD];
|
|
else if (selection_atom == atoms[ATOM_DND])
|
|
return &selection->selections[ATOM_DND];
|
|
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,
|
|
(GDestroyNotify) wl_data_offer_destroy);
|
|
g_hash_table_insert (selection->offers, wl_offer, info);
|
|
wl_data_offer_add_listener (wl_offer,
|
|
&data_offer_listener,
|
|
selection);
|
|
}
|
|
}
|
|
|
|
void
|
|
gdk_wayland_selection_ensure_primary_offer (GdkDisplay *display,
|
|
struct gtk_primary_selection_offer *gtk_offer)
|
|
{
|
|
GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
|
|
DataOfferData *info;
|
|
|
|
info = g_hash_table_lookup (selection->offers, gtk_offer);
|
|
|
|
if (!info)
|
|
{
|
|
info = data_offer_data_new (gtk_offer,
|
|
(GDestroyNotify) gtk_primary_selection_offer_destroy);
|
|
g_hash_table_insert (selection->offers, gtk_offer, info);
|
|
gtk_primary_selection_offer_add_listener (gtk_offer,
|
|
&primary_offer_listener,
|
|
selection);
|
|
}
|
|
}
|
|
|
|
void
|
|
gdk_wayland_selection_set_offer (GdkDisplay *display,
|
|
GdkAtom selection_atom,
|
|
gpointer wl_offer)
|
|
{
|
|
GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
|
|
struct wl_data_offer *prev_offer;
|
|
SelectionData *selection_data;
|
|
DataOfferData *info;
|
|
|
|
info = g_hash_table_lookup (selection->offers, wl_offer);
|
|
|
|
prev_offer = gdk_wayland_selection_get_offer (display, selection_atom);
|
|
|
|
if (prev_offer)
|
|
g_hash_table_remove (selection->offers, prev_offer);
|
|
|
|
selection_data = selection_lookup_offer_by_atom (selection, selection_atom);
|
|
|
|
if (selection_data)
|
|
{
|
|
selection_data->offer = info;
|
|
/* Clear all buffers */
|
|
g_hash_table_remove_all (selection_data->buffers);
|
|
}
|
|
}
|
|
|
|
gpointer
|
|
gdk_wayland_selection_get_offer (GdkDisplay *display,
|
|
GdkAtom selection_atom)
|
|
{
|
|
GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
|
|
const SelectionData *data;
|
|
|
|
data = selection_lookup_offer_by_atom (selection, selection_atom);
|
|
|
|
if (data && data->offer)
|
|
return data->offer->offer_data;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
GList *
|
|
gdk_wayland_selection_get_targets (GdkDisplay *display,
|
|
GdkAtom selection_atom)
|
|
{
|
|
GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
|
|
const SelectionData *data;
|
|
|
|
data = selection_lookup_offer_by_atom (selection, selection_atom);
|
|
|
|
if (data && data->offer)
|
|
return data->offer->targets;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
gdk_wayland_selection_emit_request (GdkWindow *window,
|
|
GdkAtom selection,
|
|
GdkAtom target)
|
|
{
|
|
GdkEvent *event;
|
|
|
|
event = gdk_event_new (GDK_SELECTION_REQUEST);
|
|
event->selection.window = g_object_ref (window);
|
|
event->selection.send_event = FALSE;
|
|
event->selection.selection = selection;
|
|
event->selection.target = target;
|
|
event->selection.property = gdk_atom_intern_static_string ("GDK_SELECTION");
|
|
event->selection.time = GDK_CURRENT_TIME;
|
|
event->selection.requestor = g_object_ref (window);
|
|
|
|
gdk_event_put (event);
|
|
gdk_event_free (event);
|
|
}
|
|
|
|
static AsyncWriteData *
|
|
async_write_data_new (StoredSelection *stored_selection,
|
|
gint fd)
|
|
{
|
|
AsyncWriteData *write_data;
|
|
|
|
write_data = g_slice_new0 (AsyncWriteData);
|
|
write_data->stored_selection = stored_selection;
|
|
write_data->stream = g_unix_output_stream_new (fd, TRUE);
|
|
g_ptr_array_add (stored_selection->pending_writes, write_data);
|
|
|
|
return write_data;
|
|
}
|
|
|
|
static void
|
|
async_write_data_free (AsyncWriteData *write_data)
|
|
{
|
|
g_object_unref (write_data->stream);
|
|
g_slice_free (AsyncWriteData, write_data);
|
|
}
|
|
|
|
static void
|
|
async_write_data_cb (GObject *object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
AsyncWriteData *write_data = user_data;
|
|
GError *error = NULL;
|
|
gsize bytes_written;
|
|
|
|
bytes_written = g_output_stream_write_finish (G_OUTPUT_STREAM (object),
|
|
res, &error);
|
|
if (error)
|
|
{
|
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
{
|
|
g_warning ("Error writing selection data: %s", error->message);
|
|
g_ptr_array_remove_fast (write_data->stored_selection->pending_writes,
|
|
write_data);
|
|
}
|
|
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
write_data->index += bytes_written;
|
|
|
|
if (write_data->index < write_data->stored_selection->data_len)
|
|
{
|
|
/* Write the next chunk */
|
|
async_write_data_write (write_data);
|
|
}
|
|
else
|
|
{
|
|
g_ptr_array_remove_fast (write_data->stored_selection->pending_writes,
|
|
write_data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
async_write_data_write (AsyncWriteData *write_data)
|
|
{
|
|
gsize buf_len;
|
|
guchar *buf;
|
|
|
|
buf = write_data->stored_selection->data;
|
|
buf_len = write_data->stored_selection->data_len;
|
|
|
|
g_output_stream_write_async (write_data->stream,
|
|
&buf[write_data->index],
|
|
buf_len - write_data->index,
|
|
G_PRIORITY_DEFAULT,
|
|
write_data->stored_selection->cancellable,
|
|
async_write_data_cb,
|
|
write_data);
|
|
}
|
|
|
|
static StoredSelection *
|
|
gdk_wayland_selection_find_stored_selection (GdkWaylandSelection *wayland_selection,
|
|
GdkWindow *window,
|
|
GdkAtom selection,
|
|
GdkAtom type)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < wayland_selection->stored_selections->len; i++)
|
|
{
|
|
StoredSelection *stored_selection;
|
|
|
|
stored_selection = g_ptr_array_index (wayland_selection->stored_selections, i);
|
|
|
|
if (stored_selection->source == window &&
|
|
stored_selection->selection_atom == selection &&
|
|
stored_selection->type == type)
|
|
return stored_selection;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
gdk_wayland_selection_reset_selection (GdkWaylandSelection *wayland_selection,
|
|
GdkAtom selection)
|
|
{
|
|
gint i = 0;
|
|
|
|
while (i < wayland_selection->stored_selections->len)
|
|
{
|
|
StoredSelection *stored_selection;
|
|
|
|
stored_selection = g_ptr_array_index (wayland_selection->stored_selections, i);
|
|
|
|
if (stored_selection->selection_atom == selection)
|
|
g_ptr_array_remove_index_fast (wayland_selection->stored_selections, i);
|
|
else
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void
|
|
gdk_wayland_selection_store (GdkWindow *window,
|
|
GdkAtom type,
|
|
GdkPropMode mode,
|
|
const guchar *data,
|
|
gint len)
|
|
{
|
|
GdkDisplay *display = gdk_window_get_display (window);
|
|
GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
|
|
StoredSelection *stored_selection;
|
|
|
|
if (type == gdk_atom_intern_static_string ("NULL"))
|
|
return;
|
|
if (selection->current_request_selection == GDK_NONE)
|
|
return;
|
|
|
|
stored_selection =
|
|
gdk_wayland_selection_find_stored_selection (selection, window,
|
|
selection->current_request_selection,
|
|
type);
|
|
|
|
if (!stored_selection)
|
|
{
|
|
stored_selection = stored_selection_new (selection, window,
|
|
selection->current_request_selection,
|
|
type);
|
|
g_ptr_array_add (selection->stored_selections, stored_selection);
|
|
}
|
|
|
|
if ((mode == GDK_PROP_MODE_PREPEND ||
|
|
mode == GDK_PROP_MODE_REPLACE) &&
|
|
stored_selection->data &&
|
|
stored_selection->pending_writes->len > 0)
|
|
{
|
|
/* If a prepend/replace action happens, all current readers are
|
|
* pretty much stale.
|
|
*/
|
|
stored_selection_cancel_write (stored_selection);
|
|
}
|
|
|
|
stored_selection_add_data (stored_selection, mode, data, len);
|
|
|
|
if (stored_selection->data)
|
|
stored_selection_notify_write (stored_selection);
|
|
else
|
|
{
|
|
g_ptr_array_remove_fast (selection->stored_selections,
|
|
stored_selection);
|
|
}
|
|
|
|
/* Handle the next GDK_SELECTION_REQUEST / store, if any */
|
|
selection->current_request_selection = GDK_NONE;
|
|
gdk_wayland_selection_handle_next_request (selection);
|
|
}
|
|
|
|
static SelectionBuffer *
|
|
gdk_wayland_selection_lookup_requestor_buffer (GdkWindow *requestor)
|
|
{
|
|
GdkDisplay *display = gdk_window_get_display (requestor);
|
|
GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display);
|
|
SelectionBuffer *buffer_data;
|
|
GHashTableIter iter;
|
|
gint i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (selection->selections); i++)
|
|
{
|
|
g_hash_table_iter_init (&iter, selection->selections[i].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;
|
|
}
|
|
|
|
static gboolean
|
|
gdk_wayland_selection_source_handles_target (GdkWaylandSelection *wayland_selection,
|
|
GdkAtom target)
|
|
{
|
|
GdkAtom atom;
|
|
guint i;
|
|
|
|
if (target == GDK_NONE)
|
|
return FALSE;
|
|
|
|
for (i = 0; i < wayland_selection->source_targets->len; i++)
|
|
{
|
|
atom = g_array_index (wayland_selection->source_targets, GdkAtom, i);
|
|
|
|
if (atom == target)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gdk_wayland_selection_handle_next_request (GdkWaylandSelection *wayland_selection)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < wayland_selection->stored_selections->len; i++)
|
|
{
|
|
StoredSelection *stored_selection;
|
|
|
|
stored_selection = g_ptr_array_index (wayland_selection->stored_selections, i);
|
|
|
|
if (!stored_selection->data)
|
|
{
|
|
gdk_wayland_selection_emit_request (stored_selection->source,
|
|
stored_selection->selection_atom,
|
|
stored_selection->type);
|
|
wayland_selection->current_request_selection = stored_selection->selection_atom;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gdk_wayland_selection_request_target (GdkWaylandSelection *wayland_selection,
|
|
GdkWindow *window,
|
|
GdkAtom selection,
|
|
GdkAtom target,
|
|
gint fd)
|
|
{
|
|
StoredSelection *stored_selection;
|
|
AsyncWriteData *write_data;
|
|
|
|
if (!window ||
|
|
!gdk_wayland_selection_source_handles_target (wayland_selection, target))
|
|
{
|
|
close (fd);
|
|
return FALSE;
|
|
}
|
|
|
|
stored_selection =
|
|
gdk_wayland_selection_find_stored_selection (wayland_selection, window,
|
|
selection, target);
|
|
|
|
if (stored_selection && stored_selection->data)
|
|
{
|
|
/* Fast path, we already have the type cached */
|
|
write_data = async_write_data_new (stored_selection, fd);
|
|
async_write_data_write (write_data);
|
|
return TRUE;
|
|
}
|
|
|
|
if (!stored_selection)
|
|
{
|
|
stored_selection = stored_selection_new (wayland_selection, window,
|
|
selection, target);
|
|
g_ptr_array_add (wayland_selection->stored_selections, stored_selection);
|
|
}
|
|
|
|
write_data = async_write_data_new (stored_selection, fd);
|
|
|
|
if (wayland_selection->current_request_selection == GDK_NONE)
|
|
gdk_wayland_selection_handle_next_request (wayland_selection);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
data_source_target (void *data,
|
|
struct wl_data_source *source,
|
|
const char *mime_type)
|
|
{
|
|
GDK_NOTE (EVENTS,
|
|
g_message ("data source target, source = %p, mime_type = %s",
|
|
source, mime_type));
|
|
}
|
|
|
|
static void
|
|
data_source_send (void *data,
|
|
struct wl_data_source *source,
|
|
const char *mime_type,
|
|
int32_t fd)
|
|
{
|
|
GdkWaylandSelection *wayland_selection = data;
|
|
GdkWindow *window;
|
|
GdkAtom selection;
|
|
|
|
GDK_NOTE (EVENTS,
|
|
g_message ("data source send, source = %p, mime_type = %s, fd = %d",
|
|
source, mime_type, fd));
|
|
|
|
if (!mime_type)
|
|
{
|
|
close (fd);
|
|
return;
|
|
}
|
|
|
|
if (source == wayland_selection->dnd_source)
|
|
{
|
|
window = wayland_selection->dnd_owner;
|
|
selection = atoms[ATOM_DND];
|
|
}
|
|
else if (source == wayland_selection->clipboard_source)
|
|
{
|
|
window = wayland_selection->clipboard_owner;
|
|
selection = atoms[ATOM_CLIPBOARD];
|
|
}
|
|
else
|
|
{
|
|
close (fd);
|
|
return;
|
|
}
|
|
|
|
if (!window)
|
|
return;
|
|
|
|
gdk_wayland_selection_request_target (wayland_selection, window,
|
|
selection,
|
|
gdk_atom_intern (mime_type, FALSE),
|
|
fd);
|
|
}
|
|
|
|
static void
|
|
data_source_cancelled (void *data,
|
|
struct wl_data_source *source)
|
|
{
|
|
GdkWaylandSelection *wayland_selection = data;
|
|
GdkDragContext *context;
|
|
GdkDisplay *display;
|
|
GdkAtom atom;
|
|
|
|
GDK_NOTE (EVENTS,
|
|
g_message ("data source cancelled, source = %p", source));
|
|
|
|
display = gdk_display_get_default ();
|
|
|
|
if (source == wayland_selection->dnd_source)
|
|
atom = atoms[ATOM_DND];
|
|
else if (source == wayland_selection->clipboard_source)
|
|
atom = atoms[ATOM_CLIPBOARD];
|
|
else
|
|
return;
|
|
|
|
context = gdk_wayland_drag_context_lookup_by_data_source (source);
|
|
|
|
if (context)
|
|
gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_ERROR);
|
|
|
|
emit_selection_clear (display, atom);
|
|
gdk_selection_owner_set (NULL, atom, GDK_CURRENT_TIME, FALSE);
|
|
gdk_wayland_selection_unset_data_source (display, atom);
|
|
}
|
|
|
|
static void
|
|
data_source_dnd_drop_performed (void *data,
|
|
struct wl_data_source *source)
|
|
{
|
|
GdkDragContext *context;
|
|
|
|
context = gdk_wayland_drag_context_lookup_by_data_source (source);
|
|
|
|
if (!context)
|
|
return;
|
|
|
|
g_signal_emit_by_name (context, "drop-performed", GDK_CURRENT_TIME);
|
|
}
|
|
|
|
static void
|
|
data_source_dnd_finished (void *data,
|
|
struct wl_data_source *source)
|
|
{
|
|
GdkDisplay *display = gdk_display_get_default ();
|
|
GdkDragContext *context;
|
|
|
|
context = gdk_wayland_drag_context_lookup_by_data_source (source);
|
|
|
|
if (!context)
|
|
return;
|
|
|
|
if (context->action == GDK_ACTION_MOVE)
|
|
{
|
|
gdk_wayland_selection_emit_request (context->source_window,
|
|
atoms[ATOM_DND],
|
|
gdk_atom_intern_static_string ("DELETE"));
|
|
}
|
|
|
|
g_signal_emit_by_name (context, "dnd-finished");
|
|
gdk_selection_owner_set (NULL, atoms[ATOM_DND], GDK_CURRENT_TIME, TRUE);
|
|
gdk_wayland_selection_clear_targets (display, atoms[ATOM_DND]);
|
|
}
|
|
|
|
static void
|
|
data_source_action (void *data,
|
|
struct wl_data_source *source,
|
|
uint32_t action)
|
|
{
|
|
GdkDragContext *context;
|
|
|
|
GDK_NOTE (EVENTS,
|
|
g_message ("data source action, source = %p action=%x",
|
|
source, action));
|
|
|
|
context = gdk_wayland_drag_context_lookup_by_data_source (source);
|
|
|
|
if (!context)
|
|
return;
|
|
|
|
context->action = _wl_to_gdk_actions (action);
|
|
g_signal_emit_by_name (context, "action-changed", context->action);
|
|
}
|
|
|
|
static const struct wl_data_source_listener data_source_listener = {
|
|
data_source_target,
|
|
data_source_send,
|
|
data_source_cancelled,
|
|
data_source_dnd_drop_performed,
|
|
data_source_dnd_finished,
|
|
data_source_action,
|
|
};
|
|
|
|
static void
|
|
primary_source_send (void *data,
|
|
struct gtk_primary_selection_source *source,
|
|
const char *mime_type,
|
|
int32_t fd)
|
|
{
|
|
GdkWaylandSelection *wayland_selection = data;
|
|
|
|
GDK_NOTE (EVENTS,
|
|
g_message ("primary source send, source = %p, mime_type = %s, fd = %d",
|
|
source, mime_type, fd));
|
|
|
|
if (!mime_type || !wayland_selection->primary_owner)
|
|
{
|
|
close (fd);
|
|
return;
|
|
}
|
|
|
|
gdk_wayland_selection_request_target (wayland_selection,
|
|
wayland_selection->primary_owner,
|
|
atoms[ATOM_PRIMARY],
|
|
gdk_atom_intern (mime_type, FALSE),
|
|
fd);
|
|
}
|
|
|
|
static void
|
|
primary_source_cancelled (void *data,
|
|
struct gtk_primary_selection_source *source)
|
|
{
|
|
GdkDisplay *display;
|
|
GdkAtom atom;
|
|
|
|
GDK_NOTE (EVENTS,
|
|
g_message ("primary source cancelled, source = %p", source));
|
|
|
|
display = gdk_display_get_default ();
|
|
|
|
atom = atoms[ATOM_PRIMARY];
|
|
emit_selection_clear (display, atom);
|
|
gdk_selection_owner_set (NULL, atom, GDK_CURRENT_TIME, FALSE);
|
|
gdk_wayland_selection_unset_data_source (display, atom);
|
|
}
|
|
|
|
static const struct gtk_primary_selection_source_listener primary_source_listener = {
|
|
primary_source_send,
|
|
primary_source_cancelled,
|
|
};
|
|
|
|
struct wl_data_source *
|
|
gdk_wayland_selection_get_data_source (GdkWindow *owner,
|
|
GdkAtom selection)
|
|
{
|
|
GdkDisplay *display = gdk_window_get_display (owner);
|
|
GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display);
|
|
gpointer source = NULL;
|
|
GdkWaylandDisplay *display_wayland;
|
|
|
|
if (selection == atoms[ATOM_DND])
|
|
{
|
|
if (wayland_selection->dnd_source &&
|
|
(!owner || owner == wayland_selection->dnd_owner))
|
|
return wayland_selection->dnd_source;
|
|
}
|
|
else if (selection == atoms[ATOM_PRIMARY])
|
|
{
|
|
if (wayland_selection->primary_source &&
|
|
(!owner || owner == wayland_selection->primary_owner))
|
|
return (gpointer) wayland_selection->primary_source;
|
|
|
|
if (wayland_selection->primary_source)
|
|
{
|
|
gtk_primary_selection_source_destroy (wayland_selection->primary_source);
|
|
wayland_selection->primary_source = NULL;
|
|
}
|
|
}
|
|
else if (selection == atoms[ATOM_CLIPBOARD])
|
|
{
|
|
if (wayland_selection->clipboard_source &&
|
|
(!owner || owner == wayland_selection->clipboard_owner))
|
|
return wayland_selection->clipboard_source;
|
|
|
|
if (wayland_selection->clipboard_source)
|
|
{
|
|
wl_data_source_destroy (wayland_selection->clipboard_source);
|
|
wayland_selection->clipboard_source = NULL;
|
|
}
|
|
}
|
|
else
|
|
return NULL;
|
|
|
|
if (!owner)
|
|
return NULL;
|
|
|
|
display_wayland = GDK_WAYLAND_DISPLAY (gdk_window_get_display (owner));
|
|
|
|
if (selection == atoms[ATOM_PRIMARY])
|
|
{
|
|
if (display_wayland->primary_selection_manager)
|
|
{
|
|
source = gtk_primary_selection_device_manager_create_source (display_wayland->primary_selection_manager);
|
|
gtk_primary_selection_source_add_listener (source,
|
|
&primary_source_listener,
|
|
wayland_selection);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
source = wl_data_device_manager_create_data_source (display_wayland->data_device_manager);
|
|
wl_data_source_add_listener (source,
|
|
&data_source_listener,
|
|
wayland_selection);
|
|
}
|
|
|
|
if (selection == atoms[ATOM_DND])
|
|
wayland_selection->dnd_source = source;
|
|
else if (selection == atoms[ATOM_PRIMARY])
|
|
wayland_selection->primary_source = source;
|
|
else if (selection == atoms[ATOM_CLIPBOARD])
|
|
wayland_selection->clipboard_source = source;
|
|
|
|
return source;
|
|
}
|
|
|
|
void
|
|
gdk_wayland_selection_unset_data_source (GdkDisplay *display,
|
|
GdkAtom selection)
|
|
{
|
|
GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display);
|
|
|
|
if (selection == atoms[ATOM_CLIPBOARD])
|
|
{
|
|
if (wayland_selection->clipboard_source)
|
|
{
|
|
wl_data_source_destroy (wayland_selection->clipboard_source);
|
|
wayland_selection->clipboard_source = NULL;
|
|
}
|
|
}
|
|
else if (selection == atoms[ATOM_PRIMARY])
|
|
{
|
|
if (wayland_selection->primary_source)
|
|
{
|
|
gtk_primary_selection_source_destroy (wayland_selection->primary_source);
|
|
wayland_selection->primary_source = NULL;
|
|
}
|
|
}
|
|
else if (selection == atoms[ATOM_DND])
|
|
{
|
|
wayland_selection->dnd_source = 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_PRIMARY])
|
|
return wayland_selection->primary_owner;
|
|
else if (selection == atoms[ATOM_DND])
|
|
return wayland_selection->dnd_owner;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
_gdk_wayland_display_set_selection_owner (GdkDisplay *display,
|
|
GdkWindow *owner,
|
|
GdkAtom selection,
|
|
guint32 time,
|
|
gboolean send_event)
|
|
{
|
|
GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display);
|
|
GdkSeat *seat = gdk_display_get_default_seat (display);
|
|
|
|
gdk_wayland_selection_reset_selection (wayland_selection, selection);
|
|
|
|
if (selection == atoms[ATOM_CLIPBOARD])
|
|
{
|
|
wayland_selection->clipboard_owner = owner;
|
|
if (send_event && !owner)
|
|
{
|
|
gdk_wayland_seat_set_selection (seat, NULL);
|
|
gdk_wayland_selection_unset_data_source (display, selection);
|
|
}
|
|
return TRUE;
|
|
}
|
|
else if (selection == atoms[ATOM_PRIMARY])
|
|
{
|
|
wayland_selection->primary_owner = owner;
|
|
if (send_event && !owner)
|
|
{
|
|
gdk_wayland_seat_set_primary (seat, NULL);
|
|
gdk_wayland_selection_unset_data_source (display, selection);
|
|
}
|
|
return TRUE;
|
|
}
|
|
else if (selection == atoms[ATOM_DND])
|
|
{
|
|
wayland_selection->dnd_owner = owner;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
_gdk_wayland_display_send_selection_notify (GdkDisplay *dispay,
|
|
GdkWindow *requestor,
|
|
GdkAtom selection,
|
|
GdkAtom target,
|
|
GdkAtom property,
|
|
guint32 time)
|
|
{
|
|
}
|
|
|
|
gint
|
|
_gdk_wayland_display_get_selection_property (GdkDisplay *display,
|
|
GdkWindow *requestor,
|
|
guchar **data,
|
|
GdkAtom *ret_type,
|
|
gint *ret_format)
|
|
{
|
|
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 = buffer_data->target;
|
|
if (ret_format)
|
|
*ret_format = 8;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static void
|
|
emit_empty_selection_notify (GdkWindow *requestor,
|
|
GdkAtom selection,
|
|
GdkAtom target)
|
|
{
|
|
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);
|
|
}
|
|
|
|
static void
|
|
emit_selection_clear (GdkDisplay *display,
|
|
GdkAtom selection)
|
|
{
|
|
GdkEvent *event;
|
|
GdkWindow *window;
|
|
|
|
event = gdk_event_new (GDK_SELECTION_CLEAR);
|
|
event->selection.selection = selection;
|
|
event->selection.time = GDK_CURRENT_TIME;
|
|
|
|
window = _gdk_wayland_display_get_selection_owner (display, selection);
|
|
if (window != NULL)
|
|
{
|
|
event->selection.window = g_object_ref (window);
|
|
event->selection.requestor = g_object_ref (window);
|
|
}
|
|
|
|
gdk_event_put (event);
|
|
gdk_event_free (event);
|
|
}
|
|
|
|
void
|
|
_gdk_wayland_display_convert_selection (GdkDisplay *display,
|
|
GdkWindow *requestor,
|
|
GdkAtom selection,
|
|
GdkAtom target,
|
|
guint32 time)
|
|
{
|
|
GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display);
|
|
const SelectionData *selection_data;
|
|
SelectionBuffer *buffer_data;
|
|
gpointer offer;
|
|
gchar *mimetype;
|
|
GList *target_list;
|
|
|
|
selection_data = selection_lookup_offer_by_atom (wayland_selection, selection);
|
|
if (!selection_data)
|
|
{
|
|
emit_empty_selection_notify (requestor, selection, target);
|
|
return;
|
|
}
|
|
|
|
offer = gdk_wayland_selection_get_offer (display, selection);
|
|
target_list = gdk_wayland_selection_get_targets (display, selection);
|
|
|
|
if (!offer || target == gdk_atom_intern_static_string ("DELETE"))
|
|
{
|
|
emit_empty_selection_notify (requestor, selection, target);
|
|
return;
|
|
}
|
|
|
|
mimetype = gdk_atom_name (target);
|
|
|
|
if (target != gdk_atom_intern_static_string ("TARGETS"))
|
|
{
|
|
if (!g_list_find (target_list, GDK_ATOM_TO_POINTER (target)))
|
|
{
|
|
emit_empty_selection_notify (requestor, selection, target);
|
|
return;
|
|
}
|
|
|
|
if (selection != atoms[ATOM_PRIMARY])
|
|
wl_data_offer_accept (offer,
|
|
_gdk_wayland_display_get_serial (GDK_WAYLAND_DISPLAY (display)),
|
|
mimetype);
|
|
}
|
|
|
|
buffer_data = g_hash_table_lookup (selection_data->buffers, target);
|
|
|
|
if (buffer_data)
|
|
selection_buffer_add_requestor (buffer_data, requestor);
|
|
else
|
|
{
|
|
GInputStream *stream = NULL;
|
|
int pipe_fd[2], natoms = 0;
|
|
GdkAtom *targets = NULL;
|
|
|
|
if (target == gdk_atom_intern_static_string ("TARGETS"))
|
|
{
|
|
gint i = 0;
|
|
GList *l;
|
|
|
|
natoms = g_list_length (target_list);
|
|
targets = g_new0 (GdkAtom, natoms);
|
|
|
|
for (l = target_list; l; l = l->next)
|
|
targets[i++] = l->data;
|
|
}
|
|
else
|
|
{
|
|
g_unix_open_pipe (pipe_fd, FD_CLOEXEC, NULL);
|
|
|
|
if (selection == atoms[ATOM_PRIMARY])
|
|
gtk_primary_selection_offer_receive (offer, mimetype, pipe_fd[1]);
|
|
else
|
|
wl_data_offer_receive (offer, mimetype, 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 (targets)
|
|
{
|
|
/* Store directly the local atoms */
|
|
selection_buffer_append_data (buffer_data, targets, natoms * sizeof (GdkAtom));
|
|
g_free (targets);
|
|
}
|
|
|
|
g_hash_table_insert (selection_data->buffers,
|
|
GDK_ATOM_TO_POINTER (target),
|
|
buffer_data);
|
|
}
|
|
|
|
if (!buffer_data->stream)
|
|
selection_buffer_notify (buffer_data);
|
|
|
|
g_free (mimetype);
|
|
}
|
|
|
|
gint
|
|
_gdk_wayland_display_text_property_to_utf8_list (GdkDisplay *display,
|
|
GdkAtom encoding,
|
|
gint format,
|
|
const guchar *text,
|
|
gint length,
|
|
gchar ***list)
|
|
{
|
|
GPtrArray *array;
|
|
const gchar *ptr;
|
|
gsize chunk_len;
|
|
gchar *copy;
|
|
guint nitems;
|
|
|
|
ptr = (const gchar *) text;
|
|
array = g_ptr_array_new ();
|
|
|
|
while (ptr < (const gchar *) &text[length])
|
|
{
|
|
chunk_len = strlen (ptr);
|
|
|
|
if (g_utf8_validate (ptr, chunk_len, NULL))
|
|
{
|
|
copy = g_strndup (ptr, chunk_len);
|
|
g_ptr_array_add (array, copy);
|
|
}
|
|
|
|
ptr = &ptr[chunk_len + 1];
|
|
}
|
|
|
|
nitems = array->len;
|
|
g_ptr_array_add (array, NULL);
|
|
|
|
if (list)
|
|
*list = (gchar **) g_ptr_array_free (array, FALSE);
|
|
else
|
|
g_ptr_array_free (array, TRUE);
|
|
|
|
return nitems;
|
|
}
|
|
|
|
/* This function has been copied straight from the x11 backend */
|
|
static gchar *
|
|
sanitize_utf8 (const gchar *src,
|
|
gboolean return_latin1)
|
|
{
|
|
gint len = strlen (src);
|
|
GString *result = g_string_sized_new (len);
|
|
const gchar *p = src;
|
|
|
|
while (*p)
|
|
{
|
|
if (*p == '\r')
|
|
{
|
|
p++;
|
|
if (*p == '\n')
|
|
p++;
|
|
|
|
g_string_append_c (result, '\n');
|
|
}
|
|
else
|
|
{
|
|
gunichar ch = g_utf8_get_char (p);
|
|
|
|
if (!((ch < 0x20 && ch != '\t' && ch != '\n') || (ch >= 0x7f && ch < 0xa0)))
|
|
{
|
|
if (return_latin1)
|
|
{
|
|
if (ch <= 0xff)
|
|
g_string_append_c (result, ch);
|
|
else
|
|
g_string_append_printf (result,
|
|
ch < 0x10000 ? "\\u%04x" : "\\U%08x",
|
|
ch);
|
|
}
|
|
else
|
|
{
|
|
char buf[7];
|
|
gint buflen;
|
|
|
|
buflen = g_unichar_to_utf8 (ch, buf);
|
|
g_string_append_len (result, buf, buflen);
|
|
}
|
|
}
|
|
|
|
p = g_utf8_next_char (p);
|
|
}
|
|
}
|
|
|
|
return g_string_free (result, FALSE);
|
|
}
|
|
|
|
gchar *
|
|
_gdk_wayland_display_utf8_to_string_target (GdkDisplay *display,
|
|
const gchar *str)
|
|
{
|
|
/* This is mainly needed when interfacing with old clients through
|
|
* Xwayland, the STRING target could be used, and passed as-is
|
|
* by the compositor.
|
|
*
|
|
* There's already some handling of this atom (aka "mimetype" in
|
|
* this backend) in common code, so we end up in this vfunc.
|
|
*/
|
|
return sanitize_utf8 (str, TRUE);
|
|
}
|
|
|
|
void
|
|
gdk_wayland_selection_add_targets (GdkWindow *window,
|
|
GdkAtom selection,
|
|
guint ntargets,
|
|
GdkAtom *targets)
|
|
{
|
|
GdkDisplay *display = gdk_window_get_display (window);
|
|
GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display);
|
|
gpointer data_source;
|
|
guint i;
|
|
|
|
g_return_if_fail (GDK_IS_WINDOW (window));
|
|
|
|
data_source = gdk_wayland_selection_get_data_source (window, selection);
|
|
|
|
if (!data_source)
|
|
return;
|
|
|
|
g_array_append_vals (wayland_selection->source_targets, targets, ntargets);
|
|
|
|
for (i = 0; i < ntargets; i++)
|
|
{
|
|
gchar *mimetype = gdk_atom_name (targets[i]);
|
|
|
|
wl_data_source_offer (data_source, mimetype);
|
|
g_free (mimetype);
|
|
}
|
|
|
|
if (selection == atoms[ATOM_CLIPBOARD])
|
|
{
|
|
GdkSeat *seat;
|
|
|
|
seat = gdk_display_get_default_seat (display);
|
|
gdk_wayland_seat_set_selection (seat, data_source);
|
|
}
|
|
else if (selection == atoms[ATOM_PRIMARY])
|
|
{
|
|
GdkSeat *seat;
|
|
|
|
seat = gdk_display_get_default_seat (display);
|
|
gdk_wayland_seat_set_primary (seat, data_source);
|
|
}
|
|
}
|
|
|
|
void
|
|
gdk_wayland_selection_clear_targets (GdkDisplay *display,
|
|
GdkAtom selection)
|
|
{
|
|
GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display);
|
|
|
|
wayland_selection->requested_target = GDK_NONE;
|
|
g_array_set_size (wayland_selection->source_targets, 0);
|
|
gdk_wayland_selection_unset_data_source (display, selection);
|
|
}
|
|
|
|
gboolean
|
|
gdk_wayland_selection_set_current_offer_actions (GdkDisplay *display,
|
|
uint32_t action)
|
|
{
|
|
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
|
|
struct wl_data_offer *offer;
|
|
uint32_t all_actions = 0;
|
|
|
|
offer = gdk_wayland_selection_get_offer (display, atoms[ATOM_DND]);
|
|
|
|
if (!offer)
|
|
return FALSE;
|
|
|
|
if (action != 0)
|
|
all_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
|
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE |
|
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK;
|
|
|
|
if (display_wayland->data_device_manager_version >=
|
|
WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION)
|
|
wl_data_offer_set_actions (offer, all_actions, action);
|
|
return TRUE;
|
|
}
|