diff options
-rw-r--r-- | src/wayland/meta-wayland-data-device.c | 522 | ||||
-rw-r--r-- | src/wayland/meta-wayland-data-device.h | 16 | ||||
-rw-r--r-- | src/wayland/meta-wayland-versions.h | 2 | ||||
-rw-r--r-- | 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 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; |