diff --git a/xwayland/selection.c b/xwayland/selection.c index f81fe4547..3b50f5ab5 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -26,6 +26,7 @@ #include "config.h" #include +#include #include #include #include @@ -33,6 +34,8 @@ #include #include +#include + #include #include "xwayland.h" #include "shared/helpers.h" @@ -43,6 +46,190 @@ #define wm_log(...) do {} while (0) #endif +struct xwm_selection_format { + const char *atom_name; + int atom_offset; + const char *mime_type; +}; + +static const struct xwm_selection_format xwm_selection_formats[] = { + { "UTF8_STRING", offsetof(struct weston_wm, atom.utf8_string), "text/plain;charset=utf-8" }, + { "TEXT", offsetof(struct weston_wm, atom.text), "text/plain;charset=utf-8" }, + { "STRING", offsetof(struct weston_wm, atom.string), "STRING" }, + { "image/bmp", offsetof(struct weston_wm, atom.image_bmp), "image/bmp" }, + { "image/png", offsetof(struct weston_wm, atom.image_png), "image/png" }, + { "text/rtf", offsetof(struct weston_wm, atom.text_rtf), "text/rtf" }, + { "text/html", offsetof(struct weston_wm, atom.text_html), "text/html" }, +}; + +static xcb_atom_t +xwm_atom_at_offset(struct weston_wm *wm, int offset) +{ + return *(xcb_atom_t *)((char *)wm + offset); +} + +static const struct xwm_selection_format * +xwm_selection_format_for_atom(struct weston_wm *wm, xcb_atom_t atom) +{ + unsigned i; + + for (i = 0; i < ARRAY_LENGTH(xwm_selection_formats); i++) { + if (xwm_atom_at_offset(wm, xwm_selection_formats[i].atom_offset) == atom) + return &xwm_selection_formats[i]; + } + return NULL; +} + +static const struct xwm_selection_format * +xwm_selection_format_for_mime_type(const char *mime_type) +{ + unsigned i; + + for (i = 0; i < ARRAY_LENGTH(xwm_selection_formats); i++) { + if (strcmp(xwm_selection_formats[i].mime_type, mime_type) == 0) + return &xwm_selection_formats[i]; + } + return NULL; +} + +struct png_write_closure { + struct wl_array *data; +}; + +static cairo_status_t +png_write_func(void *closure, const unsigned char *data, unsigned int length) +{ + struct png_write_closure *wc = closure; + void *p = wl_array_add(wc->data, length); + + if (!p) + return CAIRO_STATUS_WRITE_ERROR; + memcpy(p, data, length); + return CAIRO_STATUS_SUCCESS; +} + +static int +source_offers_mime_type(struct weston_wm *wm, const char *mime_type) +{ + struct weston_seat *seat = weston_wm_pick_seat(wm); + struct weston_data_source *source; + char **p; + + if (!seat || !seat->selection_data_source) + return 0; + + source = seat->selection_data_source; + wl_array_for_each(p, &source->mime_types) { + if (strcmp(*p, mime_type) == 0) + return 1; + } + return 0; +} + +static int +convert_bmp_to_png_data(struct wl_array *source_data) +{ + unsigned char *bmp = source_data->data; + size_t bmp_size = source_data->size; + uint32_t data_offset; + int32_t width, height; + uint16_t bpp; + uint32_t compression; + int top_down, stride, x, y; + size_t bmp_row_size; + unsigned char *pixels; + cairo_surface_t *surface; + struct wl_array png_data; + struct png_write_closure closure; + cairo_status_t status; + + if (bmp_size < 54 || bmp[0] != 'B' || bmp[1] != 'M') + return -1; + + memcpy(&data_offset, bmp + 10, 4); + memcpy(&width, bmp + 18, 4); + memcpy(&height, bmp + 22, 4); + memcpy(&bpp, bmp + 28, 2); + memcpy(&compression, bmp + 30, 4); + + top_down = height < 0; + if (top_down) + height = -height; + + if (width <= 0 || height <= 0) + return -1; + + if (compression != 0 && compression != 3) + return -1; + + if (bpp != 24 && bpp != 32) + return -1; + + stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + pixels = calloc(height, stride); + if (!pixels) + return -1; + + bmp_row_size = (((size_t)width * bpp / 8) + 3) & ~3; + + for (y = 0; y < height; y++) { + int src_y = top_down ? y : (height - 1 - y); + unsigned char *src_row = bmp + data_offset + src_y * bmp_row_size; + uint32_t *dst_row = (uint32_t *)(pixels + y * stride); + + if (data_offset + (size_t)(src_y + 1) * bmp_row_size > bmp_size) { + free(pixels); + return -1; + } + + for (x = 0; x < width; x++) { + unsigned char b, g, r, a; + + if (bpp == 24) { + b = src_row[x * 3]; + g = src_row[x * 3 + 1]; + r = src_row[x * 3 + 2]; + a = 255; + } else { + b = src_row[x * 4]; + g = src_row[x * 4 + 1]; + r = src_row[x * 4 + 2]; + a = src_row[x * 4 + 3]; + r = (r * a + 127) / 255; + g = (g * a + 127) / 255; + b = (b * a + 127) / 255; + } + dst_row[x] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | + ((uint32_t)g << 8) | b; + } + } + + surface = cairo_image_surface_create_for_data( + pixels, CAIRO_FORMAT_ARGB32, width, height, stride); + + if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { + cairo_surface_destroy(surface); + free(pixels); + return -1; + } + + wl_array_init(&png_data); + closure.data = &png_data; + status = cairo_surface_write_to_png_stream(surface, png_write_func, + &closure); + cairo_surface_destroy(surface); + free(pixels); + + if (status != CAIRO_STATUS_SUCCESS) { + wl_array_release(&png_data); + return -1; + } + + wl_array_release(source_data); + *source_data = png_data; + return 0; +} + static int writable_callback(int fd, uint32_t mask, void *data) { @@ -167,21 +354,28 @@ data_source_send(struct weston_data_source *base, { struct x11_data_source *source = (struct x11_data_source *) base; struct weston_wm *wm = source->wm; + const struct xwm_selection_format *fmt; + xcb_atom_t target; - 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); + fmt = xwm_selection_format_for_mime_type(mime_type); + if (!fmt) { + weston_log("unhandled mime type for send: %s\n", mime_type); + close(fd); + return; + } - xcb_flush(wm->conn); + target = xwm_atom_at_offset(wm, fmt->atom_offset); + xcb_convert_selection(wm->conn, + wm->selection_window, + wm->atom.clipboard, + target, + wm->atom.wl_selection, + XCB_TIME_CURRENT_TIME); - fcntl(fd, F_SETFL, O_WRONLY | O_NONBLOCK); - wm->data_source_fd = fd; - } + xcb_flush(wm->conn); + + fcntl(fd, F_SETFL, O_WRONLY | O_NONBLOCK); + wm->data_source_fd = fd; } static void @@ -247,10 +441,24 @@ weston_wm_get_selection_targets(struct weston_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) { + const struct xwm_selection_format *fmt; + int already_added = 0; + + fmt = xwm_selection_format_for_atom(wm, value[i]); + if (!fmt) + continue; + + wl_array_for_each(p, &source->base.mime_types) { + if (strcmp(*p, fmt->mime_type) == 0) { + already_added = 1; + break; + } + } + + if (!already_added) { p = wl_array_add(&source->base.mime_types, sizeof *p); if (p) - *p = strdup("text/plain;charset=utf-8"); + *p = strdup(fmt->mime_type); } } @@ -343,22 +551,55 @@ weston_wm_send_selection_notify(struct weston_wm *wm, xcb_atom_t property) 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 */ - }; + struct weston_seat *seat = weston_wm_pick_seat(wm); + struct weston_data_source *source; + xcb_atom_t targets[ARRAY_LENGTH(xwm_selection_formats) + 3]; + uint32_t num_targets = 0; + char **mime_p; + unsigned i, j; + int has_bmp = 0, has_png = 0; + + targets[num_targets++] = wm->atom.timestamp; + targets[num_targets++] = wm->atom.targets; + + if (seat && seat->selection_data_source) { + source = seat->selection_data_source; + wl_array_for_each(mime_p, &source->mime_types) { + if (strcmp(*mime_p, "image/bmp") == 0) + has_bmp = 1; + if (strcmp(*mime_p, "image/png") == 0) + has_png = 1; + + for (i = 0; i < ARRAY_LENGTH(xwm_selection_formats); i++) { + xcb_atom_t atom; + int duplicate = 0; + + if (strcmp(xwm_selection_formats[i].mime_type, *mime_p) != 0) + continue; + + atom = xwm_atom_at_offset(wm, xwm_selection_formats[i].atom_offset); + for (j = 0; j < num_targets; j++) { + if (targets[j] == atom) { + duplicate = 1; + break; + } + } + if (!duplicate && num_targets < ARRAY_LENGTH(targets)) + targets[num_targets++] = atom; + } + } + + if (has_bmp && !has_png && num_targets < ARRAY_LENGTH(targets)) + targets[num_targets++] = wm->atom.image_png; + } 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); + 32, + num_targets, targets); weston_wm_send_selection_notify(wm, wm->selection_request.property); } @@ -380,21 +621,29 @@ weston_wm_send_timestamp(struct weston_wm *wm) static int weston_wm_flush_source_data(struct weston_wm *wm) { - int length; + int remaining = wm->source_data.size - wm->source_data_offset; + int chunk = remaining; + + if (wm->incr && chunk > (int)incr_chunk_size) + chunk = incr_chunk_size; 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); + 8, + chunk, + (char *)wm->source_data.data + wm->source_data_offset); wm->selection_property_set = 1; - length = wm->source_data.size; - wm->source_data.size = 0; + wm->source_data_offset += chunk; + + if (wm->source_data_offset >= (int)wm->source_data.size) { + wm->source_data.size = 0; + wm->source_data_offset = 0; + } - return length; + return chunk; } static int @@ -405,7 +654,7 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) void *p; current = wm->source_data.size; - if (wm->source_data.size < incr_chunk_size) + if (wm->source_data.size < incr_chunk_size || wm->convert_bmp_to_png) p = wl_array_add(&wm->source_data, incr_chunk_size); else p = (char *) wm->source_data.data + wm->source_data.size; @@ -422,16 +671,65 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) close(fd); wm->data_source_fd = -1; wl_array_release(&wm->source_data); + wm->convert_bmp_to_png = 0; return 1; } weston_log("read fd:%d %d (available %d, mask 0x%x)\n", fd, len, available, mask); - /*remove actual data due to privacy*/ - /*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->convert_bmp_to_png) { + if (len == 0) { + weston_log("bmp-to-png conversion: %zu bytes received\n", + wm->source_data.size); + if (convert_bmp_to_png_data(&wm->source_data) != 0) { + weston_log("BMP to PNG conversion failed\n"); + weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); + if (wm->property_source) + wl_event_source_remove(wm->property_source); + wm->property_source = NULL; + close(fd); + wm->data_source_fd = -1; + wl_array_release(&wm->source_data); + wm->selection_request.requestor = XCB_NONE; + } else { + weston_log("bmp-to-png conversion: %zu bytes png\n", + wm->source_data.size); + if (wm->property_source) + wl_event_source_remove(wm->property_source); + wm->property_source = NULL; + close(fd); + wm->data_source_fd = -1; + wm->source_data_offset = 0; + + if (wm->source_data.size >= incr_chunk_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, + 1, &incr_chunk_size); + wm->selection_property_set = 1; + wm->flush_property_on_delete = 1; + weston_wm_send_selection_notify(wm, + wm->selection_request.property); + } else { + weston_wm_flush_source_data(wm); + weston_wm_send_selection_notify(wm, + wm->selection_request.property); + wl_array_release(&wm->source_data); + wm->selection_request.requestor = XCB_NONE; + } + } + wm->convert_bmp_to_png = 0; + } + return 1; + } + if (wm->source_data.size >= incr_chunk_size) { if (!wm->incr) { weston_log("got %zu bytes, starting incr\n", @@ -521,6 +819,7 @@ weston_wm_send_data(struct weston_wm *wm, xcb_atom_t target, const char *mime_ty } wl_array_init(&wm->source_data); + wm->source_data_offset = 0; wm->selection_target = target; wm->data_source_fd = p[0]; wm->property_source = wl_event_loop_add_fd(wm->server->loop, @@ -549,7 +848,9 @@ weston_wm_send_incr_chunk(struct weston_wm *wm) wm->flush_property_on_delete = 0; length = weston_wm_flush_source_data(wm); - if (wm->data_source_fd >= 0) { + if (wm->source_data.size > 0) { + wm->flush_property_on_delete = 1; + } else if (wm->data_source_fd >= 0) { wm->property_source = wl_event_loop_add_fd(wm->server->loop, wm->data_source_fd, @@ -611,6 +912,7 @@ weston_wm_handle_selection_request(struct weston_wm *wm, wm->selection_request = *selection_request; wm->incr = 0; wm->flush_property_on_delete = 0; + wm->convert_bmp_to_png = 0; if (selection_request->selection == wm->atom.clipboard_manager) { /* The weston clipboard should already have grabbed @@ -625,13 +927,24 @@ weston_wm_handle_selection_request(struct weston_wm *wm, 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 if (selection_request->target == wm->atom.image_png && + !source_offers_mime_type(wm, "image/png") && + source_offers_mime_type(wm, "image/bmp")) { + wm->convert_bmp_to_png = 1; + weston_wm_send_data(wm, wm->atom.image_png, "image/bmp"); } else { - weston_log("can only handle UTF8_STRING targets...\n"); - weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); + const struct xwm_selection_format *fmt; + + fmt = xwm_selection_format_for_atom(wm, selection_request->target); + if (fmt) { + weston_wm_send_data(wm, + xwm_atom_at_offset(wm, fmt->atom_offset), + fmt->mime_type); + } else { + weston_log("unsupported selection target: %s\n", + get_atom_name(wm->conn, selection_request->target)); + weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); + } } } diff --git a/xwayland/window-manager.c b/xwayland/window-manager.c index a7549df50..c33cae94e 100644 --- a/xwayland/window-manager.c +++ b/xwayland/window-manager.c @@ -2815,6 +2815,10 @@ weston_wm_get_resources(struct weston_wm *wm) { "WINDOW", F(atom.window) }, { "text/plain;charset=utf-8", F(atom.text_plain_utf8) }, { "text/plain", F(atom.text_plain) }, + { "image/bmp", F(atom.image_bmp) }, + { "image/png", F(atom.image_png) }, + { "text/rtf", F(atom.text_rtf) }, + { "text/html", F(atom.text_html) }, { "XdndSelection", F(atom.xdnd_selection) }, { "XdndAware", F(atom.xdnd_aware) }, { "XdndEnter", F(atom.xdnd_enter) }, diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index 6bc4e3a95..2faf0abaa 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -84,11 +84,13 @@ struct weston_wm { xcb_get_property_reply_t *property_reply; int property_start; struct wl_array source_data; + int source_data_offset; 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; + int convert_bmp_to_png; struct wl_listener selection_listener; struct wl_listener seat_create_listener; struct wl_listener seat_destroy_listener; @@ -152,6 +154,10 @@ struct weston_wm { xcb_atom_t window; xcb_atom_t text_plain_utf8; xcb_atom_t text_plain; + xcb_atom_t image_bmp; + xcb_atom_t image_png; + xcb_atom_t text_rtf; + xcb_atom_t text_html; xcb_atom_t xdnd_selection; xcb_atom_t xdnd_aware; xcb_atom_t xdnd_enter;