summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/wayland/meta-wayland-data-device.c522
-rw-r--r--src/wayland/meta-wayland-data-device.h16
-rw-r--r--src/wayland/meta-wayland-versions.h2
-rw-r--r--src/wayland/meta-xwayland-selection.c180
4 files changed, 693 insertions, 27 deletions
diff --git a/src/wayland/meta-wayland-data-device.c b/src/wayland/meta-wayland-data-device.c
index 7897c446f..101db78d1 100644
--- a/src/wayland/meta-wayland-data-device.c
+++ b/src/wayland/meta-wayland-data-device.c
@@ -37,17 +37,30 @@
#include "meta-wayland-private.h"
#include "meta-dnd-actor-private.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)
+
struct _MetaWaylandDataOffer
{
struct wl_resource *resource;
MetaWaylandDataSource *source;
struct wl_listener source_destroy_listener;
+ uint32_t dnd_actions;
+ enum wl_data_device_manager_dnd_action preferred_dnd_action;
};
typedef struct _MetaWaylandDataSourcePrivate
{
+ MetaWaylandDataOffer *offer;
struct wl_array mime_types;
gboolean has_target;
+ uint32_t dnd_actions;
+ enum wl_data_device_manager_dnd_action user_dnd_action;
+ enum wl_data_device_manager_dnd_action current_dnd_action;
+ MetaWaylandSeat *seat;
+ guint actions_set : 1;
+ guint in_ask : 1;
} MetaWaylandDataSourcePrivate;
typedef struct _MetaWaylandDataSourceWayland
@@ -74,6 +87,75 @@ unbind_resource (struct wl_resource *resource)
wl_list_remove (wl_resource_get_link (resource));
}
+static gboolean
+meta_wayland_source_get_in_ask (MetaWaylandDataSource *source)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+
+ return priv->in_ask;
+}
+
+static void
+meta_wayland_source_update_in_ask (MetaWaylandDataSource *source)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+
+ priv->in_ask =
+ priv->current_dnd_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK;
+}
+
+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;
+
+ actions = meta_wayland_data_source_get_actions (source);
+ 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 */
+ if ((user_action & available_actions) != 0)
+ 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);
+}
+
+static void
+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_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);
+}
+
static void
meta_wayland_data_source_target (MetaWaylandDataSource *source,
const char *mime_type)
@@ -98,6 +180,25 @@ meta_wayland_data_source_has_target (MetaWaylandDataSource *source)
return priv->has_target;
}
+static void
+meta_wayland_data_source_set_seat (MetaWaylandDataSource *source,
+ MetaWaylandSeat *seat)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+
+ priv->seat = seat;
+}
+
+static MetaWaylandSeat *
+meta_wayland_data_source_get_seat (MetaWaylandDataSource *source)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+
+ return priv->seat;
+}
+
void
meta_wayland_data_source_set_has_target (MetaWaylandDataSource *source,
gboolean has_target)
@@ -123,6 +224,100 @@ meta_wayland_data_source_cancel (MetaWaylandDataSource *source)
META_WAYLAND_DATA_SOURCE_GET_CLASS (source)->cancel (source);
}
+uint32_t
+meta_wayland_data_source_get_actions (MetaWaylandDataSource *source)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+
+ return priv->dnd_actions;
+}
+
+enum wl_data_device_manager_dnd_action
+meta_wayland_data_source_get_user_action (MetaWaylandDataSource *source)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+
+ if (!priv->seat)
+ return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
+
+ return priv->user_dnd_action;
+}
+
+enum wl_data_device_manager_dnd_action
+meta_wayland_data_source_get_current_action (MetaWaylandDataSource *source)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+
+ return priv->current_dnd_action;
+}
+
+static void
+meta_wayland_data_source_set_current_offer (MetaWaylandDataSource *source,
+ MetaWaylandDataOffer *offer)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+
+ priv->offer = offer;
+}
+
+static MetaWaylandDataOffer *
+meta_wayland_data_source_get_current_offer (MetaWaylandDataSource *source)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+
+ return priv->offer;
+}
+
+void
+meta_wayland_data_source_set_current_action (MetaWaylandDataSource *source,
+ enum wl_data_device_manager_dnd_action action)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+
+ if (priv->current_dnd_action == action)
+ return;
+
+ priv->current_dnd_action = action;
+
+ if (!meta_wayland_source_get_in_ask (source))
+ META_WAYLAND_DATA_SOURCE_GET_CLASS (source)->action (source, action);
+}
+
+void
+meta_wayland_data_source_set_actions (MetaWaylandDataSource *source,
+ uint32_t dnd_actions)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+
+ priv->dnd_actions = dnd_actions;
+ priv->actions_set = TRUE;
+}
+
+static void
+meta_wayland_data_source_set_user_action (MetaWaylandDataSource *source,
+ enum wl_data_device_manager_dnd_action action)
+{
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+ MetaWaylandDataOffer *offer;
+
+ if (priv->user_dnd_action == action)
+ return;
+
+ priv->user_dnd_action = action;
+ offer = meta_wayland_data_source_get_current_offer (source);
+
+ if (offer)
+ data_offer_update_action (offer);
+}
+
static void
data_offer_accept (struct wl_client *client,
struct wl_resource *resource,
@@ -161,21 +356,117 @@ data_offer_destroy (struct wl_client *client, struct wl_resource *resource)
wl_resource_destroy (resource);
}
+static void
+data_offer_finish (struct wl_client *client,
+ struct wl_resource *resource)
+{
+ 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 (meta_wayland_data_source_get_seat (offer->source) ||
+ !meta_wayland_data_source_has_target (offer->source))
+ {
+ 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;
+
+ 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
+meta_wayland_data_source_notify_drop_performed (MetaWaylandDataSource *source)
+{
+ META_WAYLAND_DATA_SOURCE_GET_CLASS (source)->drop_performed (source);
+}
+
+void
+meta_wayland_data_source_notify_finish (MetaWaylandDataSource *source)
+{
+ META_WAYLAND_DATA_SOURCE_GET_CLASS (source)->drag_finished (source);
+}
+
+static void
destroy_data_offer (struct wl_resource *resource)
{
MetaWaylandDataOffer *offer = wl_resource_get_user_data (resource);
if (offer->source)
- g_object_remove_weak_pointer (G_OBJECT (offer->source),
- (gpointer *)&offer->source);
+ {
+ if (offer == meta_wayland_data_source_get_current_offer (offer->source))
+ {
+ if (wl_resource_get_version (offer->resource) <
+ WL_DATA_OFFER_ACTION_SINCE_VERSION)
+ meta_wayland_data_source_notify_finish (offer->source);
+ else
+ {
+ meta_wayland_data_source_cancel (offer->source);
+ meta_wayland_data_source_set_current_offer (offer->source, NULL);
+ }
+ }
+
+ g_object_remove_weak_pointer (G_OBJECT (offer->source),
+ (gpointer *)&offer->source);
+ offer->source = NULL;
+ }
+ meta_display_sync_wayland_input_focus (meta_get_display ());
g_slice_free (MetaWaylandDataOffer, offer);
}
@@ -203,6 +494,9 @@ meta_wayland_data_source_send_offer (MetaWaylandDataSource *source,
wl_array_for_each (p, &priv->mime_types)
wl_data_offer_send_offer (offer->resource, *p);
+ data_offer_update_action (offer);
+ meta_wayland_data_source_set_current_offer (source, offer);
+
return offer->resource;
}
@@ -222,14 +516,56 @@ data_source_destroy (struct wl_client *client, struct wl_resource *resource)
wl_resource_destroy (resource);
}
+static void
+data_source_set_actions (struct wl_client *client,
+ struct wl_resource *resource,
+ uint32_t dnd_actions)
+{
+ MetaWaylandDataSource *source = wl_resource_get_user_data (resource);
+ MetaWaylandDataSourcePrivate *priv =
+ meta_wayland_data_source_get_instance_private (source);
+ MetaWaylandDataSourceWayland *source_wayland =
+ META_WAYLAND_DATA_SOURCE_WAYLAND (source);
+
+ if (priv->actions_set)
+ {
+ wl_resource_post_error (source_wayland->resource,
+ WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK,
+ "cannot set actions more than once");
+ return;
+ }
+
+ if (dnd_actions & ~(ALL_ACTIONS))
+ {
+ wl_resource_post_error (source_wayland->resource,
+ WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK,
+ "invalid actions mask %x", dnd_actions);
+ return;
+ }
+
+ if (meta_wayland_data_source_get_seat (source))
+ {
+ wl_resource_post_error (source_wayland->resource,
+ WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK,
+ "invalid action change after "
+ "wl_data_device.start_drag");
+ return;
+ }
+
+ meta_wayland_data_source_set_actions (source, dnd_actions);
+}
+
static struct wl_data_source_interface data_source_interface = {
data_source_offer,
- data_source_destroy
+ data_source_destroy,
+ data_source_set_actions
};
struct _MetaWaylandDragGrab {
MetaWaylandPointerGrab generic;
+ MetaWaylandKeyboardGrab keyboard_grab;
+
MetaWaylandSeat *seat;
struct wl_client *drag_client;
@@ -248,6 +584,7 @@ struct _MetaWaylandDragGrab {
struct wl_listener drag_origin_listener;
int drag_start_x, drag_start_y;
+ ClutterModifierType buttons;
};
static void
@@ -256,6 +593,7 @@ destroy_drag_focus (struct wl_listener *listener, void *data)
MetaWaylandDragGrab *grab = wl_container_of (listener, grab, drag_focus_listener);
grab->drag_focus_data_device = NULL;
+ grab->drag_focus = NULL;
}
static void
@@ -292,6 +630,9 @@ meta_wayland_drag_grab_set_focus (MetaWaylandDragGrab *drag_grab,
drag_grab->drag_focus = NULL;
}
+ if (drag_grab->drag_data_source)
+ meta_wayland_data_source_set_current_offer (drag_grab->drag_data_source, NULL);
+
if (!surface)
return;
@@ -330,6 +671,22 @@ drag_grab_focus (MetaWaylandPointerGrab *grab,
}
static void
+data_source_update_user_dnd_action (MetaWaylandDataSource *source,
+ ClutterModifierType modifiers)
+{
+ enum wl_data_device_manager_dnd_action user_dnd_action = 0;
+
+ if (modifiers & CLUTTER_SHIFT_MASK)
+ user_dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
+ else if (modifiers & CLUTTER_CONTROL_MASK)
+ user_dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+ else if (modifiers & (CLUTTER_MOD1_MASK | CLUTTER_BUTTON2_MASK))
+ user_dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK;
+
+ meta_wayland_data_source_set_user_action (source, user_dnd_action);
+}
+
+static void
drag_grab_motion (MetaWaylandPointerGrab *grab,
const ClutterEvent *event)
{
@@ -370,7 +727,15 @@ data_device_end_drag_grab (MetaWaylandDragGrab *drag_grab)
drag_grab->seat->data_device.current_grab = NULL;
- meta_wayland_pointer_end_grab (drag_grab->generic.pointer);
+ /* There might be other grabs created in result to DnD actions like popups
+ * on "ask" actions, we must not reset those, only our own.
+ */
+ if (drag_grab->generic.pointer->grab == (MetaWaylandPointerGrab *) drag_grab)
+ {
+ meta_wayland_pointer_end_grab (drag_grab->generic.pointer);
+ meta_wayland_keyboard_end_grab (drag_grab->keyboard_grab.keyboard);
+ }
+
g_slice_free (MetaWaylandDragGrab, drag_grab);
}
@@ -385,17 +750,33 @@ drag_grab_button (MetaWaylandPointerGrab *grab,
if (drag_grab->generic.pointer->grab_button == clutter_event_get_button (event) &&
event_type == CLUTTER_BUTTON_RELEASE)
{
- gboolean success = FALSE;
+ MetaWaylandDataSource *source = drag_grab->drag_data_source;
+ gboolean success;
- if (meta_wayland_data_source_has_target (drag_grab->drag_data_source))
+ if (drag_grab->drag_focus && source &&
+ meta_wayland_data_source_has_target (source) &&
+ meta_wayland_data_source_get_current_action (source))
{
+ /* Detach the data source from the grab, it's meant to live longer */
+ meta_wayland_drag_grab_set_source (drag_grab, NULL);
+ meta_wayland_data_source_set_seat (source, NULL);
+
meta_wayland_surface_drag_dest_drop (drag_grab->drag_focus);
+ meta_wayland_data_source_notify_drop_performed (source);
+
+ meta_wayland_source_update_in_ask (source);
success = TRUE;
}
+ else
+ {
+ meta_wayland_data_source_cancel (source);
+ meta_wayland_data_source_set_current_offer (source, NULL);
+ meta_wayland_data_device_set_dnd_source (&seat->data_device, NULL);
+ success= FALSE;
+ }
/* Finish drag and let actor self-destruct */
- meta_dnd_actor_drag_finish (META_DND_ACTOR (drag_grab->feedback_actor),
- success);
+ meta_dnd_actor_drag_finish (META_DND_ACTOR (drag_grab->feedback_actor), success);
drag_grab->feedback_actor = NULL;
}
@@ -410,6 +791,40 @@ static const MetaWaylandPointerGrabInterface drag_grab_interface = {
drag_grab_button,
};
+static gboolean
+keyboard_drag_grab_key (MetaWaylandKeyboardGrab *grab,
+ const ClutterEvent *event)
+{
+ return FALSE;
+}
+
+static void
+keyboard_drag_grab_modifiers (MetaWaylandKeyboardGrab *grab,
+ ClutterModifierType modifiers)
+{
+ MetaWaylandDragGrab *drag_grab;
+
+ drag_grab = wl_container_of (grab, drag_grab, keyboard_grab);
+
+ /* The modifiers here just contain keyboard modifiers, mix it with the
+ * mouse button modifiers we got when starting the drag operation.
+ */
+ modifiers |= drag_grab->buttons;
+
+ if (drag_grab->drag_data_source)
+ {
+ data_source_update_user_dnd_action (drag_grab->drag_data_source, modifiers);
+
+ if (drag_grab->drag_focus)
+ meta_wayland_surface_drag_dest_update (drag_grab->drag_focus);
+ }
+}
+
+static const MetaWaylandKeyboardGrabInterface keyboard_drag_grab_interface = {
+ keyboard_drag_grab_key,
+ keyboard_drag_grab_modifiers
+};
+
static void
destroy_data_device_origin (struct wl_listener *listener, void *data)
{
@@ -454,12 +869,16 @@ meta_wayland_data_device_start_drag (MetaWaylandDataDevice *data
MetaWaylandSeat *seat = wl_container_of (data_device, seat, data_device);
MetaWaylandDragGrab *drag_grab;
ClutterPoint pos, stage_pos;
+ ClutterModifierType modifiers;
data_device->current_grab = drag_grab = g_slice_new0 (MetaWaylandDragGrab);
drag_grab->generic.interface = funcs;
drag_grab->generic.pointer = &seat->pointer;
+ drag_grab->keyboard_grab.interface = &keyboard_drag_grab_interface;
+ drag_grab->keyboard_grab.keyboard = &seat->keyboard;
+
drag_grab->drag_client = client;
drag_grab->seat = seat;
@@ -474,9 +893,15 @@ meta_wayland_data_device_start_drag (MetaWaylandDataDevice *data
drag_grab->drag_start_x = stage_pos.x;
drag_grab->drag_start_y = stage_pos.y;
+ modifiers = clutter_input_device_get_modifier_state (seat->pointer.device);
+ drag_grab->buttons = modifiers &
+ (CLUTTER_BUTTON1_MASK | CLUTTER_BUTTON2_MASK | CLUTTER_BUTTON3_MASK |
+ CLUTTER_BUTTON4_MASK | CLUTTER_BUTTON5_MASK);
+
meta_wayland_drag_grab_set_source (drag_grab, source);
meta_wayland_data_device_set_dnd_source (data_device,
drag_grab->drag_data_source);
+ data_source_update_user_dnd_action (source, modifiers);
if (icon_surface)
{
@@ -500,6 +925,7 @@ meta_wayland_data_device_start_drag (MetaWaylandDataDevice *data
}
meta_wayland_pointer_start_grab (&seat->pointer, (MetaWaylandPointerGrab*) drag_grab);
+ meta_wayland_data_source_set_seat (source, seat);
}
void
@@ -558,6 +984,10 @@ data_device_start_drag (struct wl_client *client,
meta_wayland_data_device_start_drag (data_device, client,
&drag_grab_interface,
surface, drag_source, icon_surface);
+
+ meta_wayland_keyboard_set_focus (&seat->keyboard, NULL);
+ meta_wayland_keyboard_start_grab (&seat->keyboard,
+ &seat->data_device.current_grab->keyboard_grab);
}
static void
@@ -611,6 +1041,47 @@ meta_wayland_source_cancel (MetaWaylandDataSource *source)
}
static void
+meta_wayland_source_action (MetaWaylandDataSource *source,
+ enum wl_data_device_manager_dnd_action action)
+{
+ MetaWaylandDataSourceWayland *source_wayland =
+ META_WAYLAND_DATA_SOURCE_WAYLAND (source);
+
+ if (wl_resource_get_version (source_wayland->resource) >=
+ WL_DATA_SOURCE_ACTION_SINCE_VERSION)
+ wl_data_source_send_action (source_wayland->resource, action);
+}
+
+static void
+meta_wayland_source_drop_performed (MetaWaylandDataSource *source)
+{
+ MetaWaylandDataSourceWayland *source_wayland =
+ META_WAYLAND_DATA_SOURCE_WAYLAND (source);
+
+ if (wl_resource_get_version (source_wayland->resource) >=
+ WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION)
+ wl_data_source_send_dnd_drop_performed (source_wayland->resource);
+}
+
+static void
+meta_wayland_source_drag_finished (MetaWaylandDataSource *source)
+{
+ MetaWaylandDataSourceWayland *source_wayland =
+ META_WAYLAND_DATA_SOURCE_WAYLAND (source);
+ enum wl_data_device_manager_dnd_action action;
+
+ if (meta_wayland_source_get_in_ask (source))
+ {
+ action = meta_wayland_data_source_get_current_action (source);
+ meta_wayland_source_action (source, action);
+ }
+
+ if (wl_resource_get_version (source_wayland->resource) >=
+ WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION)
+ wl_data_source_send_dnd_finished (source_wayland->resource);
+}
+
+static void
meta_wayland_source_finalize (GObject *object)
{
G_OBJECT_CLASS (meta_wayland_data_source_parent_class)->finalize (object);
@@ -633,6 +1104,9 @@ meta_wayland_data_source_wayland_class_init (MetaWaylandDataSourceWaylandClass *
data_source_class->send = meta_wayland_source_send;
data_source_class->target = meta_wayland_source_target;
data_source_class->cancel = meta_wayland_source_cancel;
+ data_source_class->action = meta_wayland_source_action;
+ data_source_class->drop_performed = meta_wayland_source_drop_performed;
+ data_source_class->drag_finished = meta_wayland_source_drag_finished;
}
static void
@@ -657,6 +1131,7 @@ meta_wayland_data_source_init (MetaWaylandDataSource *source)
meta_wayland_data_source_get_instance_private (source);
wl_array_init (&priv->mime_types);
+ priv->current_dnd_action = -1;
}
static void
@@ -675,6 +1150,7 @@ meta_wayland_drag_dest_focus_in (MetaWaylandDataDevice *data_device,
MetaWaylandDragGrab *grab = data_device->current_grab;
struct wl_display *display;
struct wl_client *client;
+ uint32_t source_actions;
wl_fixed_t sx, sy;
if (!grab->drag_focus_data_device)
@@ -687,6 +1163,13 @@ meta_wayland_drag_dest_focus_in (MetaWaylandDataDevice *data_device,
wl_resource_add_destroy_listener (grab->drag_focus_data_device,
&grab->drag_focus_listener);
+ if (wl_resource_get_version (offer->resource) >=
+ WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION)
+ {
+ source_actions = meta_wayland_data_source_get_actions (offer->source);
+ wl_data_offer_send_source_actions (offer->resource, source_actions);
+ }
+
meta_wayland_pointer_get_relative_coordinates (grab->generic.pointer,
surface, &sx, &sy);
wl_data_device_send_enter (grab->drag_focus_data_device,
@@ -732,11 +1215,18 @@ meta_wayland_drag_dest_drop (MetaWaylandDataDevice *data_device,
wl_data_device_send_drop (grab->drag_focus_data_device);
}
+static void
+meta_wayland_drag_dest_update (MetaWaylandDataDevice *data_device,
+ MetaWaylandSurface *surface)
+{
+}
+
static const MetaWaylandDragDestFuncs meta_wayland_drag_dest_funcs = {
meta_wayland_drag_dest_focus_in,
meta_wayland_drag_dest_focus_out,
meta_wayland_drag_dest_motion,
- meta_wayland_drag_dest_drop
+ meta_wayland_drag_dest_drop,
+ meta_wayland_drag_dest_update
};
const MetaWaylandDragDestFuncs *
@@ -825,6 +1315,7 @@ data_device_set_selection (struct wl_client *client,
guint32 serial)
{
MetaWaylandDataDevice *data_device = wl_resource_get_user_data (resource);
+ MetaWaylandDataSourcePrivate *priv;
MetaWaylandDataSource *source;
if (source_resource)
@@ -832,6 +1323,19 @@ data_device_set_selection (struct wl_client *client,
else
source = NULL;
+ if (source)
+ {
+ priv = meta_wayland_data_source_get_instance_private (source);
+
+ if (priv->actions_set)
+ {
+ wl_resource_post_error(source_resource,
+ WL_DATA_SOURCE_ERROR_INVALID_SOURCE,
+ "cannot set drag-and-drop source as selection");
+ return;
+ }
+ }
+
/* FIXME: Store serial and check against incoming serial here. */
meta_wayland_data_device_set_selection (data_device, source, serial);
}
diff --git a/src/wayland/meta-wayland-data-device.h b/src/wayland/meta-wayland-data-device.h
index 483b08b2a..67057fd25 100644
--- a/src/wayland/meta-wayland-data-device.h
+++ b/src/wayland/meta-wayland-data-device.h
@@ -45,6 +45,11 @@ struct _MetaWaylandDataSourceClass
void (* target) (MetaWaylandDataSource *source,
const gchar *mime_type);
void (* cancel) (MetaWaylandDataSource *source);
+
+ void (* action) (MetaWaylandDataSource *source,
+ uint32_t action);
+ void (* drop_performed) (MetaWaylandDataSource *source);
+ void (* drag_finished) (MetaWaylandDataSource *source);
};
struct _MetaWaylandDataDevice
@@ -95,6 +100,17 @@ void meta_wayland_data_source_send (MetaWaylandDataSource *source,
const gchar *mime_type,
gint fd);
+void meta_wayland_data_source_notify_finish (MetaWaylandDataSource *source);
+
+uint32_t meta_wayland_data_source_get_actions (MetaWaylandDataSource *source);
+uint32_t meta_wayland_data_source_get_user_action (MetaWaylandDataSource *source);
+uint32_t meta_wayland_data_source_get_current_action (MetaWaylandDataSource *source);
+
+void meta_wayland_data_source_set_actions (MetaWaylandDataSource *source,
+ uint32_t dnd_actions);
+void meta_wayland_data_source_set_current_action (MetaWaylandDataSource *source,
+ uint32_t action);
+
const MetaWaylandDragDestFuncs *
meta_wayland_data_device_get_drag_dest_funcs (void);
diff --git a/src/wayland/meta-wayland-versions.h b/src/wayland/meta-wayland-versions.h
index 91a82af7c..e75d98752 100644
--- a/src/wayland/meta-wayland-versions.h
+++ b/src/wayland/meta-wayland-versions.h
@@ -36,7 +36,7 @@
/* Global/master objects (version exported by wl_registry and negotiated through bind) */
#define META_WL_COMPOSITOR_VERSION 3
-#define META_WL_DATA_DEVICE_MANAGER_VERSION 2
+#define META_WL_DATA_DEVICE_MANAGER_VERSION 3
#define META_XDG_SHELL_VERSION 1
#define META_WL_SHELL_VERSION 1
#define META_WL_SEAT_VERSION 5
diff --git a/src/wayland/meta-xwayland-selection.c b/src/wayland/meta-xwayland-selection.c
index 081a24439..5d3405ad8 100644
--- a/src/wayland/meta-xwayland-selection.c
+++ b/src/wayland/meta-xwayland-selection.c
@@ -108,6 +108,7 @@ enum {
ATOM_DND_ACTION_MOVE,
ATOM_DND_ACTION_COPY,
ATOM_DND_ACTION_ASK,
+ ATOM_DND_ACTION_PRIVATE,
N_DND_ATOMS
};
@@ -126,6 +127,7 @@ const gchar *atom_names[] = {
"XdndActionMove",
"XdndActionCopy",
"XdndActionAsk",
+ "XdndActionPrivate",
NULL
};
@@ -135,6 +137,33 @@ G_DEFINE_TYPE (MetaWaylandDataSourceXWayland, meta_wayland_data_source_xwayland,
META_TYPE_WAYLAND_DATA_SOURCE);
/* XDND helpers */
+static Atom
+action_to_atom (uint32_t action)
+{
+ if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
+ return xdnd_atoms[ATOM_DND_ACTION_COPY];
+ else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
+ return xdnd_atoms[ATOM_DND_ACTION_MOVE];
+ else if (action & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)
+ return xdnd_atoms[ATOM_DND_ACTION_ASK];
+ else
+ return None;
+}
+
+static enum wl_data_device_manager_dnd_action
+atom_to_action (Atom atom)
+{
+ if (atom == xdnd_atoms[ATOM_DND_ACTION_COPY] ||
+ atom == xdnd_atoms[ATOM_DND_ACTION_PRIVATE])
+ return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+ else if (atom == xdnd_atoms[ATOM_DND_ACTION_MOVE])
+ return WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
+ else if (atom == xdnd_atoms[ATOM_DND_ACTION_ASK])
+ return WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK;
+ else
+ return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
+}
+
static void
xdnd_send_enter (MetaXWaylandSelection *selection_data,
Window dest)
@@ -217,10 +246,21 @@ xdnd_send_position (MetaXWaylandSelection *selection_data,
int x,
int y)
{
+ MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaSelectionBridge *selection = &selection_data->dnd.selection;
+ MetaWaylandDataSource *source = compositor->seat->data_device.dnd_data_source;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+ uint32_t action = 0, user_action, actions;
XEvent xev = { 0 };
+ user_action = meta_wayland_data_source_get_user_action (source);
+ actions = meta_wayland_data_source_get_actions (source);
+
+ if (user_action & actions)
+ action = user_action;
+ if (!action)
+ action = actions;
+
xev.xclient.type = ClientMessage;
xev.xclient.message_type = xdnd_atoms[ATOM_DND_POSITION];
xev.xclient.format = 32;
@@ -230,7 +270,7 @@ xdnd_send_position (MetaXWaylandSelection *selection_data,
xev.xclient.data.l[1] = 0;
xev.xclient.data.l[2] = (x << 16) | y;
xev.xclient.data.l[3] = time;
- xev.xclient.data.l[4] = xdnd_atoms[ATOM_DND_ACTION_COPY];
+ xev.xclient.data.l[4] = action_to_atom (action);
XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}
@@ -262,6 +302,8 @@ xdnd_send_finished (MetaXWaylandSelection *selection_data,
{
MetaDndBridge *selection = &selection_data->dnd;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+ MetaWaylandDataSource *source = selection_data->dnd.selection.source;
+ uint32_t action = 0;
XEvent xev = { 0 };
xev.xclient.type = ClientMessage;
@@ -273,8 +315,9 @@ xdnd_send_finished (MetaXWaylandSelection *selection_data,
if (accepted)
{
+ action = meta_wayland_data_source_get_current_action (source);
xev.xclient.data.l[1] = 1; /* Drop successful */
- xev.xclient.data.l[2] = xdnd_atoms[ATOM_DND_ACTION_COPY];
+ xev.xclient.data.l[2] = action_to_atom (action);
}
XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
@@ -283,7 +326,7 @@ xdnd_send_finished (MetaXWaylandSelection *selection_data,
static void
xdnd_send_status (MetaXWaylandSelection *selection_data,
Window dest,
- gboolean accepted)
+ uint32_t action)
{
MetaDndBridge *selection = &selection_data->dnd;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
@@ -296,12 +339,10 @@ xdnd_send_status (MetaXWaylandSelection *selection_data,
xev.xclient.data.l[0] = selection->dnd_window;
xev.xclient.data.l[1] = 1 << 1; /* Bit 2: dest wants XdndPosition messages */
+ xev.xclient.data.l[4] = action_to_atom (action);
- if (accepted)
- {
- xev.xclient.data.l[1] |= 1 << 0; /* Bit 1: dest accepts the drop */
- xev.xclient.data.l[4] = xdnd_atoms[ATOM_DND_ACTION_COPY];
- }
+ if (xev.xclient.data.l[4])
+ xev.xclient.data.l[1] |= 1 << 0; /* Bit 1: dest accepts the drop */
XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
}
@@ -388,6 +429,32 @@ x11_selection_data_free (X11SelectionData *data)
}
static void
+x11_selection_data_send_finished (MetaSelectionBridge *selection,
+ gboolean success)
+{
+ uint32_t action = meta_wayland_data_source_get_current_action (selection->source);
+
+ if (!selection->x11_selection)
+ return;
+
+ if (success && action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
+ {
+ Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
+
+ /* Request data deletion on the drag source */
+ XConvertSelection (xdisplay,
+ selection->selection_atom,
+ gdk_x11_get_xatom_by_name ("DELETE"),
+ gdk_x11_get_xatom_by_name ("_META_SELECTION"),
+ selection->window,
+ CurrentTime);
+ }
+
+ xdnd_send_finished (selection->x11_selection->selection_data,
+ selection->owner, success);
+}
+
+static void
x11_selection_data_finish (MetaSelectionBridge *selection,
gboolean success)
{
@@ -395,14 +462,19 @@ x11_selection_data_finish (MetaSelectionBridge *selection,
return;
if (selection == &selection->x11_selection->selection_data->dnd.selection)
- xdnd_send_finished (selection->x11_selection->selection_data,
- selection->owner, success);
+ x11_selection_data_send_finished (selection, success);
g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free);
}
static void
+x11_selection_data_close (X11SelectionData *data)
+{
+ g_output_stream_close (data->stream, data->cancellable, NULL);
+}
+
+static void
x11_data_write_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
@@ -435,6 +507,7 @@ x11_data_write_cb (GObject *object,
}
else
{
+ x11_selection_data_close (selection->x11_selection);
x11_selection_data_finish (selection, success);
}
}
@@ -693,6 +766,7 @@ meta_xwayland_selection_get_incr_chunk (MetaWaylandCompositor *compositor,
else
{
/* Transfer has completed */
+ x11_selection_data_close (selection->x11_selection);
x11_selection_data_finish (selection, TRUE);
}
@@ -741,11 +815,15 @@ meta_x11_source_target (MetaWaylandDataSource *source,
MetaWaylandDataSourceXWayland *source_xwayland =
META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
MetaSelectionBridge *selection = source_xwayland->selection;
+ uint32_t action = 0;
if (selection->selection_atom == xdnd_atoms[ATOM_DND_SELECTION])
{
+ if (mime_type)
+ action = meta_wayland_data_source_get_current_action (source);
+
xdnd_send_status (compositor->xwayland_manager.selection_data,
- selection->owner, mime_type != NULL);
+ selection->owner, action);
}
}
@@ -756,11 +834,47 @@ meta_x11_source_cancel (MetaWaylandDataSource *source)
META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
MetaSelectionBridge *selection = source_xwayland->selection;
+ x11_selection_data_send_finished (selection, FALSE);
g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free);
}
static void
+meta_x11_source_action (MetaWaylandDataSource *source,
+ uint32_t action)
+{
+ MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
+ MetaWaylandDataSourceXWayland *source_xwayland =
+ META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
+ MetaSelectionBridge *selection = source_xwayland->selection;
+
+ if (selection->selection_atom == xdnd_atoms[ATOM_DND_SELECTION])
+ {
+ if (!meta_wayland_data_source_has_target (source))
+ action = 0;
+
+ xdnd_send_status (compositor->xwayland_manager.selection_data,
+ selection->owner, action);
+ }
+}
+
+static void
+meta_x11_source_drop_performed (MetaWaylandDataSource *source)
+{
+}
+
+static void
+meta_x11_source_drag_finished (MetaWaylandDataSource *source)
+{
+ MetaWaylandDataSourceXWayland *source_xwayland =
+ META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
+ MetaSelectionBridge *selection = source_xwayland->selection;
+
+ if (selection->x11_selection)
+ x11_selection_data_send_finished (selection, TRUE);
+}
+
+static void
meta_wayland_data_source_xwayland_init (MetaWaylandDataSourceXWayland *source_xwayland)
{
}
@@ -774,6 +888,9 @@ meta_wayland_data_source_xwayland_class_init (MetaWaylandDataSourceXWaylandClass
data_source_class->send = meta_x11_source_send;
data_source_class->target = meta_x11_source_target;
data_source_class->cancel = meta_x11_source_cancel;
+ data_source_class->action = meta_x11_source_action;
+ data_source_class->drop_performed = meta_x11_source_drop_performed;
+ data_source_class->drag_finished = meta_x11_source_drag_finished;
}
static MetaWaylandDataSource *
@@ -837,11 +954,27 @@ meta_x11_drag_dest_drop (MetaWaylandDataDevice *data_device,
meta_display_get_current_time_roundtrip (meta_get_display ()));
}
+static void
+meta_x11_drag_dest_update (MetaWaylandDataDevice *data_device,
+ MetaWaylandSurface *surface)
+{
+ MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
+ MetaWaylandSeat *seat = compositor->seat;
+ ClutterPoint pos;
+
+ clutter_input_device_get_coords (seat->pointer.device, NULL, &pos);
+ xdnd_send_position (compositor->xwayland_manager.selection_data,
+ compositor->xwayland_manager.selection_data->dnd.dnd_dest,
+ clutter_get_current_event_time (),
+ pos.x, pos.y);
+}
+
static const MetaWaylandDragDestFuncs meta_x11_drag_dest_funcs = {
meta_x11_drag_dest_focus_in,
meta_x11_drag_dest_focus_out,
meta_x11_drag_dest_motion,
- meta_x11_drag_dest_drop
+ meta_x11_drag_dest_drop,
+ meta_x11_drag_dest_update
};
const MetaWaylandDragDestFuncs *
@@ -1135,6 +1268,10 @@ meta_xwayland_selection_handle_selection_request (MetaWaylandCompositor *composi
selection->timestamp);
reply_selection_request (event, TRUE);
}
+ else if (data_source && event->target == gdk_x11_get_xatom_by_name ("DELETE"))
+ {
+ reply_selection_request (event, TRUE);
+ }
else
{
if (data_source &&
@@ -1257,6 +1394,7 @@ meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor
if (event->window == dnd->selection.window)
{
MetaWaylandDataSource *data_source;
+ uint32_t action = 0;
data_source = compositor->seat->data_device.dnd_data_source;
@@ -1269,6 +1407,11 @@ meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor
meta_wayland_data_source_set_has_target (data_source,
(event->data.l[1] & 1) != 0);
+ /* data.l[4] contains the action atom */
+ if (event->data.l[4])
+ action = atom_to_action ((Atom) event->data.l[4]);
+
+ meta_wayland_data_source_set_current_action (data_source, action);
return TRUE;
}
else if (event->message_type == xdnd_atoms[ATOM_DND_FINISHED])
@@ -1277,8 +1420,7 @@ meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor
if (compositor->seat->data_device.current_grab)
return FALSE;
- meta_wayland_data_device_set_dnd_source (&compositor->seat->data_device,
- NULL);
+ meta_wayland_data_source_notify_finish (data_source);
return TRUE;
}
}
@@ -1290,7 +1432,8 @@ meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor
MetaWaylandDragGrab *drag_grab = compositor->seat->data_device.current_grab;
MetaWaylandSurface *drag_focus = meta_wayland_drag_grab_get_focus (drag_grab);
- if (!drag_focus)
+ if (!drag_focus &&
+ event->message_type != xdnd_atoms[ATOM_DND_ENTER])
return FALSE;
if (event->message_type == xdnd_atoms[ATOM_DND_ENTER])
@@ -1338,6 +1481,7 @@ meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor
{
ClutterEvent *motion;
ClutterPoint pos;
+ uint32_t action = 0;
motion = clutter_event_new (CLUTTER_MOTION);
clutter_input_device_get_coords (seat->pointer.device, NULL, &pos);
@@ -1346,11 +1490,13 @@ meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor
clutter_event_set_source_device (motion, seat->pointer.device);
clutter_event_set_time (motion, dnd->last_motion_time);
+ action = atom_to_action ((Atom) event->data.l[4]);
+ meta_wayland_data_source_set_actions (dnd->selection.source, action);
+
meta_wayland_surface_drag_dest_motion (drag_focus, motion);
xdnd_send_status (compositor->xwayland_manager.selection_data,
(Window) event->data.l[0],
- meta_wayland_data_source_has_target (
- dnd->selection.source));
+ meta_wayland_data_source_get_current_action (dnd->selection.source));
clutter_event_free (motion);
return TRUE;