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;