wayland: Implement DnD actions as per wl_data_device v3 changes

We now additionally send:
  - wl_data_offer.source_actions
  - wl_data_source.action
  - wl_data_offer.action
  - wl_data_source.drop_performed
  - wl_data_source.drag_finished

The protocol changes allow for compositors to implement different policies
when chosing the action, mutter uses this to reimplement the same behavior
that GTK+ traditionally had:

  - Alt/Control/Shift modifiers change the chosen action to
    ask/copy/move respectively
  - Drags with middle button start out as "ask" by default

As mutter now also grabs the keyboard and unsets the window focus for these
purposes, the window focus is restored after the drag operation has
finished.
This commit is contained in:
Carlos Garnacho 2015-04-07 16:05:46 +02:00
parent ce78db31b7
commit 319d0bb679
4 changed files with 512 additions and 23 deletions

View File

@ -42,12 +42,18 @@ struct _MetaWaylandDataOffer
struct wl_resource *resource; struct wl_resource *resource;
MetaWaylandDataSource *source; MetaWaylandDataSource *source;
struct wl_listener source_destroy_listener; struct wl_listener source_destroy_listener;
uint32_t dnd_actions;
uint32_t preferred_dnd_action;
}; };
typedef struct _MetaWaylandDataSourcePrivate typedef struct _MetaWaylandDataSourcePrivate
{ {
MetaWaylandDataOffer *offer;
struct wl_array mime_types; struct wl_array mime_types;
gboolean has_target; gboolean has_target;
uint32_t dnd_actions;
uint32_t user_dnd_action;
uint32_t current_dnd_action;
} MetaWaylandDataSourcePrivate; } MetaWaylandDataSourcePrivate;
typedef struct _MetaWaylandDataSourceWayland typedef struct _MetaWaylandDataSourceWayland
@ -74,6 +80,50 @@ unbind_resource (struct wl_resource *resource)
wl_list_remove (wl_resource_get_link (resource)); wl_list_remove (wl_resource_get_link (resource));
} }
static uint32_t
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 0;
/* 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)
{
uint32_t current_action, action;
if (!offer->source)
return;
current_action = meta_wayland_data_source_get_current_action (offer->source);
action = data_offer_choose_action (offer);
if (current_action == action)
return;
meta_wayland_data_source_set_current_action (offer->source, action);
wl_data_offer_send_action (offer->resource, action);
}
static void static void
meta_wayland_data_source_target (MetaWaylandDataSource *source, meta_wayland_data_source_target (MetaWaylandDataSource *source,
const char *mime_type) const char *mime_type)
@ -123,6 +173,106 @@ meta_wayland_data_source_cancel (MetaWaylandDataSource *source)
META_WAYLAND_DATA_SOURCE_GET_CLASS (source)->cancel (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;
}
uint32_t
meta_wayland_data_source_get_user_action (MetaWaylandDataSource *source)
{
MetaWaylandDataSourcePrivate *priv =
meta_wayland_data_source_get_instance_private (source);
return priv->user_dnd_action;
}
uint32_t
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,
uint32_t action)
{
MetaWaylandDataSourcePrivate *priv =
meta_wayland_data_source_get_instance_private (source);
if (priv->current_dnd_action == action)
return;
priv->current_dnd_action = action;
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);
MetaWaylandDataOffer *offer;
if (priv->dnd_actions == dnd_actions)
return;
priv->dnd_actions = dnd_actions;
offer = meta_wayland_data_source_get_current_offer (source);
if (offer)
{
wl_data_offer_send_source_actions (offer->resource,
priv->dnd_actions);
data_offer_update_action (offer);
}
}
static void
meta_wayland_data_source_set_user_action (MetaWaylandDataSource *source,
uint32_t 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 static void
data_offer_accept (struct wl_client *client, data_offer_accept (struct wl_client *client,
struct wl_resource *resource, struct wl_resource *resource,
@ -161,21 +311,59 @@ data_offer_destroy (struct wl_client *client, struct wl_resource *resource)
wl_resource_destroy (resource); wl_resource_destroy (resource);
} }
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 (offer->dnd_actions == dnd_actions &&
offer->preferred_dnd_action == 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 = { static const struct wl_data_offer_interface data_offer_interface = {
data_offer_accept, data_offer_accept,
data_offer_receive, data_offer_receive,
data_offer_destroy, data_offer_destroy,
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 static void
destroy_data_offer (struct wl_resource *resource) destroy_data_offer (struct wl_resource *resource)
{ {
MetaWaylandDataOffer *offer = wl_resource_get_user_data (resource); MetaWaylandDataOffer *offer = wl_resource_get_user_data (resource);
if (offer->source) 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))
meta_wayland_data_source_notify_finish (offer->source);
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); g_slice_free (MetaWaylandDataOffer, offer);
} }
@ -203,6 +391,9 @@ meta_wayland_data_source_send_offer (MetaWaylandDataSource *source,
wl_array_for_each (p, &priv->mime_types) wl_array_for_each (p, &priv->mime_types)
wl_data_offer_send_offer (offer->resource, *p); 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; return offer->resource;
} }
@ -222,14 +413,27 @@ data_source_destroy (struct wl_client *client, struct wl_resource *resource)
wl_resource_destroy (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);
meta_wayland_data_source_set_actions (source, dnd_actions);
}
static struct wl_data_source_interface data_source_interface = { static struct wl_data_source_interface data_source_interface = {
data_source_offer, data_source_offer,
data_source_destroy data_source_destroy,
data_source_set_actions
}; };
struct _MetaWaylandDragGrab { struct _MetaWaylandDragGrab {
MetaWaylandPointerGrab generic; MetaWaylandPointerGrab generic;
MetaWaylandKeyboardGrab keyboard_grab;
MetaWaylandSeat *seat; MetaWaylandSeat *seat;
struct wl_client *drag_client; struct wl_client *drag_client;
@ -248,6 +452,7 @@ struct _MetaWaylandDragGrab {
struct wl_listener drag_origin_listener; struct wl_listener drag_origin_listener;
int drag_start_x, drag_start_y; int drag_start_x, drag_start_y;
ClutterModifierType buttons;
}; };
static void static void
@ -256,6 +461,7 @@ destroy_drag_focus (struct wl_listener *listener, void *data)
MetaWaylandDragGrab *grab = wl_container_of (listener, grab, drag_focus_listener); MetaWaylandDragGrab *grab = wl_container_of (listener, grab, drag_focus_listener);
grab->drag_focus_data_device = NULL; grab->drag_focus_data_device = NULL;
grab->drag_focus = NULL;
} }
void void
@ -285,6 +491,7 @@ meta_wayland_drag_grab_set_focus (MetaWaylandDragGrab *drag_grab,
client = wl_resource_get_client (surface->resource); client = wl_resource_get_client (surface->resource);
data_device_resource = wl_resource_find_for_client (&seat->data_device.resource_list, client); data_device_resource = wl_resource_find_for_client (&seat->data_device.resource_list, client);
meta_wayland_data_source_set_current_offer (drag_grab->drag_data_source, NULL);
if (drag_grab->drag_data_source && data_device_resource) if (drag_grab->drag_data_source && data_device_resource)
offer = meta_wayland_data_source_send_offer (drag_grab->drag_data_source, offer = meta_wayland_data_source_send_offer (drag_grab->drag_data_source,
@ -312,6 +519,22 @@ drag_grab_focus (MetaWaylandPointerGrab *grab,
meta_wayland_drag_grab_set_focus (drag_grab, surface); meta_wayland_drag_grab_set_focus (drag_grab, surface);
} }
static void
data_source_update_user_dnd_action (MetaWaylandDataSource *source,
ClutterModifierType modifiers)
{
uint32_t 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 static void
drag_grab_motion (MetaWaylandPointerGrab *grab, drag_grab_motion (MetaWaylandPointerGrab *grab,
const ClutterEvent *event) const ClutterEvent *event)
@ -356,7 +579,15 @@ data_device_end_drag_grab (MetaWaylandDragGrab *drag_grab)
drag_grab->seat->data_device.current_grab = NULL; 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); g_slice_free (MetaWaylandDragGrab, drag_grab);
} }
@ -371,13 +602,23 @@ drag_grab_button (MetaWaylandPointerGrab *grab,
if (drag_grab->generic.pointer->grab_button == clutter_event_get_button (event) && if (drag_grab->generic.pointer->grab_button == clutter_event_get_button (event) &&
event_type == CLUTTER_BUTTON_RELEASE) event_type == CLUTTER_BUTTON_RELEASE)
{ {
MetaWaylandDataSource *data_source = drag_grab->drag_data_source;
gboolean success = FALSE; gboolean success = FALSE;
if (meta_wayland_data_source_has_target (drag_grab->drag_data_source)) if (drag_grab->drag_focus &&
meta_wayland_data_source_get_current_action (drag_grab->drag_data_source) &&
meta_wayland_data_source_has_target (drag_grab->drag_data_source))
{ {
meta_wayland_surface_drag_dest_drop (drag_grab->drag_focus); meta_wayland_surface_drag_dest_drop (drag_grab->drag_focus);
meta_wayland_data_source_notify_drop_performed (data_source);
success = TRUE; success = TRUE;
} }
else
{
meta_wayland_data_source_cancel (data_source);
meta_wayland_data_source_set_current_offer (data_source, NULL);
meta_wayland_data_device_set_dnd_source (&drag_grab->seat->data_device, NULL);
}
/* Finish drag and let actor self-destruct */ /* Finish drag and let actor self-destruct */
meta_dnd_actor_drag_finish (META_DND_ACTOR (drag_grab->feedback_actor), meta_dnd_actor_drag_finish (META_DND_ACTOR (drag_grab->feedback_actor),
@ -396,6 +637,40 @@ static const MetaWaylandPointerGrabInterface drag_grab_interface = {
drag_grab_button, 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 static void
destroy_data_device_origin (struct wl_listener *listener, void *data) destroy_data_device_origin (struct wl_listener *listener, void *data)
{ {
@ -440,12 +715,16 @@ meta_wayland_data_device_start_drag (MetaWaylandDataDevice *data
MetaWaylandSeat *seat = wl_container_of (data_device, seat, data_device); MetaWaylandSeat *seat = wl_container_of (data_device, seat, data_device);
MetaWaylandDragGrab *drag_grab; MetaWaylandDragGrab *drag_grab;
ClutterPoint pos, stage_pos; ClutterPoint pos, stage_pos;
ClutterModifierType modifiers;
data_device->current_grab = drag_grab = g_slice_new0 (MetaWaylandDragGrab); data_device->current_grab = drag_grab = g_slice_new0 (MetaWaylandDragGrab);
drag_grab->generic.interface = funcs; drag_grab->generic.interface = funcs;
drag_grab->generic.pointer = &seat->pointer; 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->drag_client = client;
drag_grab->seat = seat; drag_grab->seat = seat;
@ -460,6 +739,11 @@ meta_wayland_data_device_start_drag (MetaWaylandDataDevice *data
drag_grab->drag_start_x = stage_pos.x; drag_grab->drag_start_x = stage_pos.x;
drag_grab->drag_start_y = stage_pos.y; 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);
g_object_weak_ref (G_OBJECT (source), g_object_weak_ref (G_OBJECT (source),
drag_grab_data_source_destroyed, drag_grab_data_source_destroyed,
drag_grab); drag_grab);
@ -467,6 +751,7 @@ meta_wayland_data_device_start_drag (MetaWaylandDataDevice *data
drag_grab->drag_data_source = source; drag_grab->drag_data_source = source;
meta_wayland_data_device_set_dnd_source (data_device, meta_wayland_data_device_set_dnd_source (data_device,
drag_grab->drag_data_source); drag_grab->drag_data_source);
data_source_update_user_dnd_action (source, modifiers);
if (icon_surface) if (icon_surface)
{ {
@ -548,6 +833,10 @@ data_device_start_drag (struct wl_client *client,
meta_wayland_data_device_start_drag (data_device, client, meta_wayland_data_device_start_drag (data_device, client,
&drag_grab_interface, &drag_grab_interface,
surface, drag_source, icon_surface); 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 static void
@ -600,6 +889,34 @@ meta_wayland_source_cancel (MetaWaylandDataSource *source)
wl_data_source_send_cancelled (source_wayland->resource); wl_data_source_send_cancelled (source_wayland->resource);
} }
static void
meta_wayland_source_action (MetaWaylandDataSource *source,
uint32_t action)
{
MetaWaylandDataSourceWayland *source_wayland =
META_WAYLAND_DATA_SOURCE_WAYLAND (source);
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);
wl_data_source_send_drop_performed (source_wayland->resource);
}
static void
meta_wayland_source_drag_finished (MetaWaylandDataSource *source)
{
MetaWaylandDataSourceWayland *source_wayland =
META_WAYLAND_DATA_SOURCE_WAYLAND (source);
wl_data_source_send_drag_finished (source_wayland->resource);
}
static void static void
meta_wayland_source_finalize (GObject *object) meta_wayland_source_finalize (GObject *object)
{ {
@ -623,6 +940,9 @@ meta_wayland_data_source_wayland_class_init (MetaWaylandDataSourceWaylandClass *
data_source_class->send = meta_wayland_source_send; data_source_class->send = meta_wayland_source_send;
data_source_class->target = meta_wayland_source_target; data_source_class->target = meta_wayland_source_target;
data_source_class->cancel = meta_wayland_source_cancel; 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 static void
@ -647,6 +967,7 @@ meta_wayland_data_source_init (MetaWaylandDataSource *source)
meta_wayland_data_source_get_instance_private (source); meta_wayland_data_source_get_instance_private (source);
wl_array_init (&priv->mime_types); wl_array_init (&priv->mime_types);
priv->current_dnd_action = -1;
} }
static void static void
@ -722,11 +1043,18 @@ meta_wayland_drag_dest_drop (MetaWaylandDataDevice *data_device,
wl_data_device_send_drop (grab->drag_focus_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 = { static const MetaWaylandDragDestFuncs meta_wayland_drag_dest_funcs = {
meta_wayland_drag_dest_focus_in, meta_wayland_drag_dest_focus_in,
meta_wayland_drag_dest_focus_out, meta_wayland_drag_dest_focus_out,
meta_wayland_drag_dest_motion, meta_wayland_drag_dest_motion,
meta_wayland_drag_dest_drop meta_wayland_drag_dest_drop,
meta_wayland_drag_dest_update
}; };
const MetaWaylandDragDestFuncs * const MetaWaylandDragDestFuncs *

View File

@ -45,6 +45,11 @@ struct _MetaWaylandDataSourceClass
void (* target) (MetaWaylandDataSource *source, void (* target) (MetaWaylandDataSource *source,
const gchar *mime_type); const gchar *mime_type);
void (* cancel) (MetaWaylandDataSource *source); void (* cancel) (MetaWaylandDataSource *source);
void (* action) (MetaWaylandDataSource *source,
uint32_t action);
void (* drop_performed) (MetaWaylandDataSource *source);
void (* drag_finished) (MetaWaylandDataSource *source);
}; };
struct _MetaWaylandDataDevice struct _MetaWaylandDataDevice
@ -95,6 +100,17 @@ void meta_wayland_data_source_send (MetaWaylandDataSource *source,
const gchar *mime_type, const gchar *mime_type,
gint fd); 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 * const MetaWaylandDragDestFuncs *
meta_wayland_data_device_get_drag_dest_funcs (void); meta_wayland_data_device_get_drag_dest_funcs (void);

View File

@ -36,7 +36,7 @@
/* Global/master objects (version exported by wl_registry and negotiated through bind) */ /* Global/master objects (version exported by wl_registry and negotiated through bind) */
#define META_WL_COMPOSITOR_VERSION 3 #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_XDG_SHELL_VERSION 1
#define META_WL_SHELL_VERSION 1 #define META_WL_SHELL_VERSION 1
#define META_WL_SEAT_VERSION 4 #define META_WL_SEAT_VERSION 4

View File

@ -108,6 +108,7 @@ enum {
ATOM_DND_ACTION_MOVE, ATOM_DND_ACTION_MOVE,
ATOM_DND_ACTION_COPY, ATOM_DND_ACTION_COPY,
ATOM_DND_ACTION_ASK, ATOM_DND_ACTION_ASK,
ATOM_DND_ACTION_PRIVATE,
N_DND_ATOMS N_DND_ATOMS
}; };
@ -126,6 +127,7 @@ const gchar *atom_names[] = {
"XdndActionMove", "XdndActionMove",
"XdndActionCopy", "XdndActionCopy",
"XdndActionAsk", "XdndActionAsk",
"XdndActionPrivate",
NULL NULL
}; };
@ -135,6 +137,19 @@ G_DEFINE_TYPE (MetaWaylandDataSourceXWayland, meta_wayland_data_source_xwayland,
META_TYPE_WAYLAND_DATA_SOURCE); META_TYPE_WAYLAND_DATA_SOURCE);
/* XDND helpers */ /* 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 void static void
xdnd_send_enter (MetaXWaylandSelection *selection_data, xdnd_send_enter (MetaXWaylandSelection *selection_data,
Window dest) Window dest)
@ -217,10 +232,21 @@ xdnd_send_position (MetaXWaylandSelection *selection_data,
int x, int x,
int y) int y)
{ {
MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
MetaSelectionBridge *selection = &selection_data->dnd.selection; MetaSelectionBridge *selection = &selection_data->dnd.selection;
MetaWaylandDataSource *source = compositor->seat->data_device.dnd_data_source;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
uint32_t action = 0, user_action, actions;
XEvent xev = { 0 }; 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.type = ClientMessage;
xev.xclient.message_type = xdnd_atoms[ATOM_DND_POSITION]; xev.xclient.message_type = xdnd_atoms[ATOM_DND_POSITION];
xev.xclient.format = 32; xev.xclient.format = 32;
@ -230,7 +256,7 @@ xdnd_send_position (MetaXWaylandSelection *selection_data,
xev.xclient.data.l[1] = 0; xev.xclient.data.l[1] = 0;
xev.xclient.data.l[2] = (x << 16) | y; xev.xclient.data.l[2] = (x << 16) | y;
xev.xclient.data.l[3] = time; 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); XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
} }
@ -262,6 +288,8 @@ xdnd_send_finished (MetaXWaylandSelection *selection_data,
{ {
MetaDndBridge *selection = &selection_data->dnd; MetaDndBridge *selection = &selection_data->dnd;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
MetaWaylandDataSource *source = selection_data->dnd.selection.source;
uint32_t action = 0;
XEvent xev = { 0 }; XEvent xev = { 0 };
xev.xclient.type = ClientMessage; xev.xclient.type = ClientMessage;
@ -273,8 +301,9 @@ xdnd_send_finished (MetaXWaylandSelection *selection_data,
if (accepted) if (accepted)
{ {
action = meta_wayland_data_source_get_current_action (source);
xev.xclient.data.l[1] = 1; /* Drop successful */ 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); XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
@ -283,7 +312,7 @@ xdnd_send_finished (MetaXWaylandSelection *selection_data,
static void static void
xdnd_send_status (MetaXWaylandSelection *selection_data, xdnd_send_status (MetaXWaylandSelection *selection_data,
Window dest, Window dest,
gboolean accepted) uint32_t action)
{ {
MetaDndBridge *selection = &selection_data->dnd; MetaDndBridge *selection = &selection_data->dnd;
Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
@ -296,12 +325,10 @@ xdnd_send_status (MetaXWaylandSelection *selection_data,
xev.xclient.data.l[0] = selection->dnd_window; 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[1] = 1 << 1; /* Bit 2: dest wants XdndPosition messages */
xev.xclient.data.l[4] = action_to_atom (action);
if (accepted) if (xev.xclient.data.l[4])
{ xev.xclient.data.l[1] |= 1 << 0; /* Bit 1: dest accepts the drop */
xev.xclient.data.l[1] |= 1 << 0; /* Bit 1: dest accepts the drop */
xev.xclient.data.l[4] = xdnd_atoms[ATOM_DND_ACTION_COPY];
}
XSendEvent (xdisplay, dest, False, NoEventMask, &xev); XSendEvent (xdisplay, dest, False, NoEventMask, &xev);
} }
@ -387,6 +414,32 @@ x11_selection_data_free (X11SelectionData *data)
g_slice_free (X11SelectionData, data); g_slice_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 static void
x11_selection_data_finish (MetaSelectionBridge *selection, x11_selection_data_finish (MetaSelectionBridge *selection,
gboolean success) gboolean success)
@ -395,13 +448,18 @@ x11_selection_data_finish (MetaSelectionBridge *selection,
return; return;
if (selection == &selection->x11_selection->selection_data->dnd.selection) if (selection == &selection->x11_selection->selection_data->dnd.selection)
xdnd_send_finished (selection->x11_selection->selection_data, x11_selection_data_send_finished (selection, success);
selection->owner, success);
g_clear_pointer (&selection->x11_selection, g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free); (GDestroyNotify) x11_selection_data_free);
} }
static void
x11_selection_data_close (X11SelectionData *data)
{
g_output_stream_close (data->stream, data->cancellable, NULL);
}
static void static void
x11_data_write_cb (GObject *object, x11_data_write_cb (GObject *object,
GAsyncResult *res, GAsyncResult *res,
@ -435,6 +493,7 @@ x11_data_write_cb (GObject *object,
} }
else else
{ {
x11_selection_data_close (selection->x11_selection);
x11_selection_data_finish (selection, success); x11_selection_data_finish (selection, success);
} }
} }
@ -693,6 +752,7 @@ meta_xwayland_selection_get_incr_chunk (MetaWaylandCompositor *compositor,
else else
{ {
/* Transfer has completed */ /* Transfer has completed */
x11_selection_data_close (selection->x11_selection);
x11_selection_data_finish (selection, TRUE); x11_selection_data_finish (selection, TRUE);
} }
@ -741,11 +801,15 @@ meta_x11_source_target (MetaWaylandDataSource *source,
MetaWaylandDataSourceXWayland *source_xwayland = MetaWaylandDataSourceXWayland *source_xwayland =
META_WAYLAND_DATA_SOURCE_XWAYLAND (source); META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
MetaSelectionBridge *selection = source_xwayland->selection; MetaSelectionBridge *selection = source_xwayland->selection;
uint32_t action = 0;
if (selection->selection_atom == xdnd_atoms[ATOM_DND_SELECTION]) 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, xdnd_send_status (compositor->xwayland_manager.selection_data,
selection->owner, mime_type != NULL); selection->owner, action);
} }
} }
@ -756,10 +820,46 @@ meta_x11_source_cancel (MetaWaylandDataSource *source)
META_WAYLAND_DATA_SOURCE_XWAYLAND (source); META_WAYLAND_DATA_SOURCE_XWAYLAND (source);
MetaSelectionBridge *selection = source_xwayland->selection; MetaSelectionBridge *selection = source_xwayland->selection;
x11_selection_data_send_finished (selection, FALSE);
g_clear_pointer (&selection->x11_selection, g_clear_pointer (&selection->x11_selection,
(GDestroyNotify) x11_selection_data_free); (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 static void
meta_wayland_data_source_xwayland_init (MetaWaylandDataSourceXWayland *source_xwayland) meta_wayland_data_source_xwayland_init (MetaWaylandDataSourceXWayland *source_xwayland)
{ {
@ -774,6 +874,9 @@ meta_wayland_data_source_xwayland_class_init (MetaWaylandDataSourceXWaylandClass
data_source_class->send = meta_x11_source_send; data_source_class->send = meta_x11_source_send;
data_source_class->target = meta_x11_source_target; data_source_class->target = meta_x11_source_target;
data_source_class->cancel = meta_x11_source_cancel; 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 * static MetaWaylandDataSource *
@ -837,11 +940,27 @@ meta_x11_drag_dest_drop (MetaWaylandDataDevice *data_device,
meta_display_get_current_time_roundtrip (meta_get_display ())); 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 = { static const MetaWaylandDragDestFuncs meta_x11_drag_dest_funcs = {
meta_x11_drag_dest_focus_in, meta_x11_drag_dest_focus_in,
meta_x11_drag_dest_focus_out, meta_x11_drag_dest_focus_out,
meta_x11_drag_dest_motion, meta_x11_drag_dest_motion,
meta_x11_drag_dest_drop meta_x11_drag_dest_drop,
meta_x11_drag_dest_update
}; };
const MetaWaylandDragDestFuncs * const MetaWaylandDragDestFuncs *
@ -1135,6 +1254,10 @@ meta_xwayland_selection_handle_selection_request (MetaWaylandCompositor *composi
selection->timestamp); selection->timestamp);
reply_selection_request (event, TRUE); reply_selection_request (event, TRUE);
} }
else if (data_source && event->target == gdk_x11_get_xatom_by_name ("DELETE"))
{
meta_wayland_data_source_notify_finish (data_source);
}
else else
{ {
if (data_source && if (data_source &&
@ -1257,6 +1380,7 @@ meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor
if (event->window == dnd->selection.window) if (event->window == dnd->selection.window)
{ {
MetaWaylandDataSource *data_source; MetaWaylandDataSource *data_source;
uint32_t action = 0;
data_source = compositor->seat->data_device.dnd_data_source; data_source = compositor->seat->data_device.dnd_data_source;
@ -1269,6 +1393,19 @@ meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor
meta_wayland_data_source_set_has_target (data_source, meta_wayland_data_source_set_has_target (data_source,
(event->data.l[1] & 1) != 0); (event->data.l[1] & 1) != 0);
/* data.l[4] contains the action atom */
if (event->data.l[4])
{
if (((Atom) event->data.l[4]) == xdnd_atoms[ATOM_DND_ACTION_COPY] ||
((Atom) event->data.l[4]) == xdnd_atoms[ATOM_DND_ACTION_PRIVATE])
action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
else if (((Atom) event->data.l[4]) == xdnd_atoms[ATOM_DND_ACTION_MOVE])
action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
else if (((Atom) event->data.l[4]) == xdnd_atoms[ATOM_DND_ACTION_ASK])
action = WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK;
}
meta_wayland_data_source_set_current_action (data_source, action);
return TRUE; return TRUE;
} }
else if (event->message_type == xdnd_atoms[ATOM_DND_FINISHED]) else if (event->message_type == xdnd_atoms[ATOM_DND_FINISHED])
@ -1277,8 +1414,7 @@ meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor
if (compositor->seat->data_device.current_grab) if (compositor->seat->data_device.current_grab)
return FALSE; return FALSE;
meta_wayland_data_device_set_dnd_source (&compositor->seat->data_device, meta_wayland_data_source_notify_finish (data_source);
NULL);
return TRUE; return TRUE;
} }
} }
@ -1338,6 +1474,7 @@ meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor
{ {
ClutterEvent *motion; ClutterEvent *motion;
ClutterPoint pos; ClutterPoint pos;
uint32_t action = 0;
motion = clutter_event_new (CLUTTER_MOTION); motion = clutter_event_new (CLUTTER_MOTION);
clutter_input_device_get_coords (seat->pointer.device, NULL, &pos); clutter_input_device_get_coords (seat->pointer.device, NULL, &pos);
@ -1346,11 +1483,19 @@ meta_xwayland_selection_handle_client_message (MetaWaylandCompositor *compositor
clutter_event_set_source_device (motion, seat->pointer.device); clutter_event_set_source_device (motion, seat->pointer.device);
clutter_event_set_time (motion, dnd->last_motion_time); clutter_event_set_time (motion, dnd->last_motion_time);
if ((Atom) event->data.l[4] == xdnd_atoms[ATOM_DND_ACTION_COPY])
action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
else if ((Atom) event->data.l[4] == xdnd_atoms[ATOM_DND_ACTION_MOVE])
action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
else if ((Atom) event->data.l[4] == xdnd_atoms[ATOM_DND_ACTION_ASK])
action = WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK;
meta_wayland_data_source_set_actions (dnd->selection.source, action);
meta_wayland_surface_drag_dest_motion (drag_focus, motion); meta_wayland_surface_drag_dest_motion (drag_focus, motion);
xdnd_send_status (compositor->xwayland_manager.selection_data, xdnd_send_status (compositor->xwayland_manager.selection_data,
(Window) event->data.l[0], (Window) event->data.l[0],
meta_wayland_data_source_has_target ( meta_wayland_data_source_get_current_action (dnd->selection.source));
dnd->selection.source));
clutter_event_free (motion); clutter_event_free (motion);
return TRUE; return TRUE;