/* * 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 . */ #include "config.h" #include #include #include #include #include #include "gdkwayland.h" #include "gdkprivate-wayland.h" #include "gdkdisplay-wayland.h" #include "gdkcontentformatsprivate.h" #include "gdkdndprivate.h" #include "gdkproperty.h" #include typedef struct _SelectionData SelectionData; typedef struct _DataOfferData DataOfferData; struct _DataOfferData { GDestroyNotify destroy_notify; gpointer offer_data; GdkContentFormats *targets; }; struct _SelectionData { DataOfferData *offer; }; struct _GdkWaylandSelection { /* Destination-side data */ SelectionData selection; GHashTable *offers; /* Currently alive offers, Hashtable of wl_data_offer->DataOfferData */ struct wl_data_source *dnd_source; /* Owned by the GdkDragContext */ }; 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; info->targets = gdk_content_formats_new (NULL, 0); return info; } static void data_offer_data_free (DataOfferData *info) { info->destroy_notify (info->offer_data); gdk_content_formats_unref (info->targets); g_slice_free (DataOfferData, info); } GdkWaylandSelection * gdk_wayland_selection_new (void) { GdkWaylandSelection *selection; selection = g_new0 (GdkWaylandSelection, 1); selection->offers = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) data_offer_data_free); return selection; } void gdk_wayland_selection_free (GdkWaylandSelection *selection) { g_hash_table_destroy (selection->offers); 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; GdkContentFormatsBuilder *builder; DataOfferData *info; info = g_hash_table_lookup (selection->offers, wl_data_offer); if (!info || gdk_content_formats_contain_mime_type (info->targets, type)) return; GDK_NOTE (EVENTS, g_message ("data offer offer, offer %p, type = %s", wl_data_offer, type)); builder = gdk_content_formats_builder_new (); gdk_content_formats_builder_add_formats (builder, info->targets); gdk_content_formats_builder_add_mime_type (builder, type); gdk_content_formats_unref (info->targets); info->targets = gdk_content_formats_builder_free_to_formats (builder); } 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_DISPLAY_NOTE (display, EVENTS, g_message ("data offer source actions, offer %p, actions %d", wl_data_offer, source_actions)); if (gdk_drag_context_get_dest_surface (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_surface (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 SelectionData * selection_lookup_offer_by_atom (GdkWaylandSelection *selection) { return &selection->selection; } 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); } } GdkContentFormats * gdk_wayland_selection_steal_offer (GdkDisplay *display, gpointer wl_offer) { GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display); GdkContentFormats *formats; DataOfferData *info; info = g_hash_table_lookup (selection->offers, wl_offer); if (info == NULL) return NULL; g_hash_table_steal (selection->offers, wl_offer); formats = info->targets; g_slice_free (DataOfferData, info); return formats; } void gdk_wayland_selection_set_offer (GdkDisplay *display, 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); if (prev_offer) g_hash_table_remove (selection->offers, prev_offer); selection_data = selection_lookup_offer_by_atom (selection); if (selection_data) { selection_data->offer = info; } } gpointer gdk_wayland_selection_get_offer (GdkDisplay *display) { GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display); const SelectionData *data; data = selection_lookup_offer_by_atom (selection); if (data && data->offer) return data->offer->offer_data; return NULL; } GdkContentFormats * gdk_wayland_selection_get_targets (GdkDisplay *display) { GdkWaylandSelection *selection = gdk_wayland_display_get_selection (display); const SelectionData *data; data = selection_lookup_offer_by_atom (selection); if (data && data->offer) return data->offer->targets; return NULL; } 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 gdk_wayland_drag_context_write_done (GObject *context, GAsyncResult *result, gpointer user_data) { GError *error = NULL; if (!gdk_drag_context_write_finish (GDK_DRAG_CONTEXT (context), result, &error)) { GDK_DISPLAY_NOTE (gdk_drag_context_get_display (GDK_DRAG_CONTEXT (context)), DND, g_message ("%p: failed to write stream: %s", context, error->message)); g_error_free (error); } } static void data_source_send (void *data, struct wl_data_source *source, const char *mime_type, int32_t fd) { GdkDragContext *context; GOutputStream *stream; context = gdk_wayland_drag_context_lookup_by_data_source (source); if (!context) return; GDK_DISPLAY_NOTE (gdk_drag_context_get_display (context), DND, g_message ("%p: data source send request for %s on fd %d\n", source, mime_type, fd)); //mime_type = gdk_intern_mime_type (mime_type); mime_type = g_intern_string (mime_type); stream = g_unix_output_stream_new (fd, TRUE); gdk_drag_context_write_async (context, mime_type, stream, G_PRIORITY_DEFAULT, NULL, gdk_wayland_drag_context_write_done, context); g_object_unref (stream); } static void data_source_cancelled (void *data, struct wl_data_source *source) { GdkWaylandSelection *wayland_selection = data; GdkDragContext *context; GdkDisplay *display; display = gdk_display_get_default (); GDK_DISPLAY_NOTE (display, EVENTS, g_message ("data source cancelled, source = %p", source)); if (source != wayland_selection->dnd_source) return; context = gdk_wayland_drag_context_lookup_by_data_source (source); if (context) gdk_drag_context_cancel (context, GDK_DRAG_CANCEL_ERROR); gdk_wayland_selection_unset_data_source (display); } 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) { GdkDragContext *context; context = gdk_wayland_drag_context_lookup_by_data_source (source); if (!context) return; g_signal_emit_by_name (context, "dnd-finished"); } static void data_source_action (void *data, struct wl_data_source *source, uint32_t action) { GdkDragContext *context; context = gdk_wayland_drag_context_lookup_by_data_source (source); if (!context) return; GDK_DISPLAY_NOTE (gdk_drag_context_get_display (context), EVENTS, g_message ("data source action, source = %p action=%x", source, action)); 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, }; struct wl_data_source * gdk_wayland_selection_get_data_source (GdkSurface *owner) { GdkDisplay *display = gdk_surface_get_display (owner); GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display); gpointer source = NULL; GdkWaylandDisplay *display_wayland; if (wayland_selection->dnd_source) return wayland_selection->dnd_source; display_wayland = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (owner)); source = wl_data_device_manager_create_data_source (display_wayland->data_device_manager); wl_data_source_add_listener (source, &data_source_listener, wayland_selection); wayland_selection->dnd_source = source; return source; } void gdk_wayland_selection_unset_data_source (GdkDisplay *display) { GdkWaylandSelection *wayland_selection = gdk_wayland_display_get_selection (display); wayland_selection->dnd_source = NULL; } 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); } 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); 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; }