2018-04-03 16:12:57 +00:00
|
|
|
#include <assert.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
2020-07-03 02:25:25 +00:00
|
|
|
#include <wayland-util.h>
|
2018-04-03 16:12:57 +00:00
|
|
|
#include <wlr/types/wlr_data_device.h>
|
2018-11-28 15:37:35 +00:00
|
|
|
#include <wlr/types/wlr_primary_selection.h>
|
2018-04-03 16:12:57 +00:00
|
|
|
#include <wlr/util/log.h>
|
|
|
|
#include <xcb/xfixes.h>
|
|
|
|
#include "xwayland/selection.h"
|
2018-11-28 15:37:35 +00:00
|
|
|
#include "xwayland/xwm.h"
|
2018-04-03 16:12:57 +00:00
|
|
|
|
|
|
|
static void xwm_selection_send_notify(struct wlr_xwm *xwm,
|
|
|
|
xcb_selection_request_event_t *req, bool success) {
|
|
|
|
xcb_selection_notify_event_t selection_notify = {
|
|
|
|
.response_type = XCB_SELECTION_NOTIFY,
|
|
|
|
.sequence = 0,
|
|
|
|
.time = req->time,
|
|
|
|
.requestor = req->requestor,
|
|
|
|
.selection = req->selection,
|
|
|
|
.target = req->target,
|
|
|
|
.property = success ? req->property : XCB_ATOM_NONE,
|
|
|
|
};
|
|
|
|
|
2020-06-04 12:33:32 +00:00
|
|
|
wlr_log(WLR_DEBUG, "SendEvent destination=%" PRIu32 " SelectionNotify(31) time=%" PRIu32
|
|
|
|
" requestor=%" PRIu32 " selection=%" PRIu32 " target=%" PRIu32 " property=%" PRIu32,
|
|
|
|
req->requestor, req->time, req->requestor, req->selection, req->target,
|
2018-04-03 16:12:57 +00:00
|
|
|
selection_notify.property);
|
2024-04-20 12:59:40 +00:00
|
|
|
xwm_send_event_with_size(xwm->xcb_conn,
|
2018-04-03 16:12:57 +00:00
|
|
|
0, // propagate
|
|
|
|
req->requestor,
|
|
|
|
XCB_EVENT_MASK_NO_EVENT,
|
2024-04-20 12:59:40 +00:00
|
|
|
&selection_notify,
|
|
|
|
sizeof(selection_notify));
|
2018-04-03 16:12:57 +00:00
|
|
|
xcb_flush(xwm->xcb_conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int xwm_selection_flush_source_data(
|
|
|
|
struct wlr_xwm_selection_transfer *transfer) {
|
|
|
|
xcb_change_property(transfer->selection->xwm->xcb_conn,
|
|
|
|
XCB_PROP_MODE_REPLACE,
|
|
|
|
transfer->request.requestor,
|
|
|
|
transfer->request.property,
|
|
|
|
transfer->request.target,
|
|
|
|
8, // format
|
|
|
|
transfer->source_data.size,
|
|
|
|
transfer->source_data.data);
|
|
|
|
xcb_flush(transfer->selection->xwm->xcb_conn);
|
|
|
|
transfer->property_set = true;
|
|
|
|
size_t length = transfer->source_data.size;
|
|
|
|
transfer->source_data.size = 0;
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xwm_selection_transfer_start_outgoing(
|
|
|
|
struct wlr_xwm_selection_transfer *transfer);
|
|
|
|
|
2021-01-30 00:00:17 +00:00
|
|
|
void xwm_selection_transfer_destroy_outgoing(
|
2018-04-03 16:12:57 +00:00
|
|
|
struct wlr_xwm_selection_transfer *transfer) {
|
2021-01-31 04:43:25 +00:00
|
|
|
wl_list_remove(&transfer->link);
|
xwayland: remove stale transfers from the same requestor
It seems that if we ever try to reply to a selection request after
another has been sent by the same requestor (we reply in FIFO order),
the requestor never reads from it, and we end up stalling forever on a
transfer that will never complete.
It appears that `XCB_SELECTION_REQUEST` has some sort of singleton
semantics, and new requests for the same selection are meant to replace
outstanding older ones. I couldn't find a reference for this, but
empirically this does seem to be the case.
Real (contrived) case where we don't currently do this, and things break:
* run fcitx
* run Slack
* wl-copy < <(base64 /opt/firefox/libxul.so) # or some other large file
* focus Slack (no need to paste)
fcitx will send in an `XCB_SELECTION_REQUEST`, and we'll start
processing it. Immediately after, Slack sends its own. fcitx hangs for a
long, long time. In the meantime, Slack retries and sends another
selection request. We now have two pending requests from Slack.
Eventually fcitx gives up (or it can be `pkill`'d), and we start
processing the first request Slack gave us (FIFO). Slack (Electron?)
isn't listening on the other end anymore, and this transfer never
completes. The X11 clipboard becomes unusable until Slack is killed.
After this patch, the clipboard is immediately usable again after fcitx
bails. Also added a bunch of debug-level logging that makes diagnosing
this sort of issue easier.
Refs swaywm/sway#4007.
2020-10-12 01:25:26 +00:00
|
|
|
wlr_log(WLR_DEBUG, "Destroying transfer %p", transfer);
|
2018-04-03 16:12:57 +00:00
|
|
|
|
2021-01-25 02:09:53 +00:00
|
|
|
xwm_selection_transfer_remove_event_source(transfer);
|
|
|
|
xwm_selection_transfer_close_wl_client_fd(transfer);
|
2018-04-03 16:12:57 +00:00
|
|
|
wl_array_release(&transfer->source_data);
|
|
|
|
free(transfer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int xwm_data_source_read(int fd, uint32_t mask, void *data) {
|
|
|
|
struct wlr_xwm_selection_transfer *transfer = data;
|
|
|
|
struct wlr_xwm *xwm = transfer->selection->xwm;
|
|
|
|
|
|
|
|
void *p;
|
|
|
|
size_t current = transfer->source_data.size;
|
|
|
|
if (transfer->source_data.size < INCR_CHUNK_SIZE) {
|
|
|
|
p = wl_array_add(&transfer->source_data, INCR_CHUNK_SIZE);
|
|
|
|
if (p == NULL) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_ERROR, "Could not allocate selection source_data");
|
2018-04-03 16:12:57 +00:00
|
|
|
goto error_out;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
p = (char *)transfer->source_data.data + transfer->source_data.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t available = transfer->source_data.alloc - current;
|
|
|
|
ssize_t len = read(fd, p, available);
|
|
|
|
if (len == -1) {
|
2020-10-11 02:58:57 +00:00
|
|
|
wlr_log_errno(WLR_ERROR, "read error from data source");
|
2018-04-03 16:12:57 +00:00
|
|
|
goto error_out;
|
|
|
|
}
|
|
|
|
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "read %zd bytes (available %zu, mask 0x%x)", len,
|
2018-04-03 16:12:57 +00:00
|
|
|
available, mask);
|
|
|
|
|
|
|
|
transfer->source_data.size = current + len;
|
|
|
|
if (transfer->source_data.size >= INCR_CHUNK_SIZE) {
|
|
|
|
if (!transfer->incr) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "got %zu bytes, starting incr",
|
2018-04-03 16:12:57 +00:00
|
|
|
transfer->source_data.size);
|
|
|
|
|
|
|
|
size_t incr_chunk_size = INCR_CHUNK_SIZE;
|
|
|
|
xcb_change_property(xwm->xcb_conn,
|
|
|
|
XCB_PROP_MODE_REPLACE,
|
|
|
|
transfer->request.requestor,
|
|
|
|
transfer->request.property,
|
|
|
|
xwm->atoms[INCR],
|
|
|
|
32, /* format */
|
|
|
|
1, &incr_chunk_size);
|
|
|
|
transfer->incr = true;
|
|
|
|
transfer->property_set = true;
|
|
|
|
transfer->flush_property_on_delete = true;
|
2021-01-25 02:09:53 +00:00
|
|
|
xwm_selection_transfer_remove_event_source(transfer);
|
2018-04-03 16:12:57 +00:00
|
|
|
xwm_selection_send_notify(xwm, &transfer->request, true);
|
|
|
|
} else if (transfer->property_set) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "got %zu bytes, waiting for property delete",
|
2018-04-03 16:12:57 +00:00
|
|
|
transfer->source_data.size);
|
|
|
|
|
|
|
|
transfer->flush_property_on_delete = true;
|
2021-01-25 02:09:53 +00:00
|
|
|
xwm_selection_transfer_remove_event_source(transfer);
|
2018-04-03 16:12:57 +00:00
|
|
|
} else {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "got %zu bytes, property deleted, setting new "
|
2018-04-03 16:12:57 +00:00
|
|
|
"property", transfer->source_data.size);
|
|
|
|
xwm_selection_flush_source_data(transfer);
|
|
|
|
}
|
|
|
|
} else if (len == 0 && !transfer->incr) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "non-incr transfer complete");
|
2018-04-03 16:12:57 +00:00
|
|
|
xwm_selection_flush_source_data(transfer);
|
|
|
|
xwm_selection_send_notify(xwm, &transfer->request, true);
|
|
|
|
xwm_selection_transfer_destroy_outgoing(transfer);
|
|
|
|
} else if (len == 0 && transfer->incr) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "incr transfer complete");
|
2018-04-03 16:12:57 +00:00
|
|
|
|
|
|
|
transfer->flush_property_on_delete = true;
|
|
|
|
if (transfer->property_set) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "got %zu bytes, waiting for property delete",
|
2018-04-03 16:12:57 +00:00
|
|
|
transfer->source_data.size);
|
|
|
|
} else {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "got %zu bytes, property deleted, setting new "
|
2018-04-03 16:12:57 +00:00
|
|
|
"property", transfer->source_data.size);
|
|
|
|
xwm_selection_flush_source_data(transfer);
|
|
|
|
}
|
2021-01-25 02:09:53 +00:00
|
|
|
xwm_selection_transfer_remove_event_source(transfer);
|
|
|
|
xwm_selection_transfer_close_wl_client_fd(transfer);
|
2018-04-03 16:12:57 +00:00
|
|
|
} else {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "nothing happened, buffered the bytes");
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
error_out:
|
|
|
|
xwm_selection_send_notify(xwm, &transfer->request, false);
|
|
|
|
xwm_selection_transfer_destroy_outgoing(transfer);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void xwm_send_incr_chunk(struct wlr_xwm_selection_transfer *transfer) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "property deleted");
|
2018-04-03 16:12:57 +00:00
|
|
|
|
|
|
|
transfer->property_set = false;
|
|
|
|
if (transfer->flush_property_on_delete) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "setting new property, %zu bytes",
|
2018-04-03 16:12:57 +00:00
|
|
|
transfer->source_data.size);
|
|
|
|
transfer->flush_property_on_delete = false;
|
|
|
|
int length = xwm_selection_flush_source_data(transfer);
|
|
|
|
|
2021-01-25 02:09:53 +00:00
|
|
|
if (transfer->wl_client_fd >= 0) {
|
2018-04-03 16:12:57 +00:00
|
|
|
xwm_selection_transfer_start_outgoing(transfer);
|
|
|
|
} else if (length > 0) {
|
|
|
|
/* Transfer is all done, but queue a flush for
|
|
|
|
* the delete of the last chunk so we can set
|
|
|
|
* the 0 sized property to signal the end of
|
|
|
|
* the transfer. */
|
|
|
|
transfer->flush_property_on_delete = true;
|
|
|
|
wl_array_release(&transfer->source_data);
|
|
|
|
wl_array_init(&transfer->source_data);
|
|
|
|
} else {
|
|
|
|
xwm_selection_transfer_destroy_outgoing(transfer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xwm_selection_source_send(struct wlr_xwm_selection *selection,
|
|
|
|
const char *mime_type, int32_t fd) {
|
|
|
|
if (selection == &selection->xwm->clipboard_selection) {
|
|
|
|
struct wlr_data_source *source =
|
|
|
|
selection->xwm->seat->selection_source;
|
|
|
|
if (source != NULL) {
|
|
|
|
wlr_data_source_send(source, mime_type, fd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (selection == &selection->xwm->primary_selection) {
|
2018-11-28 15:37:35 +00:00
|
|
|
struct wlr_primary_selection_source *source =
|
2018-04-03 16:12:57 +00:00
|
|
|
selection->xwm->seat->primary_selection_source;
|
|
|
|
if (source != NULL) {
|
2018-11-28 15:37:35 +00:00
|
|
|
wlr_primary_selection_source_send(source, mime_type, fd);
|
2018-04-03 16:12:57 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (selection == &selection->xwm->dnd_selection) {
|
|
|
|
struct wlr_data_source *source =
|
|
|
|
selection->xwm->seat->drag_source;
|
|
|
|
if (source != NULL) {
|
|
|
|
wlr_data_source_send(source, mime_type, fd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "not sending selection: no selection source available");
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void xwm_selection_transfer_start_outgoing(
|
|
|
|
struct wlr_xwm_selection_transfer *transfer) {
|
|
|
|
struct wlr_xwm *xwm = transfer->selection->xwm;
|
|
|
|
struct wl_event_loop *loop =
|
|
|
|
wl_display_get_event_loop(xwm->xwayland->wl_display);
|
xwayland: remove stale transfers from the same requestor
It seems that if we ever try to reply to a selection request after
another has been sent by the same requestor (we reply in FIFO order),
the requestor never reads from it, and we end up stalling forever on a
transfer that will never complete.
It appears that `XCB_SELECTION_REQUEST` has some sort of singleton
semantics, and new requests for the same selection are meant to replace
outstanding older ones. I couldn't find a reference for this, but
empirically this does seem to be the case.
Real (contrived) case where we don't currently do this, and things break:
* run fcitx
* run Slack
* wl-copy < <(base64 /opt/firefox/libxul.so) # or some other large file
* focus Slack (no need to paste)
fcitx will send in an `XCB_SELECTION_REQUEST`, and we'll start
processing it. Immediately after, Slack sends its own. fcitx hangs for a
long, long time. In the meantime, Slack retries and sends another
selection request. We now have two pending requests from Slack.
Eventually fcitx gives up (or it can be `pkill`'d), and we start
processing the first request Slack gave us (FIFO). Slack (Electron?)
isn't listening on the other end anymore, and this transfer never
completes. The X11 clipboard becomes unusable until Slack is killed.
After this patch, the clipboard is immediately usable again after fcitx
bails. Also added a bunch of debug-level logging that makes diagnosing
this sort of issue easier.
Refs swaywm/sway#4007.
2020-10-12 01:25:26 +00:00
|
|
|
wlr_log(WLR_DEBUG, "Starting transfer %p", transfer);
|
2021-01-25 02:09:53 +00:00
|
|
|
transfer->event_source = wl_event_loop_add_fd(loop, transfer->wl_client_fd,
|
2018-04-03 16:12:57 +00:00
|
|
|
WL_EVENT_READABLE, xwm_data_source_read, transfer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct wl_array *xwm_selection_source_get_mime_types(
|
|
|
|
struct wlr_xwm_selection *selection) {
|
|
|
|
if (selection == &selection->xwm->clipboard_selection) {
|
|
|
|
struct wlr_data_source *source =
|
|
|
|
selection->xwm->seat->selection_source;
|
|
|
|
if (source != NULL) {
|
|
|
|
return &source->mime_types;
|
|
|
|
}
|
|
|
|
} else if (selection == &selection->xwm->primary_selection) {
|
2018-11-28 15:37:35 +00:00
|
|
|
struct wlr_primary_selection_source *source =
|
2018-04-03 16:12:57 +00:00
|
|
|
selection->xwm->seat->primary_selection_source;
|
|
|
|
if (source != NULL) {
|
|
|
|
return &source->mime_types;
|
|
|
|
}
|
|
|
|
} else if (selection == &selection->xwm->dnd_selection) {
|
|
|
|
struct wlr_data_source *source =
|
|
|
|
selection->xwm->seat->drag_source;
|
|
|
|
if (source != NULL) {
|
|
|
|
return &source->mime_types;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Read the Wayland selection and send it to an Xwayland client.
|
|
|
|
*/
|
2020-10-13 03:47:29 +00:00
|
|
|
static bool xwm_selection_send_data(struct wlr_xwm_selection *selection,
|
2018-04-03 16:12:57 +00:00
|
|
|
xcb_selection_request_event_t *req, const char *mime_type) {
|
|
|
|
// Check MIME type
|
|
|
|
struct wl_array *mime_types =
|
|
|
|
xwm_selection_source_get_mime_types(selection);
|
|
|
|
if (mime_types == NULL) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_ERROR, "not sending selection: no MIME type list available");
|
2020-10-13 03:47:29 +00:00
|
|
|
return false;
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool found = false;
|
|
|
|
char **mime_type_ptr;
|
|
|
|
wl_array_for_each(mime_type_ptr, mime_types) {
|
|
|
|
char *t = *mime_type_ptr;
|
|
|
|
if (strcmp(t, mime_type) == 0) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_ERROR, "not sending selection: "
|
2018-04-03 16:12:57 +00:00
|
|
|
"requested an unsupported MIME type %s", mime_type);
|
2020-10-13 03:47:29 +00:00
|
|
|
return false;
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
|
|
|
|
2023-10-03 05:51:07 +00:00
|
|
|
struct wlr_xwm_selection_transfer *transfer = calloc(1, sizeof(*transfer));
|
2018-04-03 16:12:57 +00:00
|
|
|
if (transfer == NULL) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_ERROR, "Allocation failed");
|
2020-10-13 03:47:29 +00:00
|
|
|
return false;
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
2020-10-13 03:47:29 +00:00
|
|
|
|
2021-01-31 04:43:25 +00:00
|
|
|
xwm_selection_transfer_init(transfer, selection);
|
2018-04-03 16:12:57 +00:00
|
|
|
transfer->request = *req;
|
|
|
|
wl_array_init(&transfer->source_data);
|
|
|
|
|
|
|
|
int p[2];
|
|
|
|
if (pipe(p) == -1) {
|
2020-10-11 02:58:57 +00:00
|
|
|
wlr_log_errno(WLR_ERROR, "pipe() failed");
|
2020-10-13 03:47:29 +00:00
|
|
|
return false;
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
2020-10-13 03:47:29 +00:00
|
|
|
|
2018-04-03 16:12:57 +00:00
|
|
|
fcntl(p[0], F_SETFD, FD_CLOEXEC);
|
|
|
|
fcntl(p[0], F_SETFL, O_NONBLOCK);
|
|
|
|
fcntl(p[1], F_SETFD, FD_CLOEXEC);
|
|
|
|
fcntl(p[1], F_SETFL, O_NONBLOCK);
|
|
|
|
|
2021-01-25 02:09:53 +00:00
|
|
|
transfer->wl_client_fd = p[0];
|
2018-04-03 16:12:57 +00:00
|
|
|
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "Sending Wayland selection %u to Xwayland window with "
|
xwayland: remove stale transfers from the same requestor
It seems that if we ever try to reply to a selection request after
another has been sent by the same requestor (we reply in FIFO order),
the requestor never reads from it, and we end up stalling forever on a
transfer that will never complete.
It appears that `XCB_SELECTION_REQUEST` has some sort of singleton
semantics, and new requests for the same selection are meant to replace
outstanding older ones. I couldn't find a reference for this, but
empirically this does seem to be the case.
Real (contrived) case where we don't currently do this, and things break:
* run fcitx
* run Slack
* wl-copy < <(base64 /opt/firefox/libxul.so) # or some other large file
* focus Slack (no need to paste)
fcitx will send in an `XCB_SELECTION_REQUEST`, and we'll start
processing it. Immediately after, Slack sends its own. fcitx hangs for a
long, long time. In the meantime, Slack retries and sends another
selection request. We now have two pending requests from Slack.
Eventually fcitx gives up (or it can be `pkill`'d), and we start
processing the first request Slack gave us (FIFO). Slack (Electron?)
isn't listening on the other end anymore, and this transfer never
completes. The X11 clipboard becomes unusable until Slack is killed.
After this patch, the clipboard is immediately usable again after fcitx
bails. Also added a bunch of debug-level logging that makes diagnosing
this sort of issue easier.
Refs swaywm/sway#4007.
2020-10-12 01:25:26 +00:00
|
|
|
"MIME type %s, target %u, transfer %p", req->target, mime_type,
|
|
|
|
req->target, transfer);
|
2018-04-03 16:12:57 +00:00
|
|
|
xwm_selection_source_send(selection, mime_type, p[1]);
|
|
|
|
|
xwayland: remove stale transfers from the same requestor
It seems that if we ever try to reply to a selection request after
another has been sent by the same requestor (we reply in FIFO order),
the requestor never reads from it, and we end up stalling forever on a
transfer that will never complete.
It appears that `XCB_SELECTION_REQUEST` has some sort of singleton
semantics, and new requests for the same selection are meant to replace
outstanding older ones. I couldn't find a reference for this, but
empirically this does seem to be the case.
Real (contrived) case where we don't currently do this, and things break:
* run fcitx
* run Slack
* wl-copy < <(base64 /opt/firefox/libxul.so) # or some other large file
* focus Slack (no need to paste)
fcitx will send in an `XCB_SELECTION_REQUEST`, and we'll start
processing it. Immediately after, Slack sends its own. fcitx hangs for a
long, long time. In the meantime, Slack retries and sends another
selection request. We now have two pending requests from Slack.
Eventually fcitx gives up (or it can be `pkill`'d), and we start
processing the first request Slack gave us (FIFO). Slack (Electron?)
isn't listening on the other end anymore, and this transfer never
completes. The X11 clipboard becomes unusable until Slack is killed.
After this patch, the clipboard is immediately usable again after fcitx
bails. Also added a bunch of debug-level logging that makes diagnosing
this sort of issue easier.
Refs swaywm/sway#4007.
2020-10-12 01:25:26 +00:00
|
|
|
// It seems that if we ever try to reply to a selection request after
|
|
|
|
// another has been sent by the same requestor, the requestor never reads
|
|
|
|
// from it. It appears to only ever read from the latest, so purge stale
|
|
|
|
// transfers to prevent clipboard hangs.
|
|
|
|
struct wlr_xwm_selection_transfer *outgoing, *tmp;
|
2021-01-31 04:43:25 +00:00
|
|
|
wl_list_for_each_safe(outgoing, tmp, &selection->outgoing, link) {
|
xwayland: remove stale transfers from the same requestor
It seems that if we ever try to reply to a selection request after
another has been sent by the same requestor (we reply in FIFO order),
the requestor never reads from it, and we end up stalling forever on a
transfer that will never complete.
It appears that `XCB_SELECTION_REQUEST` has some sort of singleton
semantics, and new requests for the same selection are meant to replace
outstanding older ones. I couldn't find a reference for this, but
empirically this does seem to be the case.
Real (contrived) case where we don't currently do this, and things break:
* run fcitx
* run Slack
* wl-copy < <(base64 /opt/firefox/libxul.so) # or some other large file
* focus Slack (no need to paste)
fcitx will send in an `XCB_SELECTION_REQUEST`, and we'll start
processing it. Immediately after, Slack sends its own. fcitx hangs for a
long, long time. In the meantime, Slack retries and sends another
selection request. We now have two pending requests from Slack.
Eventually fcitx gives up (or it can be `pkill`'d), and we start
processing the first request Slack gave us (FIFO). Slack (Electron?)
isn't listening on the other end anymore, and this transfer never
completes. The X11 clipboard becomes unusable until Slack is killed.
After this patch, the clipboard is immediately usable again after fcitx
bails. Also added a bunch of debug-level logging that makes diagnosing
this sort of issue easier.
Refs swaywm/sway#4007.
2020-10-12 01:25:26 +00:00
|
|
|
if (transfer->request.requestor == outgoing->request.requestor) {
|
|
|
|
wlr_log(WLR_DEBUG, "Destroying stale transfer %p", outgoing);
|
|
|
|
xwm_selection_send_notify(selection->xwm, &outgoing->request, false);
|
|
|
|
xwm_selection_transfer_destroy_outgoing(outgoing);
|
2021-02-02 02:19:00 +00:00
|
|
|
} else {
|
|
|
|
wlr_log(WLR_DEBUG, "Transfer %p still running", outgoing);
|
xwayland: remove stale transfers from the same requestor
It seems that if we ever try to reply to a selection request after
another has been sent by the same requestor (we reply in FIFO order),
the requestor never reads from it, and we end up stalling forever on a
transfer that will never complete.
It appears that `XCB_SELECTION_REQUEST` has some sort of singleton
semantics, and new requests for the same selection are meant to replace
outstanding older ones. I couldn't find a reference for this, but
empirically this does seem to be the case.
Real (contrived) case where we don't currently do this, and things break:
* run fcitx
* run Slack
* wl-copy < <(base64 /opt/firefox/libxul.so) # or some other large file
* focus Slack (no need to paste)
fcitx will send in an `XCB_SELECTION_REQUEST`, and we'll start
processing it. Immediately after, Slack sends its own. fcitx hangs for a
long, long time. In the meantime, Slack retries and sends another
selection request. We now have two pending requests from Slack.
Eventually fcitx gives up (or it can be `pkill`'d), and we start
processing the first request Slack gave us (FIFO). Slack (Electron?)
isn't listening on the other end anymore, and this transfer never
completes. The X11 clipboard becomes unusable until Slack is killed.
After this patch, the clipboard is immediately usable again after fcitx
bails. Also added a bunch of debug-level logging that makes diagnosing
this sort of issue easier.
Refs swaywm/sway#4007.
2020-10-12 01:25:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-31 04:43:25 +00:00
|
|
|
wl_list_insert(&selection->outgoing, &transfer->link);
|
2018-04-03 16:12:57 +00:00
|
|
|
|
2021-02-02 02:19:00 +00:00
|
|
|
xwm_selection_transfer_start_outgoing(transfer);
|
2020-10-13 03:47:29 +00:00
|
|
|
|
|
|
|
return true;
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void xwm_selection_send_targets(struct wlr_xwm_selection *selection,
|
|
|
|
xcb_selection_request_event_t *req) {
|
|
|
|
struct wlr_xwm *xwm = selection->xwm;
|
|
|
|
|
|
|
|
struct wl_array *mime_types =
|
|
|
|
xwm_selection_source_get_mime_types(selection);
|
|
|
|
if (mime_types == NULL) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_ERROR, "not sending selection targets: "
|
2018-04-03 16:12:57 +00:00
|
|
|
"no selection source available");
|
|
|
|
xwm_selection_send_notify(selection->xwm, req, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t n = 2 + mime_types->size / sizeof(char *);
|
|
|
|
xcb_atom_t targets[n];
|
|
|
|
targets[0] = xwm->atoms[TIMESTAMP];
|
|
|
|
targets[1] = xwm->atoms[TARGETS];
|
|
|
|
|
|
|
|
size_t i = 0;
|
|
|
|
char **mime_type_ptr;
|
|
|
|
wl_array_for_each(mime_type_ptr, mime_types) {
|
|
|
|
char *mime_type = *mime_type_ptr;
|
|
|
|
targets[2+i] = xwm_mime_type_to_atom(xwm, mime_type);
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
|
|
|
xcb_change_property(xwm->xcb_conn,
|
|
|
|
XCB_PROP_MODE_REPLACE,
|
|
|
|
req->requestor,
|
|
|
|
req->property,
|
|
|
|
XCB_ATOM_ATOM,
|
|
|
|
32, // format
|
|
|
|
n, targets);
|
|
|
|
|
|
|
|
xwm_selection_send_notify(selection->xwm, req, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xwm_selection_send_timestamp(struct wlr_xwm_selection *selection,
|
|
|
|
xcb_selection_request_event_t *req) {
|
|
|
|
xcb_change_property(selection->xwm->xcb_conn,
|
|
|
|
XCB_PROP_MODE_REPLACE,
|
|
|
|
req->requestor,
|
|
|
|
req->property,
|
|
|
|
XCB_ATOM_INTEGER,
|
|
|
|
32, // format
|
|
|
|
1, &selection->timestamp);
|
|
|
|
|
|
|
|
xwm_selection_send_notify(selection->xwm, req, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void xwm_handle_selection_request(struct wlr_xwm *xwm,
|
|
|
|
xcb_selection_request_event_t *req) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "XCB_SELECTION_REQUEST (time=%u owner=%u, requestor=%u "
|
2018-04-03 16:12:57 +00:00
|
|
|
"selection=%u, target=%u, property=%u)",
|
|
|
|
req->time, req->owner, req->requestor, req->selection, req->target,
|
|
|
|
req->property);
|
|
|
|
|
|
|
|
if (req->selection == xwm->atoms[CLIPBOARD_MANAGER]) {
|
|
|
|
// The wlroots clipboard should already have grabbed the first target,
|
|
|
|
// so just send selection notify now. This isn't synchronized with the
|
|
|
|
// clipboard finishing getting the data, so there's a race here.
|
|
|
|
xwm_selection_send_notify(xwm, req, true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct wlr_xwm_selection *selection =
|
|
|
|
xwm_get_selection(xwm, req->selection);
|
|
|
|
if (selection == NULL) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "received selection request for unknown selection");
|
2020-10-13 03:47:29 +00:00
|
|
|
goto fail_notify_requestor;
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
|
|
|
|
xwayland/selection: ignore requests for anything but the newest data
Our internal state machine gets screwed up if selection events are not
monotonically increasing in time, and we can enter a self-copy loop from
the proxy window that exhausts all pipes.
Snippet of logs when this occurs:
00:00:46.238 [wlr] [xwayland/selection/incoming.c:487] XCB_XFIXES_SELECTION_NOTIFY (selection=277, owner=4194626)
00:00:46.238 [wlr] [xwayland/selection/incoming.c:487] XCB_XFIXES_SELECTION_NOTIFY (selection=277, owner=2097153)
00:00:46.238 [wlr] [xwayland/selection/outgoing.c:378] XCB_SELECTION_REQUEST (time=58979563 owner=2097153, requestor=2097153 selection=277, target=279, property=278)
00:00:46.238 [wlr] [xwayland/selection/outgoing.c:397] ignoring old request from timestamp 58979563; expecting > 58979563
00:00:46.238 [wlr] [xwayland/selection/outgoing.c:29] SendEvent destination=2097153 SelectionNotify(31) time=58979563 requestor=2097153 selection=277 target=279 property=0
00:00:46.238 [wlr] [xwayland/selection/incoming.c:453] XCB_SELECTION_NOTIFY (selection=277, property=0, target=279)
Note that 2097153 is `selection->window`, and 4194626 is Emacs.
The race occurs if the selection owner changes back to our proxy window
between when we get `XCB_XFIXES_SELECTION_NOTIFY` for Emacs and when we
call `xcb_convert_selection` in `incoming.c:source_send` -- the
ConvertSelection request can end up hitting our proxy window, but the
timestamp will be rejected.
Fixes #2192.
2021-02-11 01:42:18 +00:00
|
|
|
if (req->requestor == selection->window) {
|
|
|
|
wlr_log(WLR_ERROR, "selection request should have been caught before");
|
|
|
|
goto fail_notify_requestor;
|
|
|
|
}
|
|
|
|
|
2018-04-03 16:12:57 +00:00
|
|
|
if (selection->window != req->owner) {
|
xwayland/selection: ignore requests for anything but the newest data
Our internal state machine gets screwed up if selection events are not
monotonically increasing in time, and we can enter a self-copy loop from
the proxy window that exhausts all pipes.
Snippet of logs when this occurs:
00:00:46.238 [wlr] [xwayland/selection/incoming.c:487] XCB_XFIXES_SELECTION_NOTIFY (selection=277, owner=4194626)
00:00:46.238 [wlr] [xwayland/selection/incoming.c:487] XCB_XFIXES_SELECTION_NOTIFY (selection=277, owner=2097153)
00:00:46.238 [wlr] [xwayland/selection/outgoing.c:378] XCB_SELECTION_REQUEST (time=58979563 owner=2097153, requestor=2097153 selection=277, target=279, property=278)
00:00:46.238 [wlr] [xwayland/selection/outgoing.c:397] ignoring old request from timestamp 58979563; expecting > 58979563
00:00:46.238 [wlr] [xwayland/selection/outgoing.c:29] SendEvent destination=2097153 SelectionNotify(31) time=58979563 requestor=2097153 selection=277 target=279 property=0
00:00:46.238 [wlr] [xwayland/selection/incoming.c:453] XCB_SELECTION_NOTIFY (selection=277, property=0, target=279)
Note that 2097153 is `selection->window`, and 4194626 is Emacs.
The race occurs if the selection owner changes back to our proxy window
between when we get `XCB_XFIXES_SELECTION_NOTIFY` for Emacs and when we
call `xcb_convert_selection` in `incoming.c:source_send` -- the
ConvertSelection request can end up hitting our proxy window, but the
timestamp will be rejected.
Fixes #2192.
2021-02-11 01:42:18 +00:00
|
|
|
if (req->time != XCB_CURRENT_TIME && req->time < selection->timestamp) {
|
|
|
|
wlr_log(WLR_DEBUG, "ignored old request from timestamp %d; expected > %d",
|
|
|
|
req->time, selection->timestamp);
|
|
|
|
goto fail_notify_requestor;
|
|
|
|
}
|
|
|
|
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_DEBUG, "received selection request with invalid owner");
|
xwayland/selection: ignore requests for anything but the newest data
Our internal state machine gets screwed up if selection events are not
monotonically increasing in time, and we can enter a self-copy loop from
the proxy window that exhausts all pipes.
Snippet of logs when this occurs:
00:00:46.238 [wlr] [xwayland/selection/incoming.c:487] XCB_XFIXES_SELECTION_NOTIFY (selection=277, owner=4194626)
00:00:46.238 [wlr] [xwayland/selection/incoming.c:487] XCB_XFIXES_SELECTION_NOTIFY (selection=277, owner=2097153)
00:00:46.238 [wlr] [xwayland/selection/outgoing.c:378] XCB_SELECTION_REQUEST (time=58979563 owner=2097153, requestor=2097153 selection=277, target=279, property=278)
00:00:46.238 [wlr] [xwayland/selection/outgoing.c:397] ignoring old request from timestamp 58979563; expecting > 58979563
00:00:46.238 [wlr] [xwayland/selection/outgoing.c:29] SendEvent destination=2097153 SelectionNotify(31) time=58979563 requestor=2097153 selection=277 target=279 property=0
00:00:46.238 [wlr] [xwayland/selection/incoming.c:453] XCB_SELECTION_NOTIFY (selection=277, property=0, target=279)
Note that 2097153 is `selection->window`, and 4194626 is Emacs.
The race occurs if the selection owner changes back to our proxy window
between when we get `XCB_XFIXES_SELECTION_NOTIFY` for Emacs and when we
call `xcb_convert_selection` in `incoming.c:source_send` -- the
ConvertSelection request can end up hitting our proxy window, but the
timestamp will be rejected.
Fixes #2192.
2021-02-11 01:42:18 +00:00
|
|
|
// Don't fail (`goto fail_notify_requestor`) the selection request if we're
|
|
|
|
// no longer the selection owner.
|
|
|
|
return;
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// No xwayland surface focused, deny access to clipboard
|
|
|
|
if (xwm->focus_surface == NULL && xwm->drag_focus == NULL) {
|
2023-07-28 12:21:37 +00:00
|
|
|
if (wlr_log_get_verbosity() >= WLR_DEBUG) {
|
|
|
|
char *selection_name = xwm_get_atom_name(xwm, selection->atom);
|
|
|
|
wlr_log(WLR_DEBUG, "denying read access to selection %u (%s): "
|
|
|
|
"no xwayland surface focused", selection->atom, selection_name);
|
|
|
|
free(selection_name);
|
|
|
|
}
|
2020-10-13 03:47:29 +00:00
|
|
|
goto fail_notify_requestor;
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (req->target == xwm->atoms[TARGETS]) {
|
|
|
|
xwm_selection_send_targets(selection, req);
|
|
|
|
} else if (req->target == xwm->atoms[TIMESTAMP]) {
|
|
|
|
xwm_selection_send_timestamp(selection, req);
|
|
|
|
} else if (req->target == xwm->atoms[DELETE]) {
|
|
|
|
xwm_selection_send_notify(selection->xwm, req, true);
|
|
|
|
} else {
|
|
|
|
// Send data
|
|
|
|
char *mime_type = xwm_mime_type_from_atom(xwm, req->target);
|
|
|
|
if (mime_type == NULL) {
|
2018-07-09 21:49:54 +00:00
|
|
|
wlr_log(WLR_ERROR, "ignoring selection request: unknown atom %u",
|
2018-04-03 16:12:57 +00:00
|
|
|
req->target);
|
2020-10-13 03:47:29 +00:00
|
|
|
goto fail_notify_requestor;
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
2020-10-13 03:47:29 +00:00
|
|
|
|
|
|
|
bool send_success = xwm_selection_send_data(selection, req, mime_type);
|
2018-04-03 16:12:57 +00:00
|
|
|
free(mime_type);
|
2020-10-13 03:47:29 +00:00
|
|
|
if (!send_success) {
|
|
|
|
goto fail_notify_requestor;
|
|
|
|
}
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
2020-10-13 03:47:29 +00:00
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
fail_notify_requestor:
|
|
|
|
// Something went wrong, and there won't be any data being sent to the
|
|
|
|
// requestor, so let them know.
|
|
|
|
xwm_selection_send_notify(xwm, req, false);
|
2018-04-03 16:12:57 +00:00
|
|
|
}
|
2020-07-03 02:25:25 +00:00
|
|
|
|
|
|
|
void xwm_handle_selection_destroy_notify(struct wlr_xwm *xwm,
|
|
|
|
xcb_destroy_notify_event_t *event) {
|
|
|
|
struct wlr_xwm_selection *selections[] = {
|
|
|
|
&xwm->clipboard_selection,
|
|
|
|
&xwm->primary_selection,
|
|
|
|
&xwm->dnd_selection,
|
|
|
|
};
|
|
|
|
|
|
|
|
for (size_t i = 0; i < sizeof(selections)/sizeof(selections[0]); ++i) {
|
|
|
|
struct wlr_xwm_selection *selection = selections[i];
|
|
|
|
|
|
|
|
struct wlr_xwm_selection_transfer *outgoing, *tmp;
|
2021-01-31 04:43:25 +00:00
|
|
|
wl_list_for_each_safe(outgoing, tmp, &selection->outgoing, link) {
|
2020-07-03 02:25:25 +00:00
|
|
|
if (event->window == outgoing->request.requestor) {
|
|
|
|
xwm_selection_transfer_destroy_outgoing(outgoing);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|