|
|
|
#include <assert.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <wayland-util.h>
|
|
|
|
#include <wlr/types/wlr_data_device.h>
|
|
|
|
#include <wlr/types/wlr_primary_selection.h>
|
|
|
|
#include <wlr/util/log.h>
|
|
|
|
#include <xcb/xfixes.h>
|
|
|
|
#include "xwayland/selection.h"
|
|
|
|
#include "xwayland/xwm.h"
|
|
|
|
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
|
|
|
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,
|
|
|
|
selection_notify.property);
|
|
|
|
xcb_send_event(xwm->xcb_conn,
|
|
|
|
0, // propagate
|
|
|
|
req->requestor,
|
|
|
|
XCB_EVENT_MASK_NO_EVENT,
|
|
|
|
(const char *)&selection_notify);
|
|
|
|
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);
|
|
|
|
|
xwayland: fix use-after-free in selection handling
Fixes #2425.
wlroots can only handle one outgoing transfer at a time, so it keeps a
list of pending selections. The head of the list is the currently-active
selection, and when that transfer completes and is destroyed, the next
one is started.
The trouble is when you have a transfer to some app that is misbehaving.
fcitx is one such application. With really large transfers, fcitx will
hang and never wake up again. So, you can end up with a transfer list
that looks like this:
| T1: started | T2: pending | T3: pending | T4: pending |
The file descriptor for transfer T1 is registered in libwayland's epoll
loop. The rest are waiting in wlroots' list.
As a user, you want your clipboard back, so you `pkill fcitx`. Now
Xwayland sends `XCB_DESTROY_NOTIFY` to let us know to give up. We clean
up T4 first.
Due to a bug in wlroots code, we register the (fd, transfer data
pointer) pair for T1 with libwayland *again*, despite it already being
registered. We do this 2 more times as we remove T3 and T2.
Finally, we remove T1 and `free` all the memory associated with it,
before `close`-ing its transfer file descriptor.
However, we still have 3 copies of T1's file descriptor left in the
epoll loop, since we erroneously added them as part of removing T2/3/4.
When we `close` the file descriptor as part of T1's teardown, we
actually cause the epoll loop to wake up the next time around, saying
"this file descriptor has activity!" (it was closed, so `read`-ing would
normally return 0 to let us know of EOF).
But instead of returning 0, it returns -1 with `EBADF`, because the file
descriptor has already been closed. And finally, as part of error-handling
this, we access the transfer pointer, which was `free`'d. And we crash.
4 years ago
|
|
|
static struct wlr_xwm_selection_transfer *xwm_selection_transfer_get_first(
|
|
|
|
struct wlr_xwm_selection *selection) {
|
|
|
|
struct wlr_xwm_selection_transfer *first = NULL;
|
|
|
|
if (!wl_list_empty(&selection->outgoing)) {
|
|
|
|
first = wl_container_of(selection->outgoing.prev, first,
|
|
|
|
outgoing_link);
|
|
|
|
}
|
|
|
|
|
|
|
|
return first;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xwm_selection_transfer_destroy_outgoing(
|
|
|
|
struct wlr_xwm_selection_transfer *transfer) {
|
xwayland: fix use-after-free in selection handling
Fixes #2425.
wlroots can only handle one outgoing transfer at a time, so it keeps a
list of pending selections. The head of the list is the currently-active
selection, and when that transfer completes and is destroyed, the next
one is started.
The trouble is when you have a transfer to some app that is misbehaving.
fcitx is one such application. With really large transfers, fcitx will
hang and never wake up again. So, you can end up with a transfer list
that looks like this:
| T1: started | T2: pending | T3: pending | T4: pending |
The file descriptor for transfer T1 is registered in libwayland's epoll
loop. The rest are waiting in wlroots' list.
As a user, you want your clipboard back, so you `pkill fcitx`. Now
Xwayland sends `XCB_DESTROY_NOTIFY` to let us know to give up. We clean
up T4 first.
Due to a bug in wlroots code, we register the (fd, transfer data
pointer) pair for T1 with libwayland *again*, despite it already being
registered. We do this 2 more times as we remove T3 and T2.
Finally, we remove T1 and `free` all the memory associated with it,
before `close`-ing its transfer file descriptor.
However, we still have 3 copies of T1's file descriptor left in the
epoll loop, since we erroneously added them as part of removing T2/3/4.
When we `close` the file descriptor as part of T1's teardown, we
actually cause the epoll loop to wake up the next time around, saying
"this file descriptor has activity!" (it was closed, so `read`-ing would
normally return 0 to let us know of EOF).
But instead of returning 0, it returns -1 with `EBADF`, because the file
descriptor has already been closed. And finally, as part of error-handling
this, we access the transfer pointer, which was `free`'d. And we crash.
4 years ago
|
|
|
struct wlr_xwm_selection *selection = transfer->selection;
|
|
|
|
bool was_first = transfer == xwm_selection_transfer_get_first(selection);
|
|
|
|
wl_list_remove(&transfer->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.
4 years ago
|
|
|
wlr_log(WLR_DEBUG, "Destroying transfer %p", transfer);
|
|
|
|
|
xwayland: fix use-after-free in selection handling
Fixes #2425.
wlroots can only handle one outgoing transfer at a time, so it keeps a
list of pending selections. The head of the list is the currently-active
selection, and when that transfer completes and is destroyed, the next
one is started.
The trouble is when you have a transfer to some app that is misbehaving.
fcitx is one such application. With really large transfers, fcitx will
hang and never wake up again. So, you can end up with a transfer list
that looks like this:
| T1: started | T2: pending | T3: pending | T4: pending |
The file descriptor for transfer T1 is registered in libwayland's epoll
loop. The rest are waiting in wlroots' list.
As a user, you want your clipboard back, so you `pkill fcitx`. Now
Xwayland sends `XCB_DESTROY_NOTIFY` to let us know to give up. We clean
up T4 first.
Due to a bug in wlroots code, we register the (fd, transfer data
pointer) pair for T1 with libwayland *again*, despite it already being
registered. We do this 2 more times as we remove T3 and T2.
Finally, we remove T1 and `free` all the memory associated with it,
before `close`-ing its transfer file descriptor.
However, we still have 3 copies of T1's file descriptor left in the
epoll loop, since we erroneously added them as part of removing T2/3/4.
When we `close` the file descriptor as part of T1's teardown, we
actually cause the epoll loop to wake up the next time around, saying
"this file descriptor has activity!" (it was closed, so `read`-ing would
normally return 0 to let us know of EOF).
But instead of returning 0, it returns -1 with `EBADF`, because the file
descriptor has already been closed. And finally, as part of error-handling
this, we access the transfer pointer, which was `free`'d. And we crash.
4 years ago
|
|
|
// Start next queued transfer if we just removed the active one.
|
|
|
|
if (was_first && !wl_list_empty(&selection->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.
4 years ago
|
|
|
wlr_log(WLR_DEBUG, "Destroyed transfer was active, starting next");
|
xwayland: fix use-after-free in selection handling
Fixes #2425.
wlroots can only handle one outgoing transfer at a time, so it keeps a
list of pending selections. The head of the list is the currently-active
selection, and when that transfer completes and is destroyed, the next
one is started.
The trouble is when you have a transfer to some app that is misbehaving.
fcitx is one such application. With really large transfers, fcitx will
hang and never wake up again. So, you can end up with a transfer list
that looks like this:
| T1: started | T2: pending | T3: pending | T4: pending |
The file descriptor for transfer T1 is registered in libwayland's epoll
loop. The rest are waiting in wlroots' list.
As a user, you want your clipboard back, so you `pkill fcitx`. Now
Xwayland sends `XCB_DESTROY_NOTIFY` to let us know to give up. We clean
up T4 first.
Due to a bug in wlroots code, we register the (fd, transfer data
pointer) pair for T1 with libwayland *again*, despite it already being
registered. We do this 2 more times as we remove T3 and T2.
Finally, we remove T1 and `free` all the memory associated with it,
before `close`-ing its transfer file descriptor.
However, we still have 3 copies of T1's file descriptor left in the
epoll loop, since we erroneously added them as part of removing T2/3/4.
When we `close` the file descriptor as part of T1's teardown, we
actually cause the epoll loop to wake up the next time around, saying
"this file descriptor has activity!" (it was closed, so `read`-ing would
normally return 0 to let us know of EOF).
But instead of returning 0, it returns -1 with `EBADF`, because the file
descriptor has already been closed. And finally, as part of error-handling
this, we access the transfer pointer, which was `free`'d. And we crash.
4 years ago
|
|
|
xwm_selection_transfer_start_outgoing(
|
|
|
|
xwm_selection_transfer_get_first(selection));
|
|
|
|
}
|
|
|
|
|
|
|
|
xwm_selection_transfer_remove_source(transfer);
|
|
|
|
xwm_selection_transfer_close_source_fd(transfer);
|
|
|
|
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) {
|
|
|
|
wlr_log(WLR_ERROR, "Could not allocate selection source_data");
|
|
|
|
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) {
|
|
|
|
wlr_log_errno(WLR_ERROR, "read error from data source");
|
|
|
|
goto error_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
wlr_log(WLR_DEBUG, "read %zd bytes (available %zu, mask 0x%x)", len,
|
|
|
|
available, mask);
|
|
|
|
|
|
|
|
transfer->source_data.size = current + len;
|
|
|
|
if (transfer->source_data.size >= INCR_CHUNK_SIZE) {
|
|
|
|
if (!transfer->incr) {
|
|
|
|
wlr_log(WLR_DEBUG, "got %zu bytes, starting incr",
|
|
|
|
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;
|
|
|
|
xwm_selection_transfer_remove_source(transfer);
|
|
|
|
xwm_selection_send_notify(xwm, &transfer->request, true);
|
|
|
|
} else if (transfer->property_set) {
|
|
|
|
wlr_log(WLR_DEBUG, "got %zu bytes, waiting for property delete",
|
|
|
|
transfer->source_data.size);
|
|
|
|
|
|
|
|
transfer->flush_property_on_delete = true;
|
|
|
|
xwm_selection_transfer_remove_source(transfer);
|
|
|
|
} else {
|
|
|
|
wlr_log(WLR_DEBUG, "got %zu bytes, property deleted, setting new "
|
|
|
|
"property", transfer->source_data.size);
|
|
|
|
xwm_selection_flush_source_data(transfer);
|
|
|
|
}
|
|
|
|
} else if (len == 0 && !transfer->incr) {
|
|
|
|
wlr_log(WLR_DEBUG, "non-incr transfer complete");
|
|
|
|
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) {
|
|
|
|
wlr_log(WLR_DEBUG, "incr transfer complete");
|
|
|
|
|
|
|
|
transfer->flush_property_on_delete = true;
|
|
|
|
if (transfer->property_set) {
|
|
|
|
wlr_log(WLR_DEBUG, "got %zu bytes, waiting for property delete",
|
|
|
|
transfer->source_data.size);
|
|
|
|
} else {
|
|
|
|
wlr_log(WLR_DEBUG, "got %zu bytes, property deleted, setting new "
|
|
|
|
"property", transfer->source_data.size);
|
|
|
|
xwm_selection_flush_source_data(transfer);
|
|
|
|
}
|
|
|
|
xwm_selection_transfer_remove_source(transfer);
|
|
|
|
xwm_selection_transfer_close_source_fd(transfer);
|
|
|
|
} else {
|
|
|
|
wlr_log(WLR_DEBUG, "nothing happened, buffered the bytes");
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
wlr_log(WLR_DEBUG, "property deleted");
|
|
|
|
|
|
|
|
transfer->property_set = false;
|
|
|
|
if (transfer->flush_property_on_delete) {
|
|
|
|
wlr_log(WLR_DEBUG, "setting new property, %zu bytes",
|
|
|
|
transfer->source_data.size);
|
|
|
|
transfer->flush_property_on_delete = false;
|
|
|
|
int length = xwm_selection_flush_source_data(transfer);
|
|
|
|
|
|
|
|
if (transfer->source_fd >= 0) {
|
|
|
|
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) {
|
|
|
|
struct wlr_primary_selection_source *source =
|
|
|
|
selection->xwm->seat->primary_selection_source;
|
|
|
|
if (source != NULL) {
|
|
|
|
wlr_primary_selection_source_send(source, mime_type, fd);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wlr_log(WLR_DEBUG, "not sending selection: no selection source available");
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
4 years ago
|
|
|
wlr_log(WLR_DEBUG, "Starting transfer %p", transfer);
|
|
|
|
assert(transfer == xwm_selection_transfer_get_first(transfer->selection));
|
|
|
|
transfer->source = wl_event_loop_add_fd(loop, transfer->source_fd,
|
|
|
|
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) {
|
|
|
|
struct wlr_primary_selection_source *source =
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
static bool xwm_selection_send_data(struct wlr_xwm_selection *selection,
|
|
|
|
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) {
|
|
|
|
wlr_log(WLR_ERROR, "not sending selection: no MIME type list available");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
wlr_log(WLR_ERROR, "not sending selection: "
|
|
|
|
"requested an unsupported MIME type %s", mime_type);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct wlr_xwm_selection_transfer *transfer =
|
|
|
|
calloc(1, sizeof(struct wlr_xwm_selection_transfer));
|
|
|
|
if (transfer == NULL) {
|
|
|
|
wlr_log(WLR_ERROR, "Allocation failed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
transfer->selection = selection;
|
|
|
|
transfer->request = *req;
|
|
|
|
wl_array_init(&transfer->source_data);
|
|
|
|
|
|
|
|
int p[2];
|
|
|
|
if (pipe(p) == -1) {
|
|
|
|
wlr_log_errno(WLR_ERROR, "pipe() failed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
transfer->source_fd = p[0];
|
|
|
|
|
|
|
|
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.
4 years ago
|
|
|
"MIME type %s, target %u, transfer %p", req->target, mime_type,
|
|
|
|
req->target, transfer);
|
|
|
|
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.
4 years ago
|
|
|
// 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;
|
|
|
|
wl_list_for_each_safe(outgoing, tmp, &selection->outgoing, outgoing_link) {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wl_list_insert(&selection->outgoing, &transfer->outgoing_link);
|
|
|
|
|
|
|
|
// We can only handle one transfer at a time
|
|
|
|
if (wl_list_length(&selection->outgoing) == 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.
4 years ago
|
|
|
wlr_log(WLR_DEBUG, "No transfer active, starting %p now", transfer);
|
|
|
|
xwm_selection_transfer_start_outgoing(transfer);
|
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.
4 years ago
|
|
|
} else {
|
|
|
|
struct wlr_xwm_selection_transfer *outgoing;
|
|
|
|
wl_list_for_each(outgoing, &selection->outgoing, outgoing_link) {
|
|
|
|
wlr_log(WLR_DEBUG, "Transfer %p still queued", outgoing);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
wlr_log(WLR_ERROR, "not sending selection targets: "
|
|
|
|
"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) {
|
|
|
|
wlr_log(WLR_DEBUG, "XCB_SELECTION_REQUEST (time=%u owner=%u, requestor=%u "
|
|
|
|
"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) {
|
|
|
|
wlr_log(WLR_DEBUG, "received selection request for unknown selection");
|
|
|
|
goto fail_notify_requestor;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selection->window != req->owner) {
|
|
|
|
wlr_log(WLR_DEBUG, "received selection request with invalid owner");
|
|
|
|
goto fail_notify_requestor;
|
|
|
|
}
|
|
|
|
|
|
|
|
// No xwayland surface focused, deny access to clipboard
|
|
|
|
if (xwm->focus_surface == NULL && xwm->drag_focus == NULL) {
|
|
|
|
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);
|
|
|
|
goto fail_notify_requestor;
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
wlr_log(WLR_ERROR, "ignoring selection request: unknown atom %u",
|
|
|
|
req->target);
|
|
|
|
goto fail_notify_requestor;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool send_success = xwm_selection_send_data(selection, req, mime_type);
|
|
|
|
free(mime_type);
|
|
|
|
if (!send_success) {
|
|
|
|
goto fail_notify_requestor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
wl_list_for_each_safe(outgoing, tmp, &selection->outgoing, outgoing_link) {
|
|
|
|
if (event->window == outgoing->request.requestor) {
|
|
|
|
xwm_selection_transfer_destroy_outgoing(outgoing);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|