This MR adds clipboard support to the winewayland driver.
Under Wayland only applications that have the keyboard focus can interact with the clipboard (a.k.a. `wl_data_device`). Such constraints are not a natural fit for Wine's current clipboard infrastructure, which uses a separate thread and window in the desktop process to act as the win32-side clipboard proxy for all native windows.
This MR tries to work within the current Wine clipboard Wine by forwarding relevant clipboard messages to the foreground window which is likely to have the keyboard focus and that can actually handle them. This works well in practice (although there are some edge cases this fails), but I am open to different ideas about implementing the clipboard integration.
Some notes about the MR: 1. Some formats that require special treatment (e.g., CF_HTML, CF_HDROP) are not implemented in this MR to keep the size reasonable (and also to not distract from the goal of this MR which is to propose/discuss the basic design of clipboard integration). 2. Dynamic registration of newly-seen/unknown formats is not supported at the moment. It's not clear to what degree that's useful, since many (most?) Windows clipboard format strings are free-form and don't use the MIME type standard. But perhaps enough Windows apps know about MIME types nowadays to make this addition worth it? 3. Since access to the Wayland clipboard data is performed from the focused window threads (rather than the dedicated clipboard thread), any blocking while waiting for data to be sent/received has more potential to affect the applications. I have implemented reasonable timeouts which are hopefully enough to make this a non-issue. A (more complex) alternative would be a different design that attempts to offload any potentially blocking work to the dedicated clipboard thread (or is there some kind of async read/write mechanism we can use?). I am not convinced the extra complexity is worth it, though.
From: Alexandros Frantzis alexandros.frantzis@collabora.com
--- dlls/winewayland.drv/Makefile.in | 1 + dlls/winewayland.drv/wayland.c | 9 ++++ dlls/winewayland.drv/wayland_data_device.c | 48 ++++++++++++++++++++++ dlls/winewayland.drv/waylanddrv.h | 14 +++++++ 4 files changed, 72 insertions(+) create mode 100644 dlls/winewayland.drv/wayland_data_device.c
diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index 9ad1ad6889d..2db42eaba09 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -13,6 +13,7 @@ SOURCES = \ viewporter.xml \ vulkan.c \ wayland.c \ + wayland_data_device.c \ wayland_keyboard.c \ wayland_output.c \ wayland_pointer.c \ diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index 9432dc934c9..046f6d0f138 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -143,6 +143,7 @@ static void registry_handle_global(void *data, struct wl_registry *registry, seat->global_id = id; wl_seat_add_listener(seat->wl_seat, &seat_listener, NULL); pthread_mutex_unlock(&seat->mutex); + if (process_wayland.wl_data_device_manager) wayland_data_device_init(); } else if (strcmp(interface, "wp_viewporter") == 0) { @@ -164,6 +165,13 @@ static void registry_handle_global(void *data, struct wl_registry *registry, process_wayland.zwp_relative_pointer_manager_v1 = wl_registry_bind(registry, id, &zwp_relative_pointer_manager_v1_interface, 1); } + else if (strcmp(interface, "wl_data_device_manager") == 0) + { + process_wayland.wl_data_device_manager = + wl_registry_bind(registry, id, &wl_data_device_manager_interface, + version < 3 ? version : 3); + if (process_wayland.seat.wl_seat) wayland_data_device_init(); + } }
static void registry_handle_global_remove(void *data, struct wl_registry *registry, @@ -189,6 +197,7 @@ static void registry_handle_global_remove(void *data, struct wl_registry *regist { TRACE("removing seat\n"); if (process_wayland.pointer.wl_pointer) wayland_pointer_deinit(); + if (process_wayland.data_device.wl_data_device) wayland_data_device_deinit(); pthread_mutex_lock(&seat->mutex); wl_seat_release(seat->wl_seat); seat->wl_seat = NULL; diff --git a/dlls/winewayland.drv/wayland_data_device.c b/dlls/winewayland.drv/wayland_data_device.c new file mode 100644 index 00000000000..94d7ac7f36b --- /dev/null +++ b/dlls/winewayland.drv/wayland_data_device.c @@ -0,0 +1,48 @@ +/* + * Wayland data device + * + * Copyright 2025 Alexandros Frantzis for Collabora Ltd + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include "waylanddrv.h" + +void wayland_data_device_init(void) +{ + struct wayland_data_device *data_device = &process_wayland.data_device; + + data_device->wl_data_device = + wl_data_device_manager_get_data_device(process_wayland.wl_data_device_manager, + process_wayland.seat.wl_seat); +} + +void wayland_data_device_deinit(void) +{ + struct wayland_data_device *data_device = &process_wayland.data_device; + + if (wl_data_device_get_version(data_device->wl_data_device) < 2) + wl_data_device_destroy(data_device->wl_data_device); + else + wl_data_device_release(data_device->wl_data_device); + + data_device->wl_data_device = NULL; +} diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 72a37cb3ffb..51a5ddce2f5 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -118,6 +118,11 @@ struct wayland_seat pthread_mutex_t mutex; };
+struct wayland_data_device +{ + struct wl_data_device *wl_data_device; +}; + struct wayland { BOOL initialized; @@ -132,9 +137,11 @@ struct wayland struct wl_subcompositor *wl_subcompositor; struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1; struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1; + struct wl_data_device_manager *wl_data_device_manager; struct wayland_seat seat; struct wayland_keyboard keyboard; struct wayland_pointer pointer; + struct wayland_data_device data_device; struct wl_list output_list; /* Protects the output_list and the wayland_output.current states. */ pthread_mutex_t output_mutex; @@ -340,6 +347,13 @@ void wayland_pointer_init(struct wl_pointer *wl_pointer); void wayland_pointer_deinit(void); void wayland_pointer_clear_constraint(void);
+/********************************************************************** + * Wayland data device + */ + +void wayland_data_device_init(void); +void wayland_data_device_deinit(void); + /********************************************************************** * OpenGL */
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Under Wayland only the data device for the seat which has the keyboard focus can set the clipboard selection, so we forward the WM_CLIPBOARDUPDATE message to the proper window thread (as an internal driver window message). --- dlls/winewayland.drv/wayland.c | 1 + dlls/winewayland.drv/wayland_data_device.c | 275 +++++++++++++++++++++ dlls/winewayland.drv/wayland_keyboard.c | 4 + dlls/winewayland.drv/wayland_surface.c | 3 + dlls/winewayland.drv/waylanddrv.h | 17 ++ dlls/winewayland.drv/waylanddrv_main.c | 1 + dlls/winewayland.drv/window.c | 4 + 7 files changed, 305 insertions(+)
diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index 046f6d0f138..49f4dc9b3a8 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -37,6 +37,7 @@ struct wayland process_wayland = .seat.mutex = PTHREAD_MUTEX_INITIALIZER, .keyboard.mutex = PTHREAD_MUTEX_INITIALIZER, .pointer.mutex = PTHREAD_MUTEX_INITIALIZER, + .data_device.mutex = PTHREAD_MUTEX_INITIALIZER, .output_list = {&process_wayland.output_list, &process_wayland.output_list}, .output_mutex = PTHREAD_MUTEX_INITIALIZER }; diff --git a/dlls/winewayland.drv/wayland_data_device.c b/dlls/winewayland.drv/wayland_data_device.c index 94d7ac7f36b..f41f35c11fc 100644 --- a/dlls/winewayland.drv/wayland_data_device.c +++ b/dlls/winewayland.drv/wayland_data_device.c @@ -24,25 +24,300 @@
#include "config.h"
+#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdlib.h> +#include <unistd.h> + #include "waylanddrv.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(clipboard); + +static HWND desktop_clipboard_hwnd; + +static HWND get_clipboard_hwnd(void) +{ + if (!desktop_clipboard_hwnd) + { + static const WCHAR clipboard_classnameW[] = { + '_','_','w','i','n','e','_','c','l','i','p','b','o','a','r','d','_', + 'm','a','n','a','g','e','r','\0'}; + UNICODE_STRING clipboard_classnameU; + RtlInitUnicodeString(&clipboard_classnameU, clipboard_classnameW); + + desktop_clipboard_hwnd = + NtUserFindWindowEx(HWND_MESSAGE, NULL, &clipboard_classnameU, NULL, 0); + } + + return desktop_clipboard_hwnd; +} + +static int poll_until(int fd, int events, ULONG end_time) +{ + struct pollfd pfd = { .fd = fd, .events = events }; + ULONG now = NtGetTickCount(); + int ret = 0; + + while ((now = NtGetTickCount()) < end_time && + (ret = poll(&pfd, 1, end_time - now)) == -1 && + errno == EINTR) + { + continue; + } + + if (ret <= 0 || !(pfd.revents & events)) + { + TRACE("Failed polling ret=%d errno=%d revents=0x%x\n", + ret, ret == -1 ? errno : 0, pfd.revents); + return ret <= 0 ? ret : -1; + } + + return ret; +} + +static void write_all(int fd, const void *buf, size_t count) +{ + static const ULONG timeout = 500; + size_t nwritten = 0; + int flags; + ULONG end_time; + ssize_t ret; + + if (((flags = fcntl(fd, F_GETFL, 0)) < 0) || + (!(flags & O_NONBLOCK) && fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)) + { + TRACE("Failed to make data source send fd non-blocking, " + "will not be able to properly enforce write timeout\n"); + } + + end_time = NtGetTickCount() + timeout; + while (nwritten < count) + { + if (poll_until(fd, POLLOUT, end_time) <= 0) break; + ret = write(fd, (const char*)buf + nwritten, count - nwritten); + if (ret == -1 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) + { + break; + } + else if (ret > 0) + { + nwritten += ret; + } + } + + if (nwritten < count) + { + WARN("Failed to write all clipboard data, had %zu bytes, wrote %zu bytes\n", + count, nwritten); + } +} + +static void *export_unicode_text(void *data, size_t size, size_t *ret_size) +{ + DWORD byte_count; + char *bytes; + + /* Wayland apps expect strings to not be zero-terminated, so avoid + * zero-terminating the resulting converted string. */ + if (size >= sizeof(WCHAR) && ((WCHAR *)data)[size / sizeof(WCHAR) - 1] == 0) + size -= sizeof(WCHAR); + + RtlUnicodeToUTF8N(NULL, 0, &byte_count, data, size); + if (!(bytes = malloc(byte_count))) return NULL; + RtlUnicodeToUTF8N(bytes, byte_count, &byte_count, data, size); + + *ret_size = byte_count; + return bytes; +} + +/********************************************************************** + * wl_data_source handling + */ + +static void wayland_data_source_export(int32_t fd) +{ + struct get_clipboard_params params = { .data_only = TRUE }; + static const size_t buffer_size = 1024; + HWND clipboard_hwnd = get_clipboard_hwnd(); + void *buffer, *exported = NULL; + size_t exported_size; + + if (!clipboard_hwnd) return; + if (!(buffer = malloc(buffer_size))) return; + if (!NtUserOpenClipboard(clipboard_hwnd, 0)) + { + TRACE("failed to open clipboard for export\n"); + free(buffer); + return; + } + + params.data = buffer; + params.size = buffer_size; + if (NtUserGetClipboardData(CF_UNICODETEXT, ¶ms)) + { + exported = export_unicode_text(params.data, params.size, &exported_size); + } + else if (params.data_size) + { + /* If 'buffer_size' is too small, NtUserGetClipboardData writes the + * minimum size in 'params.data_size', so we retry with that. */ + void *new_buffer = realloc(buffer, params.data_size); + if (new_buffer) + { + buffer = new_buffer; + params.data = new_buffer; + if (NtUserGetClipboardData(CF_UNICODETEXT, ¶ms)) + exported = export_unicode_text(params.data, params.size, &exported_size); + } + } + + NtUserCloseClipboard(); + if (exported) write_all(fd, exported, exported_size); + + free(exported); + free(buffer); +} + +static void data_source_target(void *data, struct wl_data_source *source, + const char *mime_type) +{ +} + +static void data_source_send(void *data, struct wl_data_source *source, + const char *mime_type, int32_t fd) +{ + if (!strcmp(mime_type, "text/plain;charset=utf-8")) + wayland_data_source_export(fd); + close(fd); +} + +static void data_source_cancelled(void *data, struct wl_data_source *source) +{ + struct wayland_data_device *data_device = data; + + pthread_mutex_lock(&data_device->mutex); + wl_data_source_destroy(source); + if (source == data_device->wl_data_source) + data_device->wl_data_source = NULL; + pthread_mutex_unlock(&data_device->mutex); +} + +static void data_source_dnd_drop_performed(void *data, + struct wl_data_source *source) +{ +} + +static void data_source_dnd_finished(void *data, struct wl_data_source *source) +{ +} + +static void data_source_action(void *data, struct wl_data_source *source, + uint32_t dnd_action) +{ +} + +static const struct wl_data_source_listener data_source_listener = +{ + data_source_target, + data_source_send, + data_source_cancelled, + data_source_dnd_drop_performed, + data_source_dnd_finished, + data_source_action, +};
void wayland_data_device_init(void) { struct wayland_data_device *data_device = &process_wayland.data_device;
+ pthread_mutex_lock(&data_device->mutex); data_device->wl_data_device = wl_data_device_manager_get_data_device(process_wayland.wl_data_device_manager, process_wayland.seat.wl_seat); + pthread_mutex_unlock(&data_device->mutex); }
void wayland_data_device_deinit(void) { struct wayland_data_device *data_device = &process_wayland.data_device;
+ pthread_mutex_lock(&data_device->mutex); if (wl_data_device_get_version(data_device->wl_data_device) < 2) wl_data_device_destroy(data_device->wl_data_device); else wl_data_device_release(data_device->wl_data_device);
data_device->wl_data_device = NULL; + pthread_mutex_unlock(&data_device->mutex); +} + +void wayland_data_device_clipboard_update(void) +{ + struct wayland_data_device *data_device = &process_wayland.data_device; + uint32_t enter_serial = process_wayland.keyboard.enter_serial; + HWND clipboard_hwnd = get_clipboard_hwnd(); + struct wl_data_source *source; + UINT clipboard_format = 0; + + TRACE("\n"); + + if (!clipboard_hwnd) return; + + pthread_mutex_lock(&process_wayland.keyboard.mutex); + enter_serial = process_wayland.keyboard.enter_serial; + pthread_mutex_unlock(&process_wayland.keyboard.mutex); + if (!enter_serial) return; + + if (!NtUserOpenClipboard(clipboard_hwnd, 0)) + { + TRACE("failed to open clipboard for update\n"); + return; + } + + source = wl_data_device_manager_create_data_source(process_wayland.wl_data_device_manager); + if (!source) + { + ERR("failed to create data source\n"); + return; + } + + while ((clipboard_format = NtUserEnumClipboardFormats(clipboard_format))) + { + if (clipboard_format == CF_UNICODETEXT) + wl_data_source_offer(source, "text/plain;charset=utf-8"); + } + + wl_data_source_add_listener(source, &data_source_listener, data_device); + pthread_mutex_lock(&data_device->mutex); + if (data_device->wl_data_device) + wl_data_device_set_selection(data_device->wl_data_device, source, enter_serial); + /* Destroy any previous source only after setting the new source, to + * avoid spurious 'selection(nil)' events. */ + if (data_device->wl_data_source) + wl_data_source_destroy(data_device->wl_data_source); + data_device->wl_data_source = source; + pthread_mutex_unlock(&data_device->mutex); + + NtUserCloseClipboard(); +} + +LRESULT WAYLAND_ClipboardWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + switch (msg) + { + case WM_NCCREATE: + NtUserAddClipboardFormatListener(hwnd); + desktop_clipboard_hwnd = hwnd; + return TRUE; + case WM_CLIPBOARDUPDATE: + send_message_timeout(NtUserGetForegroundWindow(), + WM_WAYLAND_CLIPBOARD_UPDATE, 0, 0, + SMTO_ABORTIFHUNG, 5000, NULL); + break; + } + + return NtUserMessageCall(hwnd, msg, wparam, lparam, NULL, + NtUserDefWindowProc, FALSE); } diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c index 8f7b6dce30f..b445ebc0bd5 100644 --- a/dlls/winewayland.drv/wayland_keyboard.c +++ b/dlls/winewayland.drv/wayland_keyboard.c @@ -754,6 +754,7 @@ static void keyboard_handle_enter(void *private, struct wl_keyboard *wl_keyboard
pthread_mutex_lock(&keyboard->mutex); keyboard->focused_hwnd = hwnd; + keyboard->enter_serial = serial; pthread_mutex_unlock(&keyboard->mutex);
NtUserPostMessage(keyboard->focused_hwnd, WM_INPUTLANGCHANGEREQUEST, 0 /*FIXME*/, @@ -789,7 +790,10 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard,
pthread_mutex_lock(&keyboard->mutex); if (keyboard->focused_hwnd == hwnd) + { keyboard->focused_hwnd = NULL; + keyboard->enter_serial = 0; + } pthread_mutex_unlock(&keyboard->mutex);
/* The spec for the leave event tells us to treat all keys as released, diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 2178f5431cb..448b0ae68ba 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -201,7 +201,10 @@ void wayland_surface_destroy(struct wayland_surface *surface)
pthread_mutex_lock(&process_wayland.keyboard.mutex); if (process_wayland.keyboard.focused_hwnd == surface->hwnd) + { process_wayland.keyboard.focused_hwnd = NULL; + process_wayland.keyboard.enter_serial = 0; + } pthread_mutex_unlock(&process_wayland.keyboard.mutex);
wayland_surface_clear_role(surface); diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 51a5ddce2f5..3519c6dbd45 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -63,6 +63,7 @@ enum wayland_window_message WM_WAYLAND_INIT_DISPLAY_DEVICES = WM_WINE_FIRST_DRIVER_MSG, WM_WAYLAND_CONFIGURE, WM_WAYLAND_SET_FOREGROUND, + WM_WAYLAND_CLIPBOARD_UPDATE, };
enum wayland_surface_config_state @@ -86,6 +87,7 @@ struct wayland_keyboard struct xkb_context *xkb_context; struct xkb_state *xkb_state; HWND focused_hwnd; + uint32_t enter_serial; pthread_mutex_t mutex; };
@@ -121,6 +123,8 @@ struct wayland_seat struct wayland_data_device { struct wl_data_device *wl_data_device; + struct wl_data_source *wl_data_source; + pthread_mutex_t mutex; };
struct wayland @@ -353,6 +357,7 @@ 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);
/********************************************************************** * OpenGL @@ -379,6 +384,17 @@ static inline LRESULT send_message(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lp return NtUserMessageCall(hwnd, msg, wparam, lparam, NULL, NtUserSendMessage, FALSE); }
+static inline LRESULT send_message_timeout(HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam, UINT flags, UINT timeout, + PDWORD_PTR res_ptr ) +{ + struct send_message_timeout_params params = { .flags = flags, .timeout = timeout }; + LRESULT res = NtUserMessageCall(hwnd, msg, wparam, lparam, ¶ms, + NtUserSendMessageTimeout, FALSE); + if (res_ptr) *res_ptr = params.result; + return res; +} + RGNDATA *get_region_data(HRGN region);
/********************************************************************** @@ -386,6 +402,7 @@ RGNDATA *get_region_data(HRGN region); */
BOOL WAYLAND_ClipCursor(const RECT *clip, BOOL reset); +LRESULT WAYLAND_ClipboardWindowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); LRESULT WAYLAND_DesktopWindowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); void WAYLAND_DestroyWindow(HWND hwnd); void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor); diff --git a/dlls/winewayland.drv/waylanddrv_main.c b/dlls/winewayland.drv/waylanddrv_main.c index 47c1299dd01..d9f13194e9c 100644 --- a/dlls/winewayland.drv/waylanddrv_main.c +++ b/dlls/winewayland.drv/waylanddrv_main.c @@ -35,6 +35,7 @@ char *process_name = NULL;
static const struct user_driver_funcs waylanddrv_funcs = { + .pClipboardWindowProc = WAYLAND_ClipboardWindowProc, .pClipCursor = WAYLAND_ClipCursor, .pDesktopWindowProc = WAYLAND_DesktopWindowProc, .pDestroyWindow = WAYLAND_DestroyWindow, diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 67718936b53..9c56eb3dda6 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -620,6 +620,10 @@ LRESULT WAYLAND_WindowMessage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) case WM_WAYLAND_SET_FOREGROUND: NtUserSetForegroundWindow(hwnd); return 0; + case WM_WAYLAND_CLIPBOARD_UPDATE: + if (process_wayland.data_device.wl_data_device) + wayland_data_device_clipboard_update(); + return 0; default: FIXME("got window msg %x hwnd %p wp %lx lp %lx\n", msg, hwnd, (long)wp, lp); return 0;
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Introduce the infrastructure to export various clipboard formats in a table-driven manner, similar to what's used by winex11. --- dlls/winewayland.drv/wayland_data_device.c | 62 ++++++++++++++++++---- 1 file changed, 52 insertions(+), 10 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_data_device.c b/dlls/winewayland.drv/wayland_data_device.c index f41f35c11fc..815c9edbed0 100644 --- a/dlls/winewayland.drv/wayland_data_device.c +++ b/dlls/winewayland.drv/wayland_data_device.c @@ -35,6 +35,13 @@
WINE_DEFAULT_DEBUG_CHANNEL(clipboard);
+struct data_device_format +{ + const char *mime_type; + UINT clipboard_format; + void *(*export)(void *data, size_t size, size_t *ret_size); +}; + static HWND desktop_clipboard_hwnd;
static HWND get_clipboard_hwnd(void) @@ -132,11 +139,43 @@ static void *export_unicode_text(void *data, size_t size, size_t *ret_size) return bytes; }
+/* 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, export_unicode_text}, + {NULL, 0, NULL}, +}; + +static struct data_device_format *data_device_format_for_clipboard_format(UINT clipboard_format) +{ + struct data_device_format *format; + + for (format = supported_formats; format->mime_type; ++format) + { + if (format->clipboard_format == clipboard_format) return format; + } + + return NULL; +} + +static struct data_device_format *data_device_format_for_mime_type(const char *mime) +{ + struct data_device_format *format; + + for (format = supported_formats; format->mime_type; ++format) + { + if (!strcmp(mime, format->mime_type)) return format; + } + + return NULL; +} + /********************************************************************** * wl_data_source handling */
-static void wayland_data_source_export(int32_t fd) +static void wayland_data_source_export(struct data_device_format *format, int fd) { struct get_clipboard_params params = { .data_only = TRUE }; static const size_t buffer_size = 1024; @@ -155,9 +194,9 @@ static void wayland_data_source_export(int32_t fd)
params.data = buffer; params.size = buffer_size; - if (NtUserGetClipboardData(CF_UNICODETEXT, ¶ms)) + if (NtUserGetClipboardData(format->clipboard_format, ¶ms)) { - exported = export_unicode_text(params.data, params.size, &exported_size); + exported = format->export(params.data, params.size, &exported_size); } else if (params.data_size) { @@ -168,15 +207,15 @@ static void wayland_data_source_export(int32_t fd) { buffer = new_buffer; params.data = new_buffer; - if (NtUserGetClipboardData(CF_UNICODETEXT, ¶ms)) - exported = export_unicode_text(params.data, params.size, &exported_size); + if (NtUserGetClipboardData(format->clipboard_format, ¶ms)) + exported = format->export(params.data, params.size, &exported_size); } }
NtUserCloseClipboard(); if (exported) write_all(fd, exported, exported_size);
- free(exported); + if (exported != buffer) free(exported); free(buffer); }
@@ -188,8 +227,10 @@ static void data_source_target(void *data, struct wl_data_source *source, static void data_source_send(void *data, struct wl_data_source *source, const char *mime_type, int32_t fd) { - if (!strcmp(mime_type, "text/plain;charset=utf-8")) - wayland_data_source_export(fd); + struct data_device_format *format = + data_device_format_for_mime_type(mime_type); + + if (format) wayland_data_source_export(format, fd); close(fd); }
@@ -285,8 +326,9 @@ void wayland_data_device_clipboard_update(void)
while ((clipboard_format = NtUserEnumClipboardFormats(clipboard_format))) { - if (clipboard_format == CF_UNICODETEXT) - wl_data_source_offer(source, "text/plain;charset=utf-8"); + struct data_device_format *format = + data_device_format_for_clipboard_format(clipboard_format); + if (format) wl_data_source_offer(source, format->mime_type); }
wl_data_source_add_listener(source, &data_source_listener, data_device);
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Add support for some formats commonly used in applications and which do not require special exporting: RTF, TIFF, PNG, JPEG, GIF, SVG, RIFF, WAV. --- dlls/winewayland.drv/wayland_data_device.c | 37 +++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-)
diff --git a/dlls/winewayland.drv/wayland_data_device.c b/dlls/winewayland.drv/wayland_data_device.c index 815c9edbed0..15570b3fe74 100644 --- a/dlls/winewayland.drv/wayland_data_device.c +++ b/dlls/winewayland.drv/wayland_data_device.c @@ -39,10 +39,16 @@ 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); };
static HWND desktop_clipboard_hwnd; +static const WCHAR rich_text_formatW[] = {'R','i','c','h',' ','T','e','x','t',' ','F','o','r','m','a','t',0}; +static const WCHAR pngW[] = {'P','N','G',0}; +static const WCHAR jfifW[] = {'J','F','I','F',0}; +static const WCHAR gifW[] = {'G','I','F',0}; +static const WCHAR image_svg_xmlW[] = {'i','m','a','g','e','/','s','v','g','+','x','m','l',0};
static HWND get_clipboard_hwnd(void) { @@ -139,11 +145,26 @@ static void *export_unicode_text(void *data, size_t size, size_t *ret_size) return bytes; }
+static void *export_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, export_unicode_text}, + {"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}, {NULL, 0, NULL}, };
@@ -171,6 +192,13 @@ static struct data_device_format *data_device_format_for_mime_type(const char *m return NULL; }
+static ATOM register_clipboard_format(const WCHAR *name) +{ + ATOM atom; + if (NtAddAtom(name, lstrlenW(name) * sizeof(WCHAR), &atom)) return 0; + return atom; +} + /********************************************************************** * wl_data_source handling */ @@ -272,12 +300,19 @@ static const struct wl_data_source_listener data_source_listener = void wayland_data_device_init(void) { struct wayland_data_device *data_device = &process_wayland.data_device; + struct data_device_format *format = supported_formats;
pthread_mutex_lock(&data_device->mutex); data_device->wl_data_device = wl_data_device_manager_get_data_device(process_wayland.wl_data_device_manager, process_wayland.seat.wl_seat); pthread_mutex_unlock(&data_device->mutex); + + for (; format->mime_type; ++format) + { + if (format->clipboard_format == 0) + format->clipboard_format = register_clipboard_format(format->register_name); + } }
void wayland_data_device_deinit(void)
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;
From: Alexandros Frantzis alexandros.frantzis@collabora.com
--- dlls/winewayland.drv/wayland_data_device.c | 45 +++++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_data_device.c b/dlls/winewayland.drv/wayland_data_device.c index b0043c57825..097c3d8c221 100644 --- a/dlls/winewayland.drv/wayland_data_device.c +++ b/dlls/winewayland.drv/wayland_data_device.c @@ -76,6 +76,35 @@ static HWND get_clipboard_hwnd(void) return desktop_clipboard_hwnd; }
+/* Normalize the MIME type string by skipping inconsequential characters, + * such as spaces and double quotes, and convert to lower case. */ +static const char *normalize_mime_type(const char *mime_type) +{ + char *new_mime_type; + const char *cur_read; + char *cur_write; + size_t new_mime_len = 0; + + for (cur_read = mime_type; *cur_read != '\0'; ++cur_read) + { + if (*cur_read != ' ' && *cur_read != '"') + new_mime_len++; + } + + new_mime_type = malloc(new_mime_len + 1); + if (!new_mime_type) return NULL; + + for (cur_read = mime_type, cur_write = new_mime_type; *cur_read != '\0'; ++cur_read) + { + if (*cur_read != ' ' && *cur_read != '"') + *cur_write++ = tolower(*cur_read); + } + + *cur_write = '\0'; + + return new_mime_type; +} + static int poll_until(int fd, int events, ULONG end_time) { struct pollfd pfd = { .fd = fd, .events = events & ~POLLHUP }; @@ -358,10 +387,14 @@ static void data_source_target(void *data, struct wl_data_source *source, static void data_source_send(void *data, struct wl_data_source *source, const char *mime_type, int32_t fd) { - struct data_device_format *format = - data_device_format_for_mime_type(mime_type); + struct data_device_format *format; + const char *normalized;
- if (format) wayland_data_source_export(format, fd); + if ((normalized = normalize_mime_type(mime_type)) && + (format = data_device_format_for_mime_type(normalized))) + { + wayland_data_source_export(format, fd); + } close(fd); }
@@ -408,13 +441,13 @@ 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 *normalized; const char **p;
- if ((type_copy = strdup(type)) && + if ((normalized = normalize_mime_type(type)) && (p = wl_array_add(&data_offer->types, sizeof *p))) { - *p = type_copy; + *p = normalized; } }
I think the special mime type should be generated with a random number per wineserver instance so that clipboard transfer between applications in separate wineprefixes will work.
On Tue Feb 4 03:05:37 2025 +0000, Attila Fidan wrote:
I think the special mime type should be generated with a random number per wineserver instance so that clipboard transfer between applications in separate wineprefixes will work.
In what situation is this a problem? Wouldn't the X11 driver as it currently exists behave the same way?
On Tue Feb 4 21:40:31 2025 +0000, Esme Povirk wrote:
In what situation is this a problem? Wouldn't the X11 driver as it currently exists behave the same way?
I am not sure about the intended behaviour of the x11 driver or have experience writing for the x11 clipboard, but interprefix copy/paste works fine on my system (using xwayland). It doesn't work between two patched winewayland prefixes because the receiving prefix will ignore the sending prefix's offer due to the mime tag.