From: Alexandros Frantzis alexandros.frantzis@collabora.com
Under Wayland only the data device for the seat which has the keyboard focus has access to the offered clipboard contents, so we forward the WM_RENDERFORMAT and WM_DESTROYCLIPBOARD messages to the proper window thread (as internal driver window messages). --- dlls/winewayland.drv/wayland_data_device.c | 418 ++++++++++++++++++++- dlls/winewayland.drv/waylanddrv.h | 5 + dlls/winewayland.drv/window.c | 8 + 3 files changed, 417 insertions(+), 14 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_data_device.c b/dlls/winewayland.drv/wayland_data_device.c index 15570b3fe74..b0043c57825 100644 --- a/dlls/winewayland.drv/wayland_data_device.c +++ b/dlls/winewayland.drv/wayland_data_device.c @@ -35,12 +35,21 @@
WINE_DEFAULT_DEBUG_CHANNEL(clipboard);
+#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 wl_data_offer *wl_data_offer; + struct wl_array types; };
static HWND desktop_clipboard_hwnd; @@ -69,7 +78,7 @@ static HWND get_clipboard_hwnd(void)
static int poll_until(int fd, int events, ULONG end_time) { - struct pollfd pfd = { .fd = fd, .events = events }; + struct pollfd pfd = { .fd = fd, .events = events & ~POLLHUP }; ULONG now = NtGetTickCount(); int ret = 0;
@@ -127,6 +136,64 @@ static void write_all(int fd, const void *buf, size_t count) } }
+static void *read_all(int fd, size_t *size_out) +{ + static const ULONG receive_timeout = 500; + size_t buffer_size = 4096; + int total = 0; + unsigned char *buffer; + int nread; + ULONG end_time; + + if (!(buffer = malloc(buffer_size))) + { + ERR("failed to allocate read buffer\n"); + goto out; + } + + end_time = NtGetTickCount() + receive_timeout; + do + { + if (poll_until(fd, POLLIN | POLLHUP, end_time) <= 0) goto out; + + 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; @@ -151,30 +218,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; @@ -297,6 +400,238 @@ static const struct wl_data_source_listener data_source_listener = data_source_action, };
+/********************************************************************** + * wl_data_offer handling + */ + +static void data_offer_offer(void *data, struct wl_data_offer *wl_data_offer, + 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 void data_offer_source_actions(void *data, + struct wl_data_offer *wl_data_offer, + uint32_t source_actions) +{ +} + +static void data_offer_action(void *data, struct wl_data_offer *wl_data_offer, + uint32_t dnd_action) +{ +} + +static const struct wl_data_offer_listener data_offer_listener = +{ + data_offer_offer, + data_offer_source_actions, + data_offer_action +}; + +static void wayland_data_offer_create(struct wl_data_offer *wl_data_offer) +{ + 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->wl_data_offer = wl_data_offer; + wl_array_init(&data_offer->types); + wl_data_offer_add_listener(data_offer->wl_data_offer, + &data_offer_listener, data_offer); +} + +static void wayland_data_offer_destroy(struct wayland_data_offer *data_offer) +{ + char **p; + + wl_data_offer_destroy(data_offer->wl_data_offer); + 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); + } + + wl_data_offer_receive(data_offer->wl_data_offer, 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; +} + +/********************************************************************** + * wl_data_device handling + */ + +static void wayland_data_device_destroy_clipboard_data_offer(struct wayland_data_device *data_device) +{ + if (data_device->clipboard_wl_data_offer) + { + struct wayland_data_offer *data_offer = + wl_data_offer_get_user_data(data_device->clipboard_wl_data_offer); + if (data_offer) wayland_data_offer_destroy(data_offer); + data_device->clipboard_wl_data_offer = NULL; + } +} + +static void data_device_data_offer(void *data, + struct wl_data_device *wl_data_device, + struct wl_data_offer *wl_data_offer) +{ + wayland_data_offer_create(wl_data_offer); +} + +static void data_device_enter(void *data, struct wl_data_device *wl_data_device, + uint32_t serial, struct wl_surface *wl_surface, + wl_fixed_t x_w, wl_fixed_t y_w, + struct wl_data_offer *wl_data_offer) +{ +} + +static void data_device_leave(void *data, struct wl_data_device *wl_data_device) +{ +} + +static void data_device_motion(void *data, struct wl_data_device *wl_data_device, + uint32_t time, wl_fixed_t x_w, wl_fixed_t y_w) +{ +} + +static void data_device_drop(void *data, struct wl_data_device *wl_data_device) +{ +} + +static void data_device_selection(void *data, + struct wl_data_device *wl_data_device, + struct wl_data_offer *wl_data_offer) +{ + struct wayland_data_device *data_device = data; + struct wayland_data_offer *data_offer = NULL; + HWND clipboard_hwnd = get_clipboard_hwnd(); + char **p; + + if (!clipboard_hwnd) return; + + if (!wl_data_offer || + !(data_offer = wl_data_offer_get_user_data(wl_data_offer))) + { + if (NtUserGetClipboardOwner() == clipboard_hwnd) + { + TRACE("null offer, clearing clipboard owned by native app\n"); + NtUserOpenClipboard(clipboard_hwnd, 0); + NtUserEmptyClipboard(); + NtUserCloseClipboard(); + } + else + { + /* We can't tell if the null data offer is triggered by a native + * app or another win32 app shutting down while acting as the Wine + * clipboard data source, so play it safe by assuming the second + * case and becoming the new Wine clipboard data source. */ + TRACE("null offer, updating clipboard owned by win32 app\n"); + wayland_data_device_clipboard_update(); + } + goto done; + } + + /* 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_wl_data_offer = wl_data_offer; + pthread_mutex_unlock(&data_device->mutex); +} + +static const struct wl_data_device_listener data_device_listener = +{ + data_device_data_offer, + data_device_enter, + data_device_leave, + data_device_motion, + data_device_drop, + data_device_selection, +}; + void wayland_data_device_init(void) { struct wayland_data_device *data_device = &process_wayland.data_device; @@ -306,6 +641,8 @@ void wayland_data_device_init(void) data_device->wl_data_device = wl_data_device_manager_get_data_device(process_wayland.wl_data_device_manager, process_wayland.seat.wl_seat); + wl_data_device_add_listener(data_device->wl_data_device, &data_device_listener, + data_device); pthread_mutex_unlock(&data_device->mutex);
for (; format->mime_type; ++format) @@ -362,10 +699,14 @@ void wayland_data_device_clipboard_update(void) while ((clipboard_format = NtUserEnumClipboardFormats(clipboard_format))) { struct data_device_format *format = - data_device_format_for_clipboard_format(clipboard_format); + data_device_format_for_clipboard_format(clipboard_format, NULL); if (format) wl_data_source_offer(source, format->mime_type); }
+ /* Mark this data source with our special mime type, so we can detect it's + * coming from us. */ + wl_data_source_offer(source, WINEWAYLAND_TAG_MIME_TYPE); + wl_data_source_add_listener(source, &data_source_listener, data_device); pthread_mutex_lock(&data_device->mutex); if (data_device->wl_data_device) @@ -380,6 +721,44 @@ void wayland_data_device_clipboard_update(void) NtUserCloseClipboard(); }
+void wayland_data_device_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; + + pthread_mutex_lock(&data_device->mutex); + if (data_device->clipboard_wl_data_offer && + (data_offer = wl_data_offer_get_user_data(data_device->clipboard_wl_data_offer)) && + (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); + } +} + +void wayland_data_device_destroy_clipboard(void) +{ + struct wayland_data_device *data_device = &process_wayland.data_device; + + 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) @@ -389,10 +768,21 @@ LRESULT WAYLAND_ClipboardWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM l desktop_clipboard_hwnd = hwnd; return TRUE; case WM_CLIPBOARDUPDATE: + if (NtUserGetClipboardOwner() == desktop_clipboard_hwnd) break; send_message_timeout(NtUserGetForegroundWindow(), WM_WAYLAND_CLIPBOARD_UPDATE, 0, 0, SMTO_ABORTIFHUNG, 5000, NULL); break; + case WM_RENDERFORMAT: + send_message_timeout(NtUserGetForegroundWindow(), + WM_WAYLAND_RENDER_FORMAT, wparam, 0, + SMTO_ABORTIFHUNG, 5000, NULL); + break; + case WM_DESTROYCLIPBOARD: + send_message_timeout(NtUserGetForegroundWindow(), + WM_WAYLAND_DESTROY_CLIPBOARD, wparam, 0, + SMTO_ABORTIFHUNG, 5000, NULL); + break; }
return NtUserMessageCall(hwnd, msg, wparam, lparam, NULL, diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 3519c6dbd45..89403641468 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -64,6 +64,8 @@ enum wayland_window_message WM_WAYLAND_CONFIGURE, WM_WAYLAND_SET_FOREGROUND, WM_WAYLAND_CLIPBOARD_UPDATE, + WM_WAYLAND_RENDER_FORMAT, + WM_WAYLAND_DESTROY_CLIPBOARD, };
enum wayland_surface_config_state @@ -124,6 +126,7 @@ struct wayland_data_device { struct wl_data_device *wl_data_device; struct wl_data_source *wl_data_source; + struct wl_data_offer *clipboard_wl_data_offer; pthread_mutex_t mutex; };
@@ -358,6 +361,8 @@ void wayland_pointer_clear_constraint(void); void wayland_data_device_init(void); void wayland_data_device_deinit(void); void wayland_data_device_clipboard_update(void); +void wayland_data_device_render_format(UINT clipboard_format); +void wayland_data_device_destroy_clipboard(void);
/********************************************************************** * OpenGL diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 9c56eb3dda6..8fd0dcf1156 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -624,6 +624,14 @@ LRESULT WAYLAND_WindowMessage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) if (process_wayland.data_device.wl_data_device) wayland_data_device_clipboard_update(); return 0; + case WM_WAYLAND_RENDER_FORMAT: + if (process_wayland.data_device.wl_data_device) + wayland_data_device_render_format(wp); + return 0; + case WM_WAYLAND_DESTROY_CLIPBOARD: + if (process_wayland.data_device.wl_data_device) + wayland_data_device_destroy_clipboard(); + return 0; default: FIXME("got window msg %x hwnd %p wp %lx lp %lx\n", msg, hwnd, (long)wp, lp); return 0;