From: Alexandros Frantzis alexandros.frantzis@collabora.com
--- dlls/winewayland.drv/wayland_data_device.c | 379 ++++++++++++++++++++- dlls/winewayland.drv/waylanddrv.h | 1 + 2 files changed, 367 insertions(+), 13 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_data_device.c b/dlls/winewayland.drv/wayland_data_device.c index 1659e9bd6df..4018386b97c 100644 --- a/dlls/winewayland.drv/wayland_data_device.c +++ b/dlls/winewayland.drv/wayland_data_device.c @@ -25,6 +25,7 @@ #include "config.h"
#include <errno.h> +#include <fcntl.h> #include <stdlib.h> #include <unistd.h>
@@ -33,12 +34,23 @@
WINE_DEFAULT_DEBUG_CHANNEL(clipboard);
+/* A special MIME type we mark our data offers with, so we can detect that + * they are coming from us. */ +#define WINEWAYLAND_TAG_MIME_TYPE "application/x.winewayland.tag" + struct data_device_format { const char *mime_type; UINT clipboard_format; const WCHAR *register_name; void *(*export)(void *data, size_t size, size_t *ret_size); + void *(*import)(void *data, size_t size, size_t *ret_size); +}; + +struct wayland_data_offer +{ + struct zwlr_data_control_offer_v1 *zwlr_data_control_offer_v1; + struct wl_array types; };
static HWND clipboard_hwnd; @@ -67,6 +79,59 @@ static void write_all(int fd, const void *buf, size_t count) } }
+static void *read_all(int fd, size_t *size_out) +{ + size_t buffer_size = 4096; + int total = 0; + unsigned char *buffer; + int nread; + + if (!(buffer = malloc(buffer_size))) + { + ERR("failed to allocate read buffer\n"); + goto out; + } + + do + { + nread = read(fd, buffer + total, buffer_size - total); + if (nread == -1 && errno != EINTR) + { + TRACE("failed to read from fd (errno: %d)\n", errno); + total = 0; + goto out; + } + else if (nread > 0) + { + total += nread; + if (total == buffer_size) + { + unsigned char *new_buffer; + buffer_size *= 2; + new_buffer = realloc(buffer, buffer_size); + if (!new_buffer) + { + ERR("failed to reallocate read buffer\n"); + total = 0; + goto out; + } + buffer = new_buffer; + } + } + } while (nread > 0); + + TRACE("read %d bytes\n", total); + +out: + if (total == 0 && buffer != NULL) + { + free(buffer); + buffer = NULL; + } + *size_out = total; + return buffer; +} + static void *export_unicode_text(void *data, size_t size, size_t *ret_size) { DWORD byte_count; @@ -91,30 +156,66 @@ static void *export_data(void *data, size_t size, size_t *ret_size) return data; }
+static void *import_unicode_text(void *data, size_t size, size_t *ret_size) +{ + DWORD wsize; + WCHAR *ret; + + RtlUTF8ToUnicodeN(NULL, 0, &wsize, data, size); + if (!(ret = malloc(wsize + sizeof(WCHAR)))) return NULL; + RtlUTF8ToUnicodeN(ret, wsize, &wsize, data, size); + ret[wsize / sizeof(WCHAR)] = 0; + + *ret_size = wsize + sizeof(WCHAR); + + return ret; +} + +static void *import_data(void *data, size_t size, size_t *ret_size) +{ + *ret_size = size; + return data; +} + /* Order is important. When selecting a mime-type for a clipboard format we * will choose the first entry that matches the specified clipboard format. */ static struct data_device_format supported_formats[] = { - {"text/plain;charset=utf-8", CF_UNICODETEXT, NULL, export_unicode_text}, - {"text/rtf", 0, rich_text_formatW, export_data}, - {"image/tiff", CF_TIFF, NULL, export_data}, - {"image/png", 0, pngW, export_data}, - {"image/jpeg", 0, jfifW, export_data}, - {"image/gif", 0, gifW, export_data}, - {"image/svg+xml", 0, image_svg_xmlW, export_data}, - {"application/x-riff", CF_RIFF, NULL, export_data}, - {"audio/wav", CF_WAVE, NULL, export_data}, - {"audio/x-wav", CF_WAVE, NULL, export_data}, + {"text/plain;charset=utf-8", CF_UNICODETEXT, NULL, export_unicode_text, import_unicode_text}, + {"text/rtf", 0, rich_text_formatW, export_data, import_data}, + {"image/tiff", CF_TIFF, NULL, export_data, import_data}, + {"image/png", 0, pngW, export_data, import_data}, + {"image/jpeg", 0, jfifW, export_data, import_data}, + {"image/gif", 0, gifW, export_data, import_data}, + {"image/svg+xml", 0, image_svg_xmlW, export_data, import_data}, + {"application/x-riff", CF_RIFF, NULL, export_data, import_data}, + {"audio/wav", CF_WAVE, NULL, export_data, import_data}, + {"audio/x-wav", CF_WAVE, NULL, export_data, import_data}, {NULL, 0, NULL}, };
-static struct data_device_format *data_device_format_for_clipboard_format(UINT clipboard_format) +static BOOL string_array_contains(struct wl_array *array, const char *str) +{ + char **p; + + wl_array_for_each(p, array) + if (!strcmp(*p, str)) return TRUE; + + return FALSE; +} + +static struct data_device_format *data_device_format_for_clipboard_format(UINT clipboard_format, + struct wl_array *types) { struct data_device_format *format;
for (format = supported_formats; format->mime_type; ++format) { - if (format->clipboard_format == clipboard_format) return format; + if (format->clipboard_format == clipboard_format && + (!types || string_array_contains(types, format->mime_type))) + { + return format; + } }
return NULL; @@ -207,6 +308,202 @@ static const struct zwlr_data_control_source_v1_listener data_control_source_lis data_control_source_cancelled, };
+/********************************************************************** + * zwlr_data_control_offer_v1 handling + */ + +static void data_control_offer_offer(void *data, + struct zwlr_data_control_offer_v1 *zwlr_data_control_offer_v1, + const char *type) +{ + struct wayland_data_offer *data_offer = data; + const char *type_copy; + const char **p; + + if ((type_copy = strdup(type)) && + (p = wl_array_add(&data_offer->types, sizeof *p))) + { + *p = type_copy; + } +} + +static const struct zwlr_data_control_offer_v1_listener data_control_offer_listener = +{ + data_control_offer_offer, +}; + +static void wayland_data_offer_create(struct zwlr_data_control_offer_v1 *zwlr_data_control_offer_v1) +{ + struct wayland_data_offer *data_offer; + + if (!(data_offer = calloc(1, sizeof(*data_offer)))) + { + ERR("Failed to allocate memory for data offer\n"); + return; + } + + data_offer->zwlr_data_control_offer_v1 = zwlr_data_control_offer_v1; + wl_array_init(&data_offer->types); + zwlr_data_control_offer_v1_add_listener(data_offer->zwlr_data_control_offer_v1, + &data_control_offer_listener, data_offer); +} + +static void wayland_data_offer_destroy(struct wayland_data_offer *data_offer) +{ + char **p; + + zwlr_data_control_offer_v1_destroy(data_offer->zwlr_data_control_offer_v1); + wl_array_for_each(p, &data_offer->types) + free(*p); + wl_array_release(&data_offer->types); + free(data_offer); +} + +static int wayland_data_offer_get_import_fd(struct wayland_data_offer *data_offer, + const char *mime_type) +{ + int data_pipe[2]; + +#if HAVE_PIPE2 + if (pipe2(data_pipe, O_CLOEXEC) == -1) +#endif + { + if (pipe(data_pipe) == -1) + { + ERR("failed to create clipboard data pipe\n"); + return -1; + } + fcntl(data_pipe[0], F_SETFD, FD_CLOEXEC); + fcntl(data_pipe[1], F_SETFD, FD_CLOEXEC); + } + + zwlr_data_control_offer_v1_receive(data_offer->zwlr_data_control_offer_v1, + mime_type, data_pipe[1]); + close(data_pipe[1]); + + /* Flush to ensure our receive request reaches the server. */ + wl_display_flush(process_wayland.wl_display); + + return data_pipe[0]; +} + +static void *import_format(int fd, struct data_device_format *format, size_t *ret_size) +{ + size_t size; + void *data, *ret; + + if (!(data = read_all(fd, &size))) return NULL; + ret = format->import(data, size, ret_size); + if (ret != data) free(data); + return ret; +} + +/********************************************************************** + * zwlr_data_control_device_v1 handling + */ + +static void wayland_data_device_destroy_clipboard_data_offer(struct wayland_data_device *data_device) +{ + if (data_device->clipboard_zwlr_data_control_offer_v1) + { + struct wayland_data_offer *data_offer = + zwlr_data_control_offer_v1_get_user_data(data_device->clipboard_zwlr_data_control_offer_v1); + if (data_offer) wayland_data_offer_destroy(data_offer); + data_device->clipboard_zwlr_data_control_offer_v1 = NULL; + } +} + +static void data_control_device_data_offer( + void *data, + struct zwlr_data_control_device_v1 *zwlr_data_control_device_v1, + struct zwlr_data_control_offer_v1 *zwlr_data_control_offer_v1) +{ + wayland_data_offer_create(zwlr_data_control_offer_v1); +} + +static void clipboard_update(void); + +static void data_control_device_selection( + void *data, + struct zwlr_data_control_device_v1 *zwlr_data_control_device_v1, + struct zwlr_data_control_offer_v1 *zwlr_data_control_offer_v1) +{ + struct wayland_data_device *data_device = data; + struct wayland_data_offer *data_offer = NULL; + char **p; + + if (!zwlr_data_control_offer_v1 || + !(data_offer = zwlr_data_control_offer_v1_get_user_data(zwlr_data_control_offer_v1))) + { + TRACE("empty offer, clearing clipboard\n"); + if (NtUserOpenClipboard(clipboard_hwnd, 0)) + { + NtUserEmptyClipboard(); + NtUserCloseClipboard(); + } + goto done; + } + + TRACE("updating clipboard from wayland offer\n"); + + /* If this offer contains the special winewayland tag mime-type, it was sent + * by a winewayland process to notify external wayland clients about a Wine + * clipboard update. */ + wl_array_for_each(p, &data_offer->types) + { + if (!strcmp(*p, WINEWAYLAND_TAG_MIME_TYPE)) + { + TRACE("offer sent by winewayland, ignoring\n"); + wayland_data_offer_destroy(data_offer); + data_offer = NULL; + goto done; + } + } + + if (!NtUserOpenClipboard(clipboard_hwnd, 0)) + { + TRACE("failed to open clipboard for selection\n"); + wayland_data_offer_destroy(data_offer); + data_offer = NULL; + goto done; + } + + NtUserEmptyClipboard(); + + /* For each mime type, mark that we have available clipboard data. */ + wl_array_for_each(p, &data_offer->types) + { + struct data_device_format *format = data_device_format_for_mime_type(*p); + if (format) + { + struct set_clipboard_params params = {0}; + TRACE("available clipboard format for %s => %u\n", + *p, format->clipboard_format); + NtUserSetClipboardData(format->clipboard_format, 0, ¶ms); + } + } + + NtUserCloseClipboard(); + +done: + pthread_mutex_lock(&data_device->mutex); + wayland_data_device_destroy_clipboard_data_offer(data_device); + if (data_offer) data_device->clipboard_zwlr_data_control_offer_v1 = zwlr_data_control_offer_v1; + pthread_mutex_unlock(&data_device->mutex); +} + +static void data_control_device_finished( + void *data, struct zwlr_data_control_device_v1 *zwlr_data_control_device_v1) +{ +} + +static const struct zwlr_data_control_device_v1_listener data_control_device_listener = +{ + data_control_device_data_offer, + data_control_device_selection, + data_control_device_finished, +}; + void wayland_data_device_init(void) { struct wayland_data_device *data_device = &process_wayland.data_device; @@ -221,6 +518,12 @@ void wayland_data_device_init(void) zwlr_data_control_manager_v1_get_data_device( process_wayland.zwlr_data_control_manager_v1, process_wayland.seat.wl_seat); + if (data_device->zwlr_data_control_device_v1) + { + zwlr_data_control_device_v1_add_listener( + data_device->zwlr_data_control_device_v1, &data_control_device_listener, + data_device); + } pthread_mutex_unlock(&data_device->mutex);
for (; format->mime_type; ++format) @@ -267,7 +570,7 @@ static void clipboard_update(void) for (i = 0; i < formats_size; ++i) { struct data_device_format *format = - data_device_format_for_clipboard_format(formats[i]); + data_device_format_for_clipboard_format(formats[i], NULL); if (format) { TRACE("offering mime=%s for format=%u\n", format->mime_type, formats[i]); @@ -277,6 +580,7 @@ static void clipboard_update(void)
free(formats);
+ zwlr_data_control_source_v1_offer(source, WINEWAYLAND_TAG_MIME_TYPE); zwlr_data_control_source_v1_add_listener(source, &data_control_source_listener, data_device);
pthread_mutex_lock(&data_device->mutex); @@ -292,6 +596,48 @@ static void clipboard_update(void) wl_display_flush(process_wayland.wl_display); }
+static void render_format(UINT clipboard_format) +{ + struct wayland_data_device *data_device = &process_wayland.data_device; + struct wayland_data_offer *data_offer; + struct data_device_format *format; + int import_fd = -1; + + TRACE("clipboard_format=%u\n", clipboard_format); + + pthread_mutex_lock(&data_device->mutex); + if (data_device->clipboard_zwlr_data_control_offer_v1 && + (data_offer = zwlr_data_control_offer_v1_get_user_data(data_device->clipboard_zwlr_data_control_offer_v1)) && + (format = data_device_format_for_clipboard_format(clipboard_format, + &data_offer->types))) + { + import_fd = wayland_data_offer_get_import_fd(data_offer, format->mime_type); + } + pthread_mutex_unlock(&data_device->mutex); + + if (import_fd >= 0) + { + struct set_clipboard_params params = {0}; + if ((params.data = import_format(import_fd, format, ¶ms.size))) + { + NtUserSetClipboardData(format->clipboard_format, 0, ¶ms); + free(params.data); + } + close(import_fd); + } +} + +static void destroy_clipboard(void) +{ + struct wayland_data_device *data_device = &process_wayland.data_device; + + TRACE("\n"); + + pthread_mutex_lock(&data_device->mutex); + wayland_data_device_destroy_clipboard_data_offer(data_device); + pthread_mutex_unlock(&data_device->mutex); +} + LRESULT WAYLAND_ClipboardWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) @@ -305,8 +651,15 @@ LRESULT WAYLAND_ClipboardWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM l pthread_mutex_unlock(&process_wayland.seat.mutex); return TRUE; case WM_CLIPBOARDUPDATE: + if (NtUserGetClipboardOwner() == clipboard_hwnd) break; clipboard_update(); break; + case WM_RENDERFORMAT: + render_format(wparam); + break; + case WM_DESTROYCLIPBOARD: + destroy_clipboard(); + break; }
return NtUserMessageCall(hwnd, msg, wparam, lparam, NULL, NtUserDefWindowProc, FALSE); diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index c8f0ede23ba..c389167c3b7 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -134,6 +134,7 @@ struct wayland_data_device { struct zwlr_data_control_device_v1 *zwlr_data_control_device_v1; struct zwlr_data_control_source_v1 *zwlr_data_control_source_v1; + struct zwlr_data_control_offer_v1 *clipboard_zwlr_data_control_offer_v1; pthread_mutex_t mutex; };