diff --git a/src/wayland/meta-wayland-data-device.c b/src/wayland/meta-wayland-data-device.c index 3d606902b..5aa19384e 100644 --- a/src/wayland/meta-wayland-data-device.c +++ b/src/wayland/meta-wayland-data-device.c @@ -42,12 +42,18 @@ struct _MetaWaylandDataOffer struct wl_resource *resource; MetaWaylandDataSource *source; struct wl_listener source_destroy_listener; + uint32_t dnd_actions; + uint32_t preferred_dnd_action; }; typedef struct _MetaWaylandDataSourcePrivate { + MetaWaylandDataOffer *offer; struct wl_array mime_types; gboolean has_target; + uint32_t dnd_actions; + uint32_t user_dnd_action; + uint32_t current_dnd_action; } MetaWaylandDataSourcePrivate; typedef struct _MetaWaylandDataSourceWayland @@ -74,6 +80,50 @@ unbind_resource (struct wl_resource *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 meta_wayland_data_source_target (MetaWaylandDataSource *source, const char *mime_type) @@ -123,6 +173,106 @@ 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; +} + +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 data_offer_accept (struct wl_client *client, struct wl_resource *resource, @@ -161,21 +311,59 @@ data_offer_destroy (struct wl_client *client, struct wl_resource *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 = { data_offer_accept, data_offer_receive, 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 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)) + 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); } @@ -203,6 +391,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 +413,27 @@ 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); + + 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 +452,7 @@ struct _MetaWaylandDragGrab { struct wl_listener drag_origin_listener; int drag_start_x, drag_start_y; + ClutterModifierType buttons; }; 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); grab->drag_focus_data_device = NULL; + grab->drag_focus = NULL; } void @@ -285,6 +491,7 @@ meta_wayland_drag_grab_set_focus (MetaWaylandDragGrab *drag_grab, client = wl_resource_get_client (surface->resource); 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) 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); } +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 drag_grab_motion (MetaWaylandPointerGrab *grab, const ClutterEvent *event) @@ -356,7 +579,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); } @@ -371,13 +602,23 @@ drag_grab_button (MetaWaylandPointerGrab *grab, if (drag_grab->generic.pointer->grab_button == clutter_event_get_button (event) && event_type == CLUTTER_BUTTON_RELEASE) { + MetaWaylandDataSource *data_source = drag_grab->drag_data_source; 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_data_source_notify_drop_performed (data_source); 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 */ 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, }; +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) { @@ -440,12 +715,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; @@ -460,6 +739,11 @@ 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); + g_object_weak_ref (G_OBJECT (source), drag_grab_data_source_destroyed, drag_grab); @@ -467,6 +751,7 @@ meta_wayland_data_device_start_drag (MetaWaylandDataDevice *data drag_grab->drag_data_source = 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) { @@ -548,6 +833,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 @@ -600,6 +889,34 @@ meta_wayland_source_cancel (MetaWaylandDataSource *source) 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 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->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 @@ -647,6 +967,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 @@ -722,11 +1043,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 * 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 9ca8def69..4c7e9b89c 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 4 diff --git a/src/wayland/meta-xwayland-selection.c b/src/wayland/meta-xwayland-selection.c index 081a24439..5140d0481 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,19 @@ 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 void xdnd_send_enter (MetaXWaylandSelection *selection_data, Window dest) @@ -217,10 +232,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 +256,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 +288,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 +301,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 +312,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 +325,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); } @@ -387,6 +414,32 @@ x11_selection_data_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 x11_selection_data_finish (MetaSelectionBridge *selection, gboolean success) @@ -395,13 +448,18 @@ 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, @@ -435,6 +493,7 @@ x11_data_write_cb (GObject *object, } else { + x11_selection_data_close (selection->x11_selection); x11_selection_data_finish (selection, success); } } @@ -693,6 +752,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 +801,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,10 +820,46 @@ 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 +874,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 +940,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 +1254,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")) + { + meta_wayland_data_source_notify_finish (data_source); + } else { if (data_source && @@ -1257,6 +1380,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 +1393,19 @@ 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]) + { + 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; } 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) return FALSE; - meta_wayland_data_device_set_dnd_source (&compositor->seat->data_device, - NULL); + meta_wayland_data_source_notify_finish (data_source); return TRUE; } } @@ -1338,6 +1474,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 +1483,19 @@ 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); + 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); 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;