2020-04-09 10:22:21 -04:00
|
|
|
/*
|
|
|
|
* Copyright © 2011 Kristian Høgsberg
|
|
|
|
* 2020 Red Hat Inc.
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, distribute, and sell this software and its
|
|
|
|
* documentation for any purpose is hereby granted without fee, provided that
|
|
|
|
* the above copyright notice appear in all copies and that both that copyright
|
|
|
|
* notice and this permission notice appear in supporting documentation, and
|
|
|
|
* that the name of the copyright holders not be used in advertising or
|
|
|
|
* publicity pertaining to distribution of the software without specific,
|
|
|
|
* written prior permission. The copyright holders make no representations
|
|
|
|
* about the suitability of this software for any purpose. It is provided "as
|
|
|
|
* is" without express or implied warranty.
|
|
|
|
*
|
|
|
|
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
|
|
|
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
|
|
|
|
* EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
|
|
|
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
|
|
|
|
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
|
|
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
|
|
|
* OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <gio/gunixoutputstream.h>
|
|
|
|
#include <glib-unix.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "meta/meta-selection.h"
|
|
|
|
#include "wayland/meta-wayland-data-device.h"
|
|
|
|
#include "wayland/meta-wayland-private.h"
|
|
|
|
|
|
|
|
#include "meta-wayland-data-offer.h"
|
|
|
|
|
|
|
|
#define ALL_ACTIONS (WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | \
|
|
|
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | \
|
|
|
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
|
|
|
|
|
|
|
|
static void
|
|
|
|
data_offer_accept (struct wl_client *client,
|
|
|
|
struct wl_resource *resource,
|
|
|
|
guint32 serial,
|
|
|
|
const char *mime_type)
|
|
|
|
{
|
|
|
|
MetaWaylandDataOffer *offer = wl_resource_get_user_data (resource);
|
|
|
|
|
|
|
|
/* FIXME: Check that client is currently focused by the input
|
|
|
|
* device that is currently dragging this data source. Should
|
|
|
|
* this be a wl_data_device request? */
|
|
|
|
|
|
|
|
if (offer->source)
|
|
|
|
{
|
|
|
|
meta_wayland_data_source_target (offer->source, mime_type);
|
|
|
|
meta_wayland_data_source_set_has_target (offer->source,
|
|
|
|
mime_type != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
offer->accepted = mime_type != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
transfer_cb (MetaSelection *selection,
|
|
|
|
GAsyncResult *res,
|
|
|
|
GOutputStream *stream)
|
|
|
|
{
|
|
|
|
GError *error = NULL;
|
|
|
|
|
|
|
|
if (!meta_selection_transfer_finish (selection, res, &error))
|
|
|
|
{
|
|
|
|
g_warning ("Could not fetch selection data: %s", error->message);
|
|
|
|
g_error_free (error);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_output_stream_close (stream, NULL, NULL);
|
2020-06-07 09:11:35 -04:00
|
|
|
g_object_unref (stream);
|
2020-04-09 10:22:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
data_offer_receive (struct wl_client *client, struct wl_resource *resource,
|
|
|
|
const char *mime_type, int32_t fd)
|
|
|
|
{
|
|
|
|
MetaWaylandDataOffer *offer = wl_resource_get_user_data (resource);
|
|
|
|
MetaDisplay *display = meta_get_display ();
|
|
|
|
MetaSelectionType selection_type;
|
|
|
|
GList *mime_types;
|
|
|
|
gboolean found;
|
|
|
|
|
|
|
|
selection_type = offer->selection_type;
|
|
|
|
mime_types = meta_selection_get_mimetypes (meta_display_get_selection (display),
|
|
|
|
selection_type);
|
|
|
|
found = g_list_find_custom (mime_types, mime_type, (GCompareFunc) g_strcmp0) != NULL;
|
|
|
|
g_list_free_full (mime_types, g_free);
|
|
|
|
|
|
|
|
if (found)
|
|
|
|
{
|
|
|
|
GOutputStream *stream;
|
|
|
|
|
|
|
|
stream = g_unix_output_stream_new (fd, TRUE);
|
|
|
|
meta_selection_transfer_async (meta_display_get_selection (display),
|
|
|
|
selection_type,
|
|
|
|
mime_type,
|
|
|
|
-1,
|
|
|
|
stream,
|
|
|
|
NULL,
|
|
|
|
(GAsyncReadyCallback) transfer_cb,
|
|
|
|
stream);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
close (fd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
data_offer_destroy (struct wl_client *client,
|
|
|
|
struct wl_resource *resource)
|
|
|
|
{
|
|
|
|
wl_resource_destroy (resource);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
data_offer_finish (struct wl_client *client,
|
2021-10-06 14:22:52 -04:00
|
|
|
struct wl_resource *resource)
|
2020-04-09 10:22:21 -04:00
|
|
|
{
|
|
|
|
MetaWaylandDataOffer *offer = wl_resource_get_user_data (resource);
|
|
|
|
enum wl_data_device_manager_dnd_action current_action;
|
|
|
|
|
|
|
|
if (!offer->source ||
|
|
|
|
offer != meta_wayland_data_source_get_current_offer (offer->source))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!offer->accepted || !offer->action_sent)
|
|
|
|
{
|
|
|
|
wl_resource_post_error (offer->resource,
|
|
|
|
WL_DATA_OFFER_ERROR_INVALID_FINISH,
|
|
|
|
"premature finish request");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
current_action = meta_wayland_data_source_get_current_action (offer->source);
|
|
|
|
|
|
|
|
if (current_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE ||
|
|
|
|
current_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
|
|
|
|
{
|
|
|
|
wl_resource_post_error (offer->resource,
|
|
|
|
WL_DATA_OFFER_ERROR_INVALID_OFFER,
|
|
|
|
"offer finished with an invalid action");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
meta_wayland_data_source_notify_finish (offer->source);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
data_offer_set_actions (struct wl_client *client,
|
|
|
|
struct wl_resource *resource,
|
|
|
|
uint32_t dnd_actions,
|
|
|
|
uint32_t preferred_action)
|
|
|
|
{
|
|
|
|
MetaWaylandDataOffer *offer = wl_resource_get_user_data (resource);
|
|
|
|
|
|
|
|
if (dnd_actions & ~(ALL_ACTIONS))
|
|
|
|
{
|
|
|
|
wl_resource_post_error (offer->resource,
|
|
|
|
WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK,
|
|
|
|
"invalid actions mask %x", dnd_actions);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (preferred_action &&
|
|
|
|
(!(preferred_action & dnd_actions) ||
|
|
|
|
__builtin_popcount (preferred_action) > 1))
|
|
|
|
{
|
|
|
|
wl_resource_post_error (offer->resource,
|
|
|
|
WL_DATA_OFFER_ERROR_INVALID_ACTION,
|
|
|
|
"invalid action %x", preferred_action);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
offer->dnd_actions = dnd_actions;
|
|
|
|
offer->preferred_dnd_action = preferred_action;
|
|
|
|
|
|
|
|
meta_wayland_data_offer_update_action (offer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct wl_data_offer_interface data_offer_interface = {
|
|
|
|
data_offer_accept,
|
|
|
|
data_offer_receive,
|
|
|
|
data_offer_destroy,
|
|
|
|
data_offer_finish,
|
|
|
|
data_offer_set_actions,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
|
|
destroy_data_offer (struct wl_resource *resource)
|
|
|
|
{
|
|
|
|
MetaWaylandDataOffer *offer = wl_resource_get_user_data (resource);
|
|
|
|
MetaWaylandSeat *seat;
|
|
|
|
|
|
|
|
if (offer->source)
|
|
|
|
{
|
|
|
|
seat = meta_wayland_data_source_get_seat (offer->source);
|
|
|
|
|
|
|
|
if (offer == meta_wayland_data_source_get_current_offer (offer->source))
|
|
|
|
{
|
|
|
|
if (seat->data_device.dnd_data_source == offer->source)
|
|
|
|
{
|
|
|
|
if (wl_resource_get_version (offer->resource) <
|
|
|
|
WL_DATA_OFFER_ACTION_SINCE_VERSION)
|
|
|
|
meta_wayland_data_source_notify_finish (offer->source);
|
|
|
|
else if (meta_wayland_data_source_get_drop_performed (offer->source))
|
|
|
|
meta_wayland_data_source_cancel (offer->source);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
meta_wayland_data_source_set_current_offer (offer->source, NULL);
|
|
|
|
meta_wayland_data_source_set_has_target (offer->source, FALSE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g_object_remove_weak_pointer (G_OBJECT (offer->source),
|
|
|
|
(gpointer *)&offer->source);
|
|
|
|
offer->source = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
meta_display_sync_wayland_input_focus (meta_get_display ());
|
2020-10-19 13:57:57 -04:00
|
|
|
g_free (offer);
|
2020-04-09 10:22:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
MetaWaylandDataOffer *
|
|
|
|
meta_wayland_data_offer_new (MetaSelectionType selection_type,
|
|
|
|
MetaWaylandDataSource *source,
|
|
|
|
struct wl_resource *target)
|
|
|
|
{
|
|
|
|
MetaWaylandDataOffer *offer;
|
|
|
|
|
2020-10-19 13:57:57 -04:00
|
|
|
offer = g_new0 (MetaWaylandDataOffer, 1);
|
2020-04-09 10:22:21 -04:00
|
|
|
offer->selection_type = selection_type;
|
|
|
|
offer->resource = wl_resource_create (wl_resource_get_client (target),
|
|
|
|
&wl_data_offer_interface,
|
|
|
|
wl_resource_get_version (target), 0);
|
|
|
|
wl_resource_set_implementation (offer->resource,
|
|
|
|
&data_offer_interface,
|
|
|
|
offer,
|
|
|
|
destroy_data_offer);
|
|
|
|
if (source)
|
|
|
|
{
|
|
|
|
offer->source = source;
|
|
|
|
g_object_add_weak_pointer (G_OBJECT (source), (gpointer *)&offer->source);
|
|
|
|
}
|
|
|
|
|
|
|
|
return offer;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum wl_data_device_manager_dnd_action
|
|
|
|
data_offer_choose_action (MetaWaylandDataOffer *offer)
|
|
|
|
{
|
|
|
|
MetaWaylandDataSource *source = offer->source;
|
|
|
|
uint32_t actions, user_action, available_actions;
|
|
|
|
|
|
|
|
if (wl_resource_get_version (offer->resource) <
|
|
|
|
WL_DATA_OFFER_ACTION_SINCE_VERSION)
|
|
|
|
return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
|
|
|
|
|
|
|
|
meta_wayland_data_source_get_actions (source, &actions);
|
|
|
|
user_action = meta_wayland_data_source_get_user_action (source);
|
|
|
|
|
|
|
|
available_actions = actions & offer->dnd_actions;
|
|
|
|
|
|
|
|
if (!available_actions)
|
|
|
|
return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
|
|
|
|
|
|
|
|
/* If the user is forcing an action, go for it */
|
2021-10-06 14:08:12 -04:00
|
|
|
if ((user_action & available_actions) != 0 &&
|
|
|
|
!(user_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK &&
|
|
|
|
meta_wayland_data_source_get_drop_performed (source)))
|
2020-04-09 10:22:21 -04:00
|
|
|
return user_action;
|
|
|
|
|
|
|
|
/* If the dest side has a preferred DnD action, use it */
|
|
|
|
if ((offer->preferred_dnd_action & available_actions) != 0)
|
|
|
|
return offer->preferred_dnd_action;
|
|
|
|
|
|
|
|
/* Use the first found action, in bit order */
|
|
|
|
return 1 << (ffs (available_actions) - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
meta_wayland_data_offer_update_action (MetaWaylandDataOffer *offer)
|
|
|
|
{
|
|
|
|
enum wl_data_device_manager_dnd_action current_action, action;
|
|
|
|
MetaWaylandDataSource *source;
|
|
|
|
|
|
|
|
if (!offer->source)
|
|
|
|
return;
|
|
|
|
|
|
|
|
source = offer->source;
|
|
|
|
current_action = meta_wayland_data_source_get_current_action (source);
|
|
|
|
action = data_offer_choose_action (offer);
|
|
|
|
|
|
|
|
if (current_action == action)
|
|
|
|
return;
|
|
|
|
|
|
|
|
meta_wayland_data_source_set_current_action (source, action);
|
|
|
|
|
|
|
|
if (!meta_wayland_data_source_get_in_ask (source) &&
|
|
|
|
wl_resource_get_version (offer->resource) >=
|
|
|
|
WL_DATA_OFFER_ACTION_SINCE_VERSION)
|
|
|
|
{
|
|
|
|
wl_data_offer_send_action (offer->resource, action);
|
|
|
|
offer->action_sent = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct wl_resource *
|
|
|
|
meta_wayland_data_offer_get_resource (MetaWaylandDataOffer *offer)
|
|
|
|
{
|
|
|
|
return offer->resource;
|
|
|
|
}
|
|
|
|
|
|
|
|
MetaWaylandDataSource *
|
|
|
|
meta_wayland_data_offer_get_source (MetaWaylandDataOffer *offer)
|
|
|
|
{
|
|
|
|
return offer->source;
|
|
|
|
}
|