|
|
|
#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: 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)) {
|
|
|
|
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);
|
|
|
|
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 void 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");
|
|
|
|
xwm_selection_send_notify(selection->xwm, req, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
xwm_selection_send_notify(selection->xwm, req, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct wlr_xwm_selection_transfer *transfer =
|
|
|
|
calloc(1, sizeof(struct wlr_xwm_selection_transfer));
|
|
|
|
if (transfer == NULL) {
|
|
|
|
wlr_log(WLR_ERROR, "Allocation failed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
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");
|
|
|
|
xwm_selection_send_notify(selection->xwm, req, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
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 "
|
|
|
|
"MIME type %s, target %u", req->target, mime_type, req->target);
|
|
|
|
xwm_selection_source_send(selection, mime_type, p[1]);
|
|
|
|
|
|
|
|
wl_list_insert(&selection->outgoing, &transfer->outgoing_link);
|
|
|
|
|
|
|
|
// We can only handle one transfer at a time
|
|
|
|
if (wl_list_length(&selection->outgoing) == 1) {
|
|
|
|
xwm_selection_transfer_start_outgoing(transfer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selection->window != req->owner) {
|
|
|
|
wlr_log(WLR_DEBUG, "received selection request with invalid owner");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
xwm_selection_send_notify(xwm, req, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
xwm_selection_send_notify(xwm, req, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
xwm_selection_send_data(selection, req, mime_type);
|
|
|
|
free(mime_type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|