From 7d1881273547f2589645e646f04fa92431467e13 Mon Sep 17 00:00:00 2001 From: emersion Date: Thu, 5 Apr 2018 22:06:17 -0400 Subject: [PATCH] xwayland: parse DND_ENTER messages --- include/xwayland/selection.h | 2 + xwayland/selection/dnd.c | 257 +++++++++++++++++++++++++--------- xwayland/selection/incoming.c | 50 ++++--- 3 files changed, 223 insertions(+), 86 deletions(-) diff --git a/include/xwayland/selection.h b/include/xwayland/selection.h index fc910e46a..52b17f912 100644 --- a/include/xwayland/selection.h +++ b/include/xwayland/selection.h @@ -65,6 +65,8 @@ bool primary_selection_source_is_xwayland( struct wlr_primary_selection_source *wlr_source); void xwm_seat_handle_start_drag(struct wlr_xwm *xwm, struct wlr_drag *drag); +int xwm_dnd_handle_xfixes_selection_notify(struct wlr_xwm *xwm, + xcb_xfixes_selection_notify_event_t *event); void xwm_selection_init(struct wlr_xwm *xwm); void xwm_selection_finish(struct wlr_xwm *xwm); diff --git a/xwayland/selection/dnd.c b/xwayland/selection/dnd.c index ed265c280..f4a1196c2 100644 --- a/xwayland/selection/dnd.c +++ b/xwayland/selection/dnd.c @@ -163,76 +163,207 @@ static void xwm_dnd_send_leave(struct wlr_xwm *xwm) { xwm_dnd_send_event(xwm, xwm->atoms[DND_FINISHED], &data); }*/ +/** + * Handle a status message for an outgoing DnD operation. + */ +static void xwm_handle_dnd_status(struct wlr_xwm *xwm, + xcb_client_message_data_t *data) { + if (xwm->drag == NULL) { + wlr_log(L_DEBUG, "ignoring XdndStatus client message because " + "there's no drag"); + return; + } + + xcb_window_t target_window = data->data32[0]; + bool accepted = data->data32[1] & 1; + xcb_atom_t action_atom = data->data32[4]; + + if (xwm->drag_focus == NULL || + target_window != xwm->drag_focus->window_id) { + wlr_log(L_DEBUG, "ignoring XdndStatus client message because " + "it doesn't match the current drag focus window ID"); + return; + } + + enum wl_data_device_manager_dnd_action action = + data_device_manager_dnd_action_from_atom(xwm, action_atom); + + struct wlr_drag *drag = xwm->drag; + assert(drag != NULL); + + drag->source->accepted = accepted; + wlr_data_source_dnd_action(drag->source, action); + + wlr_log(L_DEBUG, "DND_STATUS window=%d accepted=%d action=%d", + target_window, accepted, action); +} + +/** + * Handle a finish message for an outgoing DnD operation. + */ +static void xwm_handle_dnd_finished(struct wlr_xwm *xwm, + xcb_client_message_data_t *data) { + // This should only happen after the drag has ended, but before the drag + // source is destroyed + if (xwm->seat == NULL || xwm->seat->drag_source == NULL || + xwm->drag != NULL) { + wlr_log(L_DEBUG, "ignoring XdndFinished client message because " + "there's no finished drag"); + return; + } + + struct wlr_data_source *source = xwm->seat->drag_source; + + xcb_window_t target_window = data->data32[0]; + bool performed = data->data32[1] & 1; + xcb_atom_t action_atom = data->data32[2]; + + if (xwm->drag_focus == NULL || + target_window != xwm->drag_focus->window_id) { + wlr_log(L_DEBUG, "ignoring XdndFinished client message because " + "it doesn't match the finished drag focus window ID"); + return; + } + + enum wl_data_device_manager_dnd_action action = + data_device_manager_dnd_action_from_atom(xwm, action_atom); + + if (performed) { + wlr_data_source_dnd_finish(source); + } + + wlr_log(L_DEBUG, "DND_FINISH window=%d performed=%d action=%d", + target_window, performed, action); +} + +static bool xwm_add_atom_to_mime_types(struct wlr_xwm *xwm, + struct wl_array *mime_types, xcb_atom_t atom) { + char *mime_type = xwm_mime_type_from_atom(xwm, atom); + if (mime_type == NULL) { + return false; + } + char **mime_type_ptr = + wl_array_add(mime_types, sizeof(*mime_type_ptr)); + if (mime_type_ptr == NULL) { + return false; + } + *mime_type_ptr = mime_type; + return true; +} + +static bool xwm_dnd_get_mime_types(struct wlr_xwm *xwm, + struct wl_array *mime_types, xcb_window_t source) { + xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn, + 1, // delete + source, + xwm->atoms[DND_TYPE_LIST], + XCB_GET_PROPERTY_TYPE_ANY, + 0, // offset + 0x1fffffff // length + ); + + xcb_get_property_reply_t *reply = + xcb_get_property_reply(xwm->xcb_conn, cookie, NULL); + if (reply == NULL) { + return false; + } + if (reply->type != XCB_ATOM_ATOM || reply->value_len == 0) { + wlr_log(L_ERROR, "invalid XdndTypeList property"); + goto error; + } + + xcb_atom_t *atoms = xcb_get_property_value(reply); + for (uint32_t i = 0; i < reply->value_len; ++i) { + if (!xwm_add_atom_to_mime_types(xwm, mime_types, atoms[i])) { + wlr_log(L_ERROR, "failed to add MIME type atom to list"); + goto error; + } + } + + free(reply); + return true; + +error: + free(reply); + return false; +} + +/** + * Handle an enter message for an incoming DnD operation. + */ +static void xwm_handle_dnd_enter(struct wlr_xwm *xwm, + xcb_client_message_data_t *data) { + if (xwm->seat == NULL) { + wlr_log(L_DEBUG, "ignoring XdndEnter client message because " + "there's no Xwayland seat"); + return; + } + + xcb_window_t source_window = data->data32[0]; + + if (source_window != xwm->dnd_selection.owner) { + wlr_log(L_DEBUG, "ignoring XdndEnter client message because " + "the source window hasn't set the drag-and-drop selection"); + return; + } + + struct wl_array mime_types; + wl_array_init(&mime_types); + if ((data->data32[1] & 1) == 0) { + // Less than 3 MIME types, those are in the message data + for (size_t i = 0; i < 3; ++i) { + xcb_atom_t atom = data->data32[2+i]; + if (atom == XCB_ATOM_NONE) { + break; + } + if (!xwm_add_atom_to_mime_types(xwm, &mime_types, atom)) { + wlr_log(L_ERROR, "failed to add MIME type atom to list"); + break; + } + } + } else { + if (!xwm_dnd_get_mime_types(xwm, &mime_types, source_window)) { + wlr_log(L_ERROR, "failed to add MIME type atom to list"); + } + } + + wlr_log(L_INFO, "parsed XdndEnter"); +} + int xwm_handle_selection_client_message(struct wlr_xwm *xwm, xcb_client_message_event_t *ev) { + // Outgoing if (ev->type == xwm->atoms[DND_STATUS]) { - if (xwm->drag == NULL) { - wlr_log(L_DEBUG, "ignoring XdndStatus client message because " - "there's no drag"); - return 1; - } - - xcb_client_message_data_t *data = &ev->data; - xcb_window_t target_window = data->data32[0]; - bool accepted = data->data32[1] & 1; - xcb_atom_t action_atom = data->data32[4]; - - if (xwm->drag_focus == NULL || - target_window != xwm->drag_focus->window_id) { - wlr_log(L_DEBUG, "ignoring XdndStatus client message because " - "it doesn't match the current drag focus window ID"); - return 1; - } - - enum wl_data_device_manager_dnd_action action = - data_device_manager_dnd_action_from_atom(xwm, action_atom); - - struct wlr_drag *drag = xwm->drag; - assert(drag != NULL); - - drag->source->accepted = accepted; - wlr_data_source_dnd_action(drag->source, action); - - wlr_log(L_DEBUG, "DND_STATUS window=%d accepted=%d action=%d", - target_window, accepted, action); + xwm_handle_dnd_status(xwm, &ev->data); return 1; } else if (ev->type == xwm->atoms[DND_FINISHED]) { - // This should only happen after the drag has ended, but before the drag - // source is destroyed - if (xwm->seat == NULL || xwm->seat->drag_source == NULL || - xwm->drag != NULL) { - wlr_log(L_DEBUG, "ignoring XdndFinished client message because " - "there's no finished drag"); - return 1; - } - - struct wlr_data_source *source = xwm->seat->drag_source; - - xcb_client_message_data_t *data = &ev->data; - xcb_window_t target_window = data->data32[0]; - bool performed = data->data32[1] & 1; - xcb_atom_t action_atom = data->data32[2]; - - if (xwm->drag_focus == NULL || - target_window != xwm->drag_focus->window_id) { - wlr_log(L_DEBUG, "ignoring XdndFinished client message because " - "it doesn't match the finished drag focus window ID"); - return 1; - } - - enum wl_data_device_manager_dnd_action action = - data_device_manager_dnd_action_from_atom(xwm, action_atom); - - if (performed) { - wlr_data_source_dnd_finish(source); - } - - wlr_log(L_DEBUG, "DND_FINISH window=%d performed=%d action=%d", - target_window, performed, action); + xwm_handle_dnd_finished(xwm, &ev->data); return 1; - } else { - return 0; } + + // Incoming + if (ev->type == xwm->atoms[DND_ENTER]) { + xwm_handle_dnd_enter(xwm, &ev->data); + return 1; + } + + return 0; +} + +int xwm_dnd_handle_xfixes_selection_notify(struct wlr_xwm *xwm, + xcb_xfixes_selection_notify_event_t *event) { + assert(event->selection == xwm->atoms[DND_SELECTION]); + struct wlr_xwm_selection *selection = &xwm->dnd_selection; + + selection->owner = event->owner; + + if (event->owner != XCB_ATOM_NONE) { + // TODO: start grab + } else { + // TODO: end grab + } + + return 1; } static void seat_handle_drag_focus(struct wl_listener *listener, void *data) { diff --git a/xwayland/selection/incoming.c b/xwayland/selection/incoming.c index 5deb2286d..89361a577 100644 --- a/xwayland/selection/incoming.c +++ b/xwayland/selection/incoming.c @@ -379,8 +379,7 @@ static void xwm_selection_get_targets(struct wlr_xwm_selection *selection) { void xwm_handle_selection_notify(struct wlr_xwm *xwm, xcb_selection_notify_event_t *event) { wlr_log(L_DEBUG, "XCB_SELECTION_NOTIFY (selection=%u, property=%u, target=%u)", - event->selection, event->property, - event->target); + event->selection, event->property, event->target); struct wlr_xwm_selection *selection = xwm_get_selection(xwm, event->selection); @@ -410,6 +409,11 @@ int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm, wlr_log(L_DEBUG, "XCB_XFIXES_SELECTION_NOTIFY (selection=%u, owner=%u)", event->selection, event->owner); + // Drag'n'drop works differently + if (event->selection == xwm->atoms[DND_SELECTION]) { + return xwm_dnd_handle_xfixes_selection_notify(xwm, event); + } + struct wlr_xwm_selection *selection = xwm_get_selection(xwm, event->selection); if (selection == NULL) { @@ -435,28 +439,28 @@ int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm, } selection->owner = XCB_WINDOW_NONE; - return 1; + } else { + selection->owner = event->owner; + + // This is our selection window. + // We have to use XCB_TIME_CURRENT_TIME when we claim the + // selection, so grab the actual timestamp here so we can + // answer TIMESTAMP conversion requests correctly. + if (event->owner == selection->window) { + selection->timestamp = event->timestamp; + return 1; + } + + struct wlr_xwm_selection_transfer *transfer = &selection->incoming; + transfer->incr = false; + // doing this will give a selection notify where we actually handle the sync + xcb_convert_selection(xwm->xcb_conn, selection->window, + selection->atom, + xwm->atoms[TARGETS], + xwm->atoms[WL_SELECTION], + event->timestamp); + xcb_flush(xwm->xcb_conn); } - selection->owner = event->owner; - - // We have to use XCB_TIME_CURRENT_TIME when we claim the - // selection, so grab the actual timestamp here so we can - // answer TIMESTAMP conversion requests correctly. - if (event->owner == selection->window) { - selection->timestamp = event->timestamp; - return 1; - } - - struct wlr_xwm_selection_transfer *transfer = &selection->incoming; - transfer->incr = false; - // doing this will give a selection notify where we actually handle the sync - xcb_convert_selection(xwm->xcb_conn, selection->window, - selection->atom, - xwm->atoms[TARGETS], - xwm->atoms[WL_SELECTION], - event->timestamp); - xcb_flush(xwm->xcb_conn); - return 1; }