weston/xwayland/selection.c
Derek Foreman 4e18448286 xwm: Don't clear the selection if it has no text type available
weston maintains a copy of the most recently selected "thing" - it picks
the first available type when it copies, and saves that one only.

When an application quits weston will make the saved selection active.

When xwm sees the selection set it will check if any of the offered types
are text.  If no text type is offered it will clear the selection.

weston then interprets this in the same way as an application exiting and
causing the selection to be unset, and we get caught in a live lock with
both weston and xwayland consuming as much cpu as they can.

The simple fix is to just remove the test for text presence.

Signed-off-by: Derek Foreman <derekf@osg.samsung.com>
Reviewed-by: Carlos Garnacho <carlosg@gnome.org>
2016-02-04 16:10:36 -08:00

716 lines
19 KiB
C

/*
* Copyright © 2012 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include "xwayland.h"
#include "shared/helpers.h"
static int
writable_callback(int fd, uint32_t mask, void *data)
{
struct weston_wm *wm = data;
unsigned char *property;
int len, remainder;
property = xcb_get_property_value(wm->property_reply);
remainder = xcb_get_property_value_length(wm->property_reply) -
wm->property_start;
len = write(fd, property + wm->property_start, remainder);
if (len == -1) {
free(wm->property_reply);
wm->property_reply = NULL;
if (wm->property_source)
wl_event_source_remove(wm->property_source);
wm->property_source = NULL;
close(fd);
weston_log("write error to target fd: %m\n");
return 1;
}
weston_log("wrote %d (chunk size %d) of %d bytes\n",
wm->property_start + len,
len, xcb_get_property_value_length(wm->property_reply));
wm->property_start += len;
if (len == remainder) {
free(wm->property_reply);
wm->property_reply = NULL;
if (wm->property_source)
wl_event_source_remove(wm->property_source);
wm->property_source = NULL;
if (wm->incr) {
xcb_delete_property(wm->conn,
wm->selection_window,
wm->atom.wl_selection);
} else {
weston_log("transfer complete\n");
close(fd);
}
}
return 1;
}
static void
weston_wm_write_property(struct weston_wm *wm, xcb_get_property_reply_t *reply)
{
wm->property_start = 0;
wm->property_reply = reply;
writable_callback(wm->data_source_fd, WL_EVENT_WRITABLE, wm);
if (wm->property_reply)
wm->property_source =
wl_event_loop_add_fd(wm->server->loop,
wm->data_source_fd,
WL_EVENT_WRITABLE,
writable_callback, wm);
}
static void
weston_wm_get_incr_chunk(struct weston_wm *wm)
{
xcb_get_property_cookie_t cookie;
xcb_get_property_reply_t *reply;
cookie = xcb_get_property(wm->conn,
0, /* delete */
wm->selection_window,
wm->atom.wl_selection,
XCB_GET_PROPERTY_TYPE_ANY,
0, /* offset */
0x1fffffff /* length */);
reply = xcb_get_property_reply(wm->conn, cookie, NULL);
if (reply == NULL)
return;
dump_property(wm, wm->atom.wl_selection, reply);
if (xcb_get_property_value_length(reply) > 0) {
/* reply's ownership is transfered to wm, which is responsible
* for freeing it */
weston_wm_write_property(wm, reply);
} else {
weston_log("transfer complete\n");
close(wm->data_source_fd);
free(reply);
}
}
struct x11_data_source {
struct weston_data_source base;
struct weston_wm *wm;
};
static void
data_source_accept(struct weston_data_source *source,
uint32_t time, const char *mime_type)
{
}
static void
data_source_send(struct weston_data_source *base,
const char *mime_type, int32_t fd)
{
struct x11_data_source *source = (struct x11_data_source *) base;
struct weston_wm *wm = source->wm;
if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) {
/* Get data for the utf8_string target */
xcb_convert_selection(wm->conn,
wm->selection_window,
wm->atom.clipboard,
wm->atom.utf8_string,
wm->atom.wl_selection,
XCB_TIME_CURRENT_TIME);
xcb_flush(wm->conn);
fcntl(fd, F_SETFL, O_WRONLY | O_NONBLOCK);
wm->data_source_fd = fd;
}
}
static void
data_source_cancel(struct weston_data_source *source)
{
}
static void
weston_wm_get_selection_targets(struct weston_wm *wm)
{
struct x11_data_source *source;
struct weston_compositor *compositor;
struct weston_seat *seat = weston_wm_pick_seat(wm);
xcb_get_property_cookie_t cookie;
xcb_get_property_reply_t *reply;
xcb_atom_t *value;
char **p;
uint32_t i;
cookie = xcb_get_property(wm->conn,
1, /* delete */
wm->selection_window,
wm->atom.wl_selection,
XCB_GET_PROPERTY_TYPE_ANY,
0, /* offset */
4096 /* length */);
reply = xcb_get_property_reply(wm->conn, cookie, NULL);
if (reply == NULL)
return;
dump_property(wm, wm->atom.wl_selection, reply);
if (reply->type != XCB_ATOM_ATOM) {
free(reply);
return;
}
source = zalloc(sizeof *source);
if (source == NULL) {
free(reply);
return;
}
wl_signal_init(&source->base.destroy_signal);
source->base.accept = data_source_accept;
source->base.send = data_source_send;
source->base.cancel = data_source_cancel;
source->wm = wm;
wl_array_init(&source->base.mime_types);
value = xcb_get_property_value(reply);
for (i = 0; i < reply->value_len; i++) {
if (value[i] == wm->atom.utf8_string) {
p = wl_array_add(&source->base.mime_types, sizeof *p);
if (p)
*p = strdup("text/plain;charset=utf-8");
}
}
compositor = wm->server->compositor;
weston_seat_set_selection(seat, &source->base,
wl_display_next_serial(compositor->wl_display));
free(reply);
}
static void
weston_wm_get_selection_data(struct weston_wm *wm)
{
xcb_get_property_cookie_t cookie;
xcb_get_property_reply_t *reply;
cookie = xcb_get_property(wm->conn,
1, /* delete */
wm->selection_window,
wm->atom.wl_selection,
XCB_GET_PROPERTY_TYPE_ANY,
0, /* offset */
0x1fffffff /* length */);
reply = xcb_get_property_reply(wm->conn, cookie, NULL);
dump_property(wm, wm->atom.wl_selection, reply);
if (reply == NULL) {
return;
} else if (reply->type == wm->atom.incr) {
wm->incr = 1;
free(reply);
} else {
wm->incr = 0;
/* reply's ownership is transfered to wm, which is responsible
* for freeing it */
weston_wm_write_property(wm, reply);
}
}
static void
weston_wm_handle_selection_notify(struct weston_wm *wm,
xcb_generic_event_t *event)
{
xcb_selection_notify_event_t *selection_notify =
(xcb_selection_notify_event_t *) event;
if (selection_notify->property == XCB_ATOM_NONE) {
/* convert selection failed */
} else if (selection_notify->target == wm->atom.targets) {
weston_wm_get_selection_targets(wm);
} else {
weston_wm_get_selection_data(wm);
}
}
static const size_t incr_chunk_size = 64 * 1024;
static void
weston_wm_send_selection_notify(struct weston_wm *wm, xcb_atom_t property)
{
xcb_selection_notify_event_t selection_notify;
memset(&selection_notify, 0, sizeof selection_notify);
selection_notify.response_type = XCB_SELECTION_NOTIFY;
selection_notify.sequence = 0;
selection_notify.time = wm->selection_request.time;
selection_notify.requestor = wm->selection_request.requestor;
selection_notify.selection = wm->selection_request.selection;
selection_notify.target = wm->selection_request.target;
selection_notify.property = property;
xcb_send_event(wm->conn, 0, /* propagate */
wm->selection_request.requestor,
XCB_EVENT_MASK_NO_EVENT, (char *) &selection_notify);
}
static void
weston_wm_send_targets(struct weston_wm *wm)
{
xcb_atom_t targets[] = {
wm->atom.timestamp,
wm->atom.targets,
wm->atom.utf8_string,
/* wm->atom.compound_text, */
wm->atom.text,
/* wm->atom.string */
};
xcb_change_property(wm->conn,
XCB_PROP_MODE_REPLACE,
wm->selection_request.requestor,
wm->selection_request.property,
XCB_ATOM_ATOM,
32, /* format */
ARRAY_LENGTH(targets), targets);
weston_wm_send_selection_notify(wm, wm->selection_request.property);
}
static void
weston_wm_send_timestamp(struct weston_wm *wm)
{
xcb_change_property(wm->conn,
XCB_PROP_MODE_REPLACE,
wm->selection_request.requestor,
wm->selection_request.property,
XCB_ATOM_INTEGER,
32, /* format */
1, &wm->selection_timestamp);
weston_wm_send_selection_notify(wm, wm->selection_request.property);
}
static int
weston_wm_flush_source_data(struct weston_wm *wm)
{
int length;
xcb_change_property(wm->conn,
XCB_PROP_MODE_REPLACE,
wm->selection_request.requestor,
wm->selection_request.property,
wm->selection_target,
8, /* format */
wm->source_data.size,
wm->source_data.data);
wm->selection_property_set = 1;
length = wm->source_data.size;
wm->source_data.size = 0;
return length;
}
static int
weston_wm_read_data_source(int fd, uint32_t mask, void *data)
{
struct weston_wm *wm = data;
int len, current, available;
void *p;
current = wm->source_data.size;
if (wm->source_data.size < incr_chunk_size)
p = wl_array_add(&wm->source_data, incr_chunk_size);
else
p = (char *) wm->source_data.data + wm->source_data.size;
available = wm->source_data.alloc - current;
len = read(fd, p, available);
if (len == -1) {
weston_log("read error from data source: %m\n");
weston_wm_send_selection_notify(wm, XCB_ATOM_NONE);
wl_event_source_remove(wm->property_source);
wm->property_source = NULL;
close(fd);
wl_array_release(&wm->source_data);
}
weston_log("read %d (available %d, mask 0x%x) bytes: \"%.*s\"\n",
len, available, mask, len, (char *) p);
wm->source_data.size = current + len;
if (wm->source_data.size >= incr_chunk_size) {
if (!wm->incr) {
weston_log("got %zu bytes, starting incr\n",
wm->source_data.size);
wm->incr = 1;
xcb_change_property(wm->conn,
XCB_PROP_MODE_REPLACE,
wm->selection_request.requestor,
wm->selection_request.property,
wm->atom.incr,
32, /* format */
1, &incr_chunk_size);
wm->selection_property_set = 1;
wm->flush_property_on_delete = 1;
wl_event_source_remove(wm->property_source);
wm->property_source = NULL;
weston_wm_send_selection_notify(wm, wm->selection_request.property);
} else if (wm->selection_property_set) {
weston_log("got %zu bytes, waiting for "
"property delete\n", wm->source_data.size);
wm->flush_property_on_delete = 1;
wl_event_source_remove(wm->property_source);
wm->property_source = NULL;
} else {
weston_log("got %zu bytes, "
"property deleted, seting new property\n",
wm->source_data.size);
weston_wm_flush_source_data(wm);
}
} else if (len == 0 && !wm->incr) {
weston_log("non-incr transfer complete\n");
/* Non-incr transfer all done. */
weston_wm_flush_source_data(wm);
weston_wm_send_selection_notify(wm, wm->selection_request.property);
xcb_flush(wm->conn);
wl_event_source_remove(wm->property_source);
wm->property_source = NULL;
close(fd);
wl_array_release(&wm->source_data);
wm->selection_request.requestor = XCB_NONE;
} else if (len == 0 && wm->incr) {
weston_log("incr transfer complete\n");
wm->flush_property_on_delete = 1;
if (wm->selection_property_set) {
weston_log("got %zu bytes, waiting for "
"property delete\n", wm->source_data.size);
} else {
weston_log("got %zu bytes, "
"property deleted, seting new property\n",
wm->source_data.size);
weston_wm_flush_source_data(wm);
}
xcb_flush(wm->conn);
wl_event_source_remove(wm->property_source);
wm->property_source = NULL;
close(wm->data_source_fd);
wm->data_source_fd = -1;
close(fd);
} else {
weston_log("nothing happened, buffered the bytes\n");
}
return 1;
}
static void
weston_wm_send_data(struct weston_wm *wm, xcb_atom_t target, const char *mime_type)
{
struct weston_data_source *source;
struct weston_seat *seat = weston_wm_pick_seat(wm);
int p[2];
if (pipe2(p, O_CLOEXEC | O_NONBLOCK) == -1) {
weston_log("pipe2 failed: %m\n");
weston_wm_send_selection_notify(wm, XCB_ATOM_NONE);
return;
}
wl_array_init(&wm->source_data);
wm->selection_target = target;
wm->data_source_fd = p[0];
wm->property_source = wl_event_loop_add_fd(wm->server->loop,
wm->data_source_fd,
WL_EVENT_READABLE,
weston_wm_read_data_source,
wm);
source = seat->selection_data_source;
source->send(source, mime_type, p[1]);
close(p[1]);
}
static void
weston_wm_send_incr_chunk(struct weston_wm *wm)
{
int length;
weston_log("property deleted\n");
wm->selection_property_set = 0;
if (wm->flush_property_on_delete) {
weston_log("setting new property, %zu bytes\n",
wm->source_data.size);
wm->flush_property_on_delete = 0;
length = weston_wm_flush_source_data(wm);
if (wm->data_source_fd >= 0) {
wm->property_source =
wl_event_loop_add_fd(wm->server->loop,
wm->data_source_fd,
WL_EVENT_READABLE,
weston_wm_read_data_source,
wm);
} 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 propert to signal the end of
* the transfer. */
wm->flush_property_on_delete = 1;
wl_array_release(&wm->source_data);
} else {
wm->selection_request.requestor = XCB_NONE;
}
}
}
static int
weston_wm_handle_selection_property_notify(struct weston_wm *wm,
xcb_generic_event_t *event)
{
xcb_property_notify_event_t *property_notify =
(xcb_property_notify_event_t *) event;
if (property_notify->window == wm->selection_window) {
if (property_notify->state == XCB_PROPERTY_NEW_VALUE &&
property_notify->atom == wm->atom.wl_selection &&
wm->incr)
weston_wm_get_incr_chunk(wm);
return 1;
} else if (property_notify->window == wm->selection_request.requestor) {
if (property_notify->state == XCB_PROPERTY_DELETE &&
property_notify->atom == wm->selection_request.property &&
wm->incr)
weston_wm_send_incr_chunk(wm);
return 1;
}
return 0;
}
static void
weston_wm_handle_selection_request(struct weston_wm *wm,
xcb_generic_event_t *event)
{
xcb_selection_request_event_t *selection_request =
(xcb_selection_request_event_t *) event;
weston_log("selection request, %s, ",
get_atom_name(wm->conn, selection_request->selection));
weston_log_continue("target %s, ",
get_atom_name(wm->conn, selection_request->target));
weston_log_continue("property %s\n",
get_atom_name(wm->conn, selection_request->property));
wm->selection_request = *selection_request;
wm->incr = 0;
wm->flush_property_on_delete = 0;
if (selection_request->selection == wm->atom.clipboard_manager) {
/* The weston 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. */
weston_wm_send_selection_notify(wm, wm->selection_request.property);
return;
}
if (selection_request->target == wm->atom.targets) {
weston_wm_send_targets(wm);
} else if (selection_request->target == wm->atom.timestamp) {
weston_wm_send_timestamp(wm);
} else if (selection_request->target == wm->atom.utf8_string ||
selection_request->target == wm->atom.text) {
weston_wm_send_data(wm, wm->atom.utf8_string,
"text/plain;charset=utf-8");
} else {
weston_log("can only handle UTF8_STRING targets...\n");
weston_wm_send_selection_notify(wm, XCB_ATOM_NONE);
}
}
static int
weston_wm_handle_xfixes_selection_notify(struct weston_wm *wm,
xcb_generic_event_t *event)
{
xcb_xfixes_selection_notify_event_t *xfixes_selection_notify =
(xcb_xfixes_selection_notify_event_t *) event;
struct weston_compositor *compositor;
struct weston_seat *seat = weston_wm_pick_seat(wm);
uint32_t serial;
if (xfixes_selection_notify->selection != wm->atom.clipboard)
return 0;
weston_log("xfixes selection notify event: owner %d\n",
xfixes_selection_notify->owner);
if (xfixes_selection_notify->owner == XCB_WINDOW_NONE) {
if (wm->selection_owner != wm->selection_window) {
/* A real X client selection went away, not our
* proxy selection. Clear the wayland selection. */
compositor = wm->server->compositor;
serial = wl_display_next_serial(compositor->wl_display);
weston_seat_set_selection(seat, NULL, serial);
}
wm->selection_owner = XCB_WINDOW_NONE;
return 1;
}
wm->selection_owner = xfixes_selection_notify->owner;
/* We have to use XCB_TIME_CURRENT_TIME when we claim the
* selection, so grab the actual timestamp here so we can
* answer TIMESTAMP conversion requests correctly. */
if (xfixes_selection_notify->owner == wm->selection_window) {
wm->selection_timestamp = xfixes_selection_notify->timestamp;
weston_log("our window, skipping\n");
return 1;
}
wm->incr = 0;
xcb_convert_selection(wm->conn, wm->selection_window,
wm->atom.clipboard,
wm->atom.targets,
wm->atom.wl_selection,
xfixes_selection_notify->timestamp);
xcb_flush(wm->conn);
return 1;
}
int
weston_wm_handle_selection_event(struct weston_wm *wm,
xcb_generic_event_t *event)
{
switch (event->response_type & ~0x80) {
case XCB_SELECTION_NOTIFY:
weston_wm_handle_selection_notify(wm, event);
return 1;
case XCB_PROPERTY_NOTIFY:
return weston_wm_handle_selection_property_notify(wm, event);
case XCB_SELECTION_REQUEST:
weston_wm_handle_selection_request(wm, event);
return 1;
}
switch (event->response_type - wm->xfixes->first_event) {
case XCB_XFIXES_SELECTION_NOTIFY:
return weston_wm_handle_xfixes_selection_notify(wm, event);
}
return 0;
}
static void
weston_wm_set_selection(struct wl_listener *listener, void *data)
{
struct weston_seat *seat = data;
struct weston_wm *wm =
container_of(listener, struct weston_wm, selection_listener);
struct weston_data_source *source = seat->selection_data_source;
if (source == NULL) {
if (wm->selection_owner == wm->selection_window)
xcb_set_selection_owner(wm->conn,
XCB_ATOM_NONE,
wm->atom.clipboard,
wm->selection_timestamp);
return;
}
if (source->send == data_source_send)
return;
xcb_set_selection_owner(wm->conn,
wm->selection_window,
wm->atom.clipboard,
XCB_TIME_CURRENT_TIME);
}
void
weston_wm_selection_init(struct weston_wm *wm)
{
struct weston_seat *seat;
uint32_t values[1], mask;
wm->selection_request.requestor = XCB_NONE;
values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE;
wm->selection_window = xcb_generate_id(wm->conn);
xcb_create_window(wm->conn,
XCB_COPY_FROM_PARENT,
wm->selection_window,
wm->screen->root,
0, 0,
10, 10,
0,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
wm->screen->root_visual,
XCB_CW_EVENT_MASK, values);
xcb_set_selection_owner(wm->conn,
wm->selection_window,
wm->atom.clipboard_manager,
XCB_TIME_CURRENT_TIME);
mask =
XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER |
XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY |
XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
xcb_xfixes_select_selection_input(wm->conn, wm->selection_window,
wm->atom.clipboard, mask);
seat = weston_wm_pick_seat(wm);
wm->selection_listener.notify = weston_wm_set_selection;
wl_signal_add(&seat->selection_signal, &wm->selection_listener);
weston_wm_set_selection(&wm->selection_listener, seat);
}