diff --git a/compositor/compositor.h b/compositor/compositor.h index 359c88804..4c011f01a 100644 --- a/compositor/compositor.h +++ b/compositor/compositor.h @@ -457,6 +457,8 @@ void wlsc_xserver_destroy(struct wlsc_compositor *compositor); void wlsc_xserver_surface_activate(struct wlsc_surface *surface); +void +wlsc_xserver_set_selection(struct wlsc_input_device *device); struct wlsc_zoom; typedef void (*wlsc_zoom_done_func_t)(struct wlsc_zoom *zoom, void *data); diff --git a/compositor/data-device.c b/compositor/data-device.c index 6410608c4..c772246c8 100644 --- a/compositor/data-device.c +++ b/compositor/data-device.c @@ -339,6 +339,8 @@ wlsc_input_device_set_selection(struct wlsc_input_device *device, } } + wlsc_xserver_set_selection(device); + device->selection_data_source_listener.func = destroy_selection_data_source; wl_list_insert(source->resource.destroy_listener_list.prev, diff --git a/compositor/xserver-launcher.c b/compositor/xserver-launcher.c index 3a4cc347d..7d2d134c2 100644 --- a/compositor/xserver-launcher.c +++ b/compositor/xserver-launcher.c @@ -75,6 +75,12 @@ struct wlsc_wm { struct wl_event_source *property_source; xcb_get_property_reply_t *property_reply; int property_start; + struct wl_array source_data; + xcb_selection_request_event_t selection_request; + xcb_atom_t selection_target; + xcb_timestamp_t selection_timestamp; + int selection_property_set; + int flush_property_on_delete; struct { xcb_atom_t wm_protocols; @@ -469,6 +475,39 @@ wlsc_wm_get_incr_chunk(struct wlsc_wm *wm) } } +void +wlsc_xserver_set_selection(struct wlsc_input_device *device) +{ + struct wlsc_xserver *wxs = device->compositor->wxs; + struct wlsc_wm *wm = wxs->wm; + struct wlsc_data_source *source; + const char **p, **end; + int has_text_plain = 0; + + fprintf(stderr, "set selection\n"); + + source = device->selection_data_source; + p = source->mime_types.data; + end = (const char **) + ((char *) source->mime_types.data + source->mime_types.size); + + while (p < end) { + fprintf(stderr, " %s\n", *p); + if (strcmp(*p, "text/plain") == 0 || + strcmp(*p, "text/plain;charset=utf-8") == 0) + has_text_plain = 1; + p++; + } + + if (wm && has_text_plain && + source->create_offer != data_source_create_offer) { + xcb_set_selection_owner(wm->conn, + wm->selection_window, + wm->atom.clipboard, + XCB_TIME_CURRENT_TIME); + } +} + static void wlsc_wm_handle_configure_request(struct wlsc_wm *wm, xcb_generic_event_t *event) { @@ -555,14 +594,9 @@ wlsc_wm_handle_map_request(struct wlsc_wm *wm, xcb_generic_event_t *event) { xcb_map_request_event_t *map_request = (xcb_map_request_event_t *) event; - uint32_t values[1]; fprintf(stderr, "XCB_MAP_REQUEST (window %d)\n", map_request->window); - values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; - xcb_change_window_attributes(wm->conn, map_request->window, - XCB_CW_EVENT_MASK, values); - xcb_map_window(wm->conn, map_request->window); } @@ -648,6 +682,265 @@ wlsc_wm_handle_map_notify(struct wlsc_wm *wm, xcb_generic_event_t *event) wlsc_wm_activate(wm, window, XCB_TIME_CURRENT_TIME); } +static const int incr_chunk_size = 64 * 1024; + +static void +wlsc_wm_send_selection_notify(struct wlsc_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 +wlsc_wm_send_targets(struct wlsc_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); + + wlsc_wm_send_selection_notify(wm, wm->selection_request.property); +} + +static void +wlsc_wm_send_timestamp(struct wlsc_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); + + wlsc_wm_send_selection_notify(wm, wm->selection_request.property); +} + +static int +wlsc_wm_flush_source_data(struct wlsc_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 +wlsc_wm_read_data_source(int fd, uint32_t mask, void *data) +{ + struct wlsc_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) { + fprintf(stderr, "read error from data source: %m\n"); + wlsc_wm_send_selection_notify(wm, XCB_ATOM_NONE); + wl_event_source_remove(wm->property_source); + close(fd); + wl_array_release(&wm->source_data); + } + + fprintf(stderr, "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) { + fprintf(stderr, "got %d 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); + wlsc_wm_send_selection_notify(wm, wm->selection_request.property); + } else if (wm->selection_property_set) { + fprintf(stderr, "got %d bytes, waiting for " + "property delete\n", wm->source_data.size); + + wm->flush_property_on_delete = 1; + wl_event_source_remove(wm->property_source); + } else { + fprintf(stderr, "got %d bytes, " + "property deleted, seting new property\n", + wm->source_data.size); + wlsc_wm_flush_source_data(wm); + } + } else if (len == 0 && !wm->incr) { + fprintf(stderr, "non-incr transfer complete\n"); + /* Non-incr transfer all done. */ + wlsc_wm_flush_source_data(wm); + wlsc_wm_send_selection_notify(wm, wm->selection_request.property); + xcb_flush(wm->conn); + wl_event_source_remove(wm->property_source); + close(fd); + wl_array_release(&wm->source_data); + wm->selection_request.requestor = XCB_NONE; + } else if (len == 0 && wm->incr) { + fprintf(stderr, "incr transfer complete\n"); + + wm->flush_property_on_delete = 1; + if (wm->selection_property_set) { + fprintf(stderr, "got %d bytes, waiting for " + "property delete\n", wm->source_data.size); + } else { + fprintf(stderr, "got %d bytes, " + "property deleted, seting new property\n", + wm->source_data.size); + wlsc_wm_flush_source_data(wm); + } + xcb_flush(wm->conn); + wl_event_source_remove(wm->property_source); + wm->data_source_fd = -1; + close(fd); + } else { + fprintf(stderr, "nothing happened, buffered the bytes\n"); + } + + return 1; +} + +static void +wlsc_wm_send_data(struct wlsc_wm *wm, xcb_atom_t target, const char *mime_type) +{ + struct wlsc_input_device *device = (struct wlsc_input_device *) + wm->server->compositor->input_device; + int p[2]; + + if (pipe2(p, O_CLOEXEC | O_NONBLOCK) == -1) { + fprintf(stderr, "pipe2 failed: %m\n"); + wlsc_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, + wlsc_wm_read_data_source, + wm); + + wl_resource_post_event(&device->selection_data_source->resource, + WL_DATA_SOURCE_SEND, mime_type, p[1]); + close(p[1]); +} + +static void +wlsc_wm_send_incr_chunk(struct wlsc_wm *wm) +{ + fprintf(stderr, "property deleted\n"); + int length; + + wm->selection_property_set = 0; + if (wm->flush_property_on_delete) { + fprintf(stderr, "setting new property, %d bytes\n", + wm->source_data.size); + wm->flush_property_on_delete = 0; + length = wlsc_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, + wlsc_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 void +wlsc_wm_handle_selection_request(struct wlsc_wm *wm, + xcb_generic_event_t *event) +{ + xcb_selection_request_event_t *selection_request = + (xcb_selection_request_event_t *) event; + + fprintf(stderr, "selection request, %s, ", + get_atom_name(wm->conn, selection_request->selection)); + fprintf(stderr, "target %s, ", + get_atom_name(wm->conn, selection_request->target)); + fprintf(stderr, "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->target == wm->atom.targets) { + wlsc_wm_send_targets(wm); + } else if (selection_request->target == wm->atom.timestamp) { + wlsc_wm_send_timestamp(wm); + } else if (selection_request->target == wm->atom.utf8_string || + selection_request->target == wm->atom.text) { + wlsc_wm_send_data(wm, wm->atom.utf8_string, + "text/plain;charset=utf-8"); + } else { + fprintf(stderr, "can only handle UTF8_STRING targets...\n"); + wlsc_wm_send_selection_notify(wm, XCB_ATOM_NONE); + } +} + static void wlsc_wm_handle_property_notify(struct wlsc_wm *wm, xcb_generic_event_t *event) { @@ -659,6 +952,11 @@ wlsc_wm_handle_property_notify(struct wlsc_wm *wm, xcb_generic_event_t *event) property_notify->atom == wm->atom.wl_selection && wm->incr) wlsc_wm_get_incr_chunk(wm); + } 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) + wlsc_wm_send_incr_chunk(wm); } else if (property_notify->atom == XCB_ATOM_WM_CLASS) { fprintf(stderr, "wm_class changed\n"); } else if (property_notify->atom == XCB_ATOM_WM_TRANSIENT_FOR) { @@ -688,6 +986,7 @@ wlsc_wm_handle_create_notify(struct wlsc_wm *wm, xcb_generic_event_t *event) xcb_create_notify_event_t *create_notify = (xcb_create_notify_event_t *) event; struct wlsc_wm_window *window; + uint32_t values[1]; fprintf(stderr, "XCB_CREATE_NOTIFY (window %d)\n", create_notify->window); @@ -698,6 +997,10 @@ wlsc_wm_handle_create_notify(struct wlsc_wm *wm, xcb_generic_event_t *event) return; } + values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_change_window_attributes(wm->conn, create_notify->window, + XCB_CW_EVENT_MASK, values); + memset(window, 0, sizeof *window); window->id = create_notify->window; hash_table_insert(wm->window_hash, window->id, window); @@ -753,11 +1056,20 @@ wlsc_wm_handle_xfixes_selection_notify(struct wlsc_wm *wm, printf("xfixes selection notify event: owner %d\n", 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; + fprintf(stderr, "our window, skipping\n"); + return; + } + xcb_convert_selection(wm->conn, wm->selection_window, wm->atom.clipboard, wm->atom.targets, wm->atom.wl_selection, - XCB_TIME_CURRENT_TIME); + xfixes_selection_notify->timestamp); xcb_flush(wm->conn); } @@ -801,6 +1113,9 @@ wlsc_wm_handle_event(int fd, uint32_t mask, void *data) case XCB_SELECTION_NOTIFY: wlsc_wm_handle_selection_notify(wm, event); break; + case XCB_SELECTION_REQUEST: + wlsc_wm_handle_selection_request(wm, event); + break; } switch (event->response_type - wm->xfixes->first_event) { @@ -948,6 +1263,8 @@ wlsc_wm_create(struct wlsc_xserver *wxs) xcb_change_window_attributes(wm->conn, wm->screen->root, XCB_CW_EVENT_MASK, values); + wm->selection_request.requestor = XCB_NONE; + wm->selection_window = xcb_generate_id(wm->conn); xcb_create_window(wm->conn, XCB_COPY_FROM_PARENT,