[mutter] wayland: Implement DnD actions as per wl_data_device v3 changes



commit 9b26694bbca5145d01abb66c0ac5d85f70047875
Author: Carlos Garnacho <carlosg gnome org>
Date:   Tue Apr 7 16:05:46 2015 +0200

    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.dnd_drop_performed
      - wl_data_source.dnd_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.
    
    The Xdnd bridge code is also modified to cope with actions, so mixed
    wayland-x11 scenarios are able to convey that information.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=760805

 src/wayland/meta-wayland-data-device.c |  522 +++++++++++++++++++++++++++++++-
 src/wayland/meta-wayland-data-device.h |   16 +
 src/wayland/meta-wayland-versions.h    |    2 +-
 src/wayland/meta-xwayland-selection.c  |  180 ++++++++++-
 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 7897c44..101db78 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 483b08b..67057fd 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 91a82af..e75d987 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 081a244..5d3405a 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;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]