From b2d4724640d0accae33099ab061fa5b8fe700fea Mon Sep 17 00:00:00 2001 From: Iweisc Date: Sat, 7 Feb 2026 15:45:14 +0600 Subject: [PATCH 1/3] xwayland: add clipboard support for images, RTF, and HTML The XWayland selection bridge only forwarded text (UTF8_STRING/TEXT), silently dropping all other formats. This meant X11 apps could not paste images, rich text, or HTML copied from Windows via the RDP clipboard backend, even though native Wayland apps could. Add a format mapping table that bridges additional X11 atoms to their Wayland MIME types: image/bmp, image/png, text/rtf, and text/html. Rewrite the target advertisement, selection request handling, and data source send paths to use this table instead of hardcoded checks. Additionally, implement BMP-to-PNG conversion using Cairo so that image/png is advertised to X11 apps when the source only provides image/bmp, since most Linux applications expect PNG for clipboard images. Fixes: microsoft/wslg#236 --- xwayland/selection.c | 322 ++++++++++++++++++++++++++++++++++---- xwayland/window-manager.c | 4 + xwayland/xwayland.h | 5 + 3 files changed, 297 insertions(+), 34 deletions(-) diff --git a/xwayland/selection.c b/xwayland/selection.c index f81fe4547..98249afea 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,168 @@ #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 +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, bmp_row_size, stride, x, y; + 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 = ((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]; + } + 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 +332,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 +419,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 +529,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); } @@ -405,7 +624,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 +641,41 @@ 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); + } else { + weston_log("bmp-to-png conversion: %zu bytes png\n", + wm->source_data.size); + weston_wm_flush_source_data(wm); + weston_wm_send_selection_notify(wm, + wm->selection_request.property); + } + 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; + 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", @@ -611,6 +855,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 +870,22 @@ 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 { - 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 if (selection_request->target == wm->atom.image_png) { + wm->convert_bmp_to_png = 1; + weston_wm_send_data(wm, wm->atom.image_png, "image/bmp"); + } 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..bbf5ac3a2 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -89,6 +89,7 @@ struct weston_wm { 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 +153,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; From 22eeac63b91024362ddd53a5c8fed898a19e8c7f Mon Sep 17 00:00:00 2001 From: Iweisc Date: Sat, 7 Feb 2026 20:54:01 +0600 Subject: [PATCH 2/3] Fix unreachable BMP-to-PNG conversion path for clipboard images The BMP-to-PNG conversion branch in weston_wm_handle_selection_request was unreachable because the generic format table lookup always matched image_png first. This caused X11 apps to receive empty data when requesting image/png from a Wayland source that only offered image/bmp. Move the conversion check before the format table lookup, guarded by source_offers_mime_type to verify the source actually has image/bmp but not image/png. Also fix an integer overflow in the BMP row size calculation by using size_t. --- xwayland/selection.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/xwayland/selection.c b/xwayland/selection.c index 98249afea..b79a98ed2 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -108,6 +108,24 @@ png_write_func(void *closure, const unsigned char *data, unsigned int 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) { @@ -117,7 +135,8 @@ convert_bmp_to_png_data(struct wl_array *source_data) int32_t width, height; uint16_t bpp; uint32_t compression; - int top_down, bmp_row_size, stride, x, y; + int top_down, stride, x, y; + size_t bmp_row_size; unsigned char *pixels; cairo_surface_t *surface; struct wl_array png_data; @@ -151,7 +170,7 @@ convert_bmp_to_png_data(struct wl_array *source_data) if (!pixels) return -1; - bmp_row_size = ((width * bpp / 8) + 3) & ~3; + 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); @@ -870,6 +889,11 @@ 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.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 { const struct xwm_selection_format *fmt; @@ -878,9 +902,6 @@ weston_wm_handle_selection_request(struct weston_wm *wm, weston_wm_send_data(wm, xwm_atom_at_offset(wm, fmt->atom_offset), fmt->mime_type); - } else if (selection_request->target == wm->atom.image_png) { - wm->convert_bmp_to_png = 1; - weston_wm_send_data(wm, wm->atom.image_png, "image/bmp"); } else { weston_log("unsupported selection target: %s\n", get_atom_name(wm->conn, selection_request->target)); From c823bab0622a6f8c2db25f7f462f490ff4f88c93 Mon Sep 17 00:00:00 2001 From: Iweisc Date: Sat, 7 Feb 2026 22:34:40 +0600 Subject: [PATCH 3/3] Fix premultiplied alpha and INCR transfer in BMP-to-PNG clipboard conversion Premultiply RGB by alpha for Cairo ARGB32 format in the 32-bit BMP path. Use INCR incremental transfer for converted PNGs exceeding 64KB instead of writing the entire buffer in a single xcb_change_property call which could exceed X11 max request size. --- xwayland/selection.c | 74 +++++++++++++++++++++++++++++++++----------- xwayland/xwayland.h | 1 + 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/xwayland/selection.c b/xwayland/selection.c index b79a98ed2..3b50f5ab5 100644 --- a/xwayland/selection.c +++ b/xwayland/selection.c @@ -195,6 +195,9 @@ convert_bmp_to_png_data(struct wl_array *source_data) 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; @@ -618,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 @@ -676,20 +687,44 @@ weston_wm_read_data_source(int fd, uint32_t mask, void *data) 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); - weston_wm_flush_source_data(wm); - weston_wm_send_selection_notify(wm, - wm->selection_request.property); + 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; + } } - 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; wm->convert_bmp_to_png = 0; } return 1; @@ -784,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, @@ -812,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, diff --git a/xwayland/xwayland.h b/xwayland/xwayland.h index bbf5ac3a2..2faf0abaa 100644 --- a/xwayland/xwayland.h +++ b/xwayland/xwayland.h @@ -84,6 +84,7 @@ 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;