diff --git a/include/rootston/input.h b/include/rootston/input.h index 0af48577..726dda24 100644 --- a/include/rootston/input.h +++ b/include/rootston/input.h @@ -28,4 +28,6 @@ struct roots_seat *input_seat_from_wlr_seat(struct roots_input *input, bool input_view_has_focus(struct roots_input *input, struct roots_view *view); +struct roots_seat *input_get_seat(struct roots_input *input, char *name); + #endif diff --git a/include/wlr/xwayland.h b/include/wlr/xwayland.h index 54558a7b..cef17f38 100644 --- a/include/wlr/xwayland.h +++ b/include/wlr/xwayland.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #ifdef HAS_XCB_ICCCM @@ -16,7 +17,6 @@ struct wlr_xwayland_cursor; struct wlr_xwayland { pid_t pid; int display; - struct wlr_seat *seat; int x_fd[2], wl_fd[2], wm_fd[2]; struct wl_client *client; struct wl_display *wl_display; @@ -170,4 +170,7 @@ void wlr_xwayland_surface_set_maximized(struct wlr_xwayland *wlr_xwayland, void wlr_xwayland_surface_set_fullscreen(struct wlr_xwayland *wlr_xwayland, struct wlr_xwayland_surface *surface, bool fullscreen); +void wlr_xwayland_set_seat(struct wlr_xwayland *xwayland, + struct wlr_seat *seat); + #endif diff --git a/rootston/input.c b/rootston/input.c index ce20e840..94524b5f 100644 --- a/rootston/input.c +++ b/rootston/input.c @@ -27,7 +27,7 @@ static const char *device_type(enum wlr_input_device_type type) { return NULL; } -static struct roots_seat *input_get_seat(struct roots_input *input, char *name) { +struct roots_seat *input_get_seat(struct roots_input *input, char *name) { struct roots_seat *seat = NULL; wl_list_for_each(seat, &input->seats, link) { if (strcmp(seat->seat->name, name) == 0) { @@ -86,6 +86,7 @@ struct roots_input *input_create(struct roots_server *server, input->server = server; wl_list_init(&input->seats); + roots_seat_create(input, ROOTS_CONFIG_DEFAULT_SEAT_NAME); input->input_add.notify = input_add_notify; wl_signal_add(&server->backend->events.input_add, &input->input_add); diff --git a/rootston/main.c b/rootston/main.c index 74f9195c..38bbec53 100644 --- a/rootston/main.c +++ b/rootston/main.c @@ -13,6 +13,19 @@ struct roots_server server = { 0 }; static void ready(struct wl_listener *listener, void *data) { + struct roots_desktop *desktop = + wl_container_of(listener, desktop, xwayland_ready); + +#ifdef HAS_XWAYLAND + struct wlr_xwayland *xwayland = data; + if (xwayland) { + struct roots_seat *seat = + input_get_seat(desktop->server->input, + ROOTS_CONFIG_DEFAULT_SEAT_NAME); + wlr_xwayland_set_seat(xwayland, seat->seat); + } +#endif + if (server.config->startup_cmd != NULL) { const char *cmd = server.config->startup_cmd; pid_t pid = fork(); @@ -38,12 +51,6 @@ int main(int argc, char **argv) { server.desktop = desktop_create(&server, server.config); server.input = input_create(&server, server.config); - struct roots_seat *default_seat = - roots_seat_create(server.input, ROOTS_CONFIG_DEFAULT_SEAT_NAME); - if (server.desktop->xwayland) { - server.desktop->xwayland->seat = default_seat->seat; - } - const char *socket = wl_display_add_socket_auto(server.wl_display); if (!socket) { wlr_log_errno(L_ERROR, "Unable to open wayland socket"); diff --git a/xwayland/selection.c b/xwayland/selection.c index acc93728..3d57f394 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -2,38 +2,236 @@ #include #include #include +#include #include #include #include "wlr/util/log.h" #include "wlr/types/wlr_data_device.h" #include "xwm.h" -static int xwm_handle_selection_property_notify(struct wlr_xwm *xwm, - xcb_generic_event_t *event) { - xcb_property_notify_event_t *property_notify = - (xcb_property_notify_event_t *) event; +static const size_t incr_chunk_size = 64 * 1024; - if (property_notify->window == xwm->selection_window) { - if (property_notify->state == XCB_PROPERTY_NEW_VALUE && - property_notify->atom == xwm->atoms[WL_SELECTION] && - xwm->incr) - wlr_log(L_DEBUG, "TODO: get selection"); - return 1; - } else if (property_notify->window == xwm->selection_request.requestor) { - if (property_notify->state == XCB_PROPERTY_DELETE && - property_notify->atom == xwm->selection_request.property && - xwm->incr) - wlr_log(L_DEBUG, "TODO: send selection"); - return 1; +static void xwm_send_selection_notify(struct wlr_xwm *xwm, + 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 = xwm->selection_request.time; + selection_notify.requestor = xwm->selection_request.requestor; + selection_notify.selection = xwm->selection_request.selection; + selection_notify.target = xwm->selection_request.target; + selection_notify.property = property; + + xcb_send_event(xwm->xcb_conn, 0, // propagate + xwm->selection_request.requestor, + XCB_EVENT_MASK_NO_EVENT, (char *)&selection_notify); +} + +static int xwm_flush_source_data(struct wlr_xwm *xwm) +{ + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->selection_request.requestor, + xwm->selection_request.property, + xwm->selection_target, + 8, // format + xwm->source_data.size, + xwm->source_data.data); + xwm->selection_property_set = 1; + int length = xwm->source_data.size; + xwm->source_data.size = 0; + + return length; +} + +static int xwm_read_data_source(int fd, uint32_t mask, void *data) { + struct wlr_xwm *xwm = data; + void *p; + + int current = xwm->source_data.size; + if (xwm->source_data.size < incr_chunk_size) { + p = wl_array_add(&xwm->source_data, incr_chunk_size); + } else { + p = (char *) xwm->source_data.data + xwm->source_data.size; } - return 0; + int available = xwm->source_data.alloc - current; + + int len = read(fd, p, available); + if (len == -1) { + wlr_log(L_ERROR, "read error from data source: %m\n"); + xwm_send_selection_notify(xwm, XCB_ATOM_NONE); + wl_event_source_remove(xwm->property_source); + xwm->property_source = NULL; + close(fd); + wl_array_release(&xwm->source_data); + } + + wlr_log(L_DEBUG, "read %d (available %d, mask 0x%x) bytes: \"%.*s\"\n", + len, available, mask, len, (char *) p); + + xwm->source_data.size = current + len; + if (xwm->source_data.size >= incr_chunk_size) { + if (!xwm->incr) { + wlr_log(L_DEBUG, "got %zu bytes, starting incr\n", + xwm->source_data.size); + xwm->incr = 1; + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->selection_request.requestor, + xwm->selection_request.property, + xwm->atoms[INCR], + 32, /* format */ + 1, &incr_chunk_size); + xwm->selection_property_set = 1; + xwm->flush_property_on_delete = 1; + wl_event_source_remove(xwm->property_source); + xwm->property_source = NULL; + xwm_send_selection_notify(xwm, xwm->selection_request.property); + } else if (xwm->selection_property_set) { + wlr_log(L_DEBUG, "got %zu bytes, waiting for " + "property delete\n", xwm->source_data.size); + + xwm->flush_property_on_delete = 1; + wl_event_source_remove(xwm->property_source); + xwm->property_source = NULL; + } else { + wlr_log(L_DEBUG, "got %zu bytes, " + "property deleted, setting new property\n", + xwm->source_data.size); + xwm_flush_source_data(xwm); + } + } else if (len == 0 && !xwm->incr) { + wlr_log(L_DEBUG, "non-incr transfer complete\n"); + /* Non-incr transfer all done. */ + xwm_flush_source_data(xwm); + xwm_send_selection_notify(xwm, xwm->selection_request.property); + xcb_flush(xwm->xcb_conn); + wl_event_source_remove(xwm->property_source); + xwm->property_source = NULL; + close(fd); + wl_array_release(&xwm->source_data); + xwm->selection_request.requestor = XCB_NONE; + } else if (len == 0 && xwm->incr) { + wlr_log(L_DEBUG, "incr transfer complete\n"); + + xwm->flush_property_on_delete = 1; + if (xwm->selection_property_set) { + wlr_log(L_DEBUG, "got %zu bytes, waiting for " + "property delete\n", xwm->source_data.size); + } else { + wlr_log(L_DEBUG, "got %zu bytes, " + "property deleted, setting new property\n", + xwm->source_data.size); + xwm_flush_source_data(xwm); + } + xcb_flush(xwm->xcb_conn); + wl_event_source_remove(xwm->property_source); + xwm->property_source = NULL; + close(xwm->data_source_fd); + xwm->data_source_fd = -1; + close(fd); + } else { + wlr_log(L_DEBUG, "nothing happened, buffered the bytes\n"); + } + + return 1; +} + +static void xwm_send_data(struct wlr_xwm *xwm, xcb_atom_t target, + const char *mime_type) { + struct wlr_data_source *source; + int p[2]; + + if (pipe(p) == -1) { + wlr_log(L_ERROR, "pipe failed: %m\n"); + xwm_send_selection_notify(xwm, XCB_ATOM_NONE); + 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); + + wl_array_init(&xwm->source_data); + xwm->selection_target = target; + xwm->data_source_fd = p[0]; + struct wl_event_loop *loop = + wl_display_get_event_loop(xwm->xwayland->wl_display); + xwm->property_source = wl_event_loop_add_fd(loop, + xwm->data_source_fd, + WL_EVENT_READABLE, + xwm_read_data_source, + xwm); + + source = xwm->seat->selection_source; + source->send(source, mime_type, p[1]); + close(p[1]); +} + +static void xwm_send_timestamp(struct wlr_xwm *xwm) { + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->selection_request.requestor, + xwm->selection_request.property, + XCB_ATOM_INTEGER, + 32, // format + 1, &xwm->selection_timestamp); + + xwm_send_selection_notify(xwm, xwm->selection_request.property); +} + +static void xwm_send_targets(struct wlr_xwm *xwm) { + xcb_atom_t targets[] = { + xwm->atoms[TIMESTAMP], + xwm->atoms[TARGETS], + xwm->atoms[UTF8_STRING], + xwm->atoms[TEXT], + }; + + xcb_change_property(xwm->xcb_conn, + XCB_PROP_MODE_REPLACE, + xwm->selection_request.requestor, + xwm->selection_request.property, + XCB_ATOM_ATOM, + 32, // format + sizeof(targets) / sizeof(targets[0]), targets); + + xwm_send_selection_notify(xwm, xwm->selection_request.property); } static void xwm_handle_selection_request(struct wlr_xwm *xwm, xcb_generic_event_t *event) { - wlr_log(L_DEBUG, "TODO: SELECTION REQUEST"); - return; + xcb_selection_request_event_t *selection_request = + (xcb_selection_request_event_t *) event; + + xwm->selection_request = *selection_request; + xwm->incr = 0; + xwm->flush_property_on_delete = 0; + + if (selection_request->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_send_selection_notify(xwm, xwm->selection_request.property); + return; + } + + if (selection_request->target == xwm->atoms[TARGETS]) { + xwm_send_targets(xwm); + } else if (selection_request->target == xwm->atoms[TIMESTAMP]) { + xwm_send_timestamp(xwm); + } else if (selection_request->target == xwm->atoms[UTF8_STRING] || + selection_request->target == xwm->atoms[TEXT]) { + xwm_send_data(xwm, xwm->atoms[UTF8_STRING], "text/plain;charset=utf-8"); + } else { + wlr_log(L_DEBUG, "can only handle UTF8_STRING targets\n"); + xwm_send_selection_notify(xwm, XCB_ATOM_NONE); + } } static int writable_callback(int fd, uint32_t mask, void *data) { @@ -205,7 +403,7 @@ static void xwm_get_selection_targets(struct wlr_xwm *xwm) { } } - wlr_seat_set_selection(xwm->xwayland->seat, &source->base, + wlr_seat_set_selection(xwm->seat, &source->base, wl_display_next_serial(xwm->xwayland->wl_display)); free(reply); @@ -271,7 +469,7 @@ static int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm, int xwm_handle_selection_event(struct wlr_xwm *xwm, xcb_generic_event_t *event) { - if (!xwm->xwayland->seat) { + if (!xwm->seat) { wlr_log(L_DEBUG, "not handling selection events:" "no seat assigned to xwayland"); return 0; @@ -281,8 +479,6 @@ int xwm_handle_selection_event(struct wlr_xwm *xwm, case XCB_SELECTION_NOTIFY: xwm_handle_selection_notify(xwm, event); return 1; - case XCB_PROPERTY_NOTIFY: - return xwm_handle_selection_property_notify(xwm, event); case XCB_SELECTION_REQUEST: xwm_handle_selection_request(xwm, event); return 1; @@ -327,3 +523,44 @@ void xwm_selection_init(struct wlr_xwm *xwm) { xcb_xfixes_select_selection_input(xwm->xcb_conn, xwm->selection_window, xwm->atoms[CLIPBOARD], mask); } + +static void handle_seat_set_selection(struct wl_listener *listener, + void *data) { + struct wlr_seat *seat = data; + struct wlr_xwm *xwm = + wl_container_of(listener, xwm, seat_selection_change); + struct wlr_data_source *source = seat->selection_source; + + if (source == NULL) { + if (xwm->selection_owner == xwm->selection_window) { + xcb_set_selection_owner(xwm->xcb_conn, + XCB_ATOM_NONE, + xwm->atoms[CLIPBOARD], + xwm->selection_timestamp); + } + + return; + } + + if (source->send == data_source_send) + return; + + xcb_set_selection_owner(xwm->xcb_conn, + xwm->selection_window, + xwm->atoms[CLIPBOARD], + XCB_TIME_CURRENT_TIME); +} + +void xwm_set_seat(struct wlr_xwm *xwm, struct wlr_seat *seat) { + assert(xwm); + assert(seat); + if (xwm->seat) { + wl_list_remove(&xwm->seat_selection_change.link); + xwm->seat = NULL; + } + + wl_signal_add(&seat->events.selection, &xwm->seat_selection_change); + xwm->seat_selection_change.notify = handle_seat_set_selection; + xwm->seat = seat; + handle_seat_set_selection(&xwm->seat_selection_change, seat); +} diff --git a/xwayland/xwayland.c b/xwayland/xwayland.c index ecec785c..bd43fefb 100644 --- a/xwayland/xwayland.c +++ b/xwayland/xwayland.c @@ -341,3 +341,8 @@ void wlr_xwayland_set_cursor(struct wlr_xwayland *wlr_xwayland, wlr_xwayland->cursor->hotspot_x = hotspot_x; wlr_xwayland->cursor->hotspot_y = hotspot_y; } + +void wlr_xwayland_set_seat(struct wlr_xwayland *xwayland, + struct wlr_seat *seat) { + xwm_set_seat(xwayland->xwm, seat); +} diff --git a/xwayland/xwm.c b/xwayland/xwm.c index af429a5c..407ee362 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -48,6 +48,8 @@ const char *atom_map[ATOM_LAST] = { "TARGETS", "CLIPBOARD_MANAGER", "INCR", + "TEXT", + "TIMESTAMP", }; /* General helpers */ diff --git a/xwayland/xwm.h b/xwayland/xwm.h index a7bfd623..47e674c0 100644 --- a/xwayland/xwm.h +++ b/xwayland/xwm.h @@ -36,6 +36,8 @@ enum atom_name { TARGETS, CLIPBOARD_MANAGER, INCR, + TEXT, + TIMESTAMP, ATOM_LAST, }; @@ -50,6 +52,7 @@ enum net_wm_state_action { struct wlr_xwm { struct wlr_xwayland *xwayland; struct wl_event_source *event_source; + struct wlr_seat *seat; xcb_atom_t atoms[ATOM_LAST]; xcb_connection_t *xcb_conn; @@ -70,6 +73,10 @@ struct wlr_xwm { int property_start; xcb_get_property_reply_t *property_reply; struct wl_event_source *property_source; + int flush_property_on_delete; + struct wl_array source_data; + xcb_atom_t selection_target; + bool selection_property_set; struct wlr_xwayland_surface *focus_surface; @@ -79,6 +86,7 @@ struct wlr_xwm { const xcb_query_extension_reply_t *xfixes; struct wl_listener compositor_surface_create; + struct wl_listener seat_selection_change; }; struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland); @@ -92,4 +100,6 @@ int xwm_handle_selection_event(struct wlr_xwm *xwm, xcb_generic_event_t *event); void xwm_selection_init(struct wlr_xwm *xwm); +void xwm_set_seat(struct wlr_xwm *xwm, struct wlr_seat *seat); + #endif