From: Alexandros Frantzis alexandros.frantzis@collabora.com
--- dlls/winewayland.drv/wayland.c | 1 + dlls/winewayland.drv/wayland_data_device.c | 217 +++++++++++++++++++++ dlls/winewayland.drv/waylanddrv.h | 2 + 3 files changed, 220 insertions(+)
diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index 7fddb77338a..5801fab346e 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 17ba8cddb03..556ef8e8deb 100644 --- a/dlls/winewayland.drv/wayland_data_device.c +++ b/dlls/winewayland.drv/wayland_data_device.c @@ -24,18 +24,230 @@
#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 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; +} + +/********************************************************************** + * zwlr_data_control_source_v1 handling + */ + +static void wayland_data_source_export(int32_t fd) +{ + struct get_clipboard_params params = { .data_only = TRUE, .size = 1024 }; + void *exported = NULL; + size_t exported_size; + + if (!NtUserOpenClipboard(clipboard_hwnd, 0)) + { + TRACE("failed to open clipboard for export\n"); + return; + } + + for (;;) + { + if (!(params.data = malloc(params.size))) break; + if (NtUserGetClipboardData(CF_UNICODETEXT, ¶ms)) + { + exported = export_unicode_text(params.data, params.size, &exported_size); + break; + } + if (!params.data_size) break; + free(params.data); + params.size = params.data_size; + params.data_size = 0; + } + + NtUserCloseClipboard(); + if (exported) write_all(fd, exported, exported_size); + + free(exported); + free(params.data); +} + +static void data_control_source_send(void *data, + struct zwlr_data_control_source_v1 *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_control_source_cancelled(void *data, + struct zwlr_data_control_source_v1 *source) +{ + struct wayland_data_device *data_device = data; + + pthread_mutex_lock(&data_device->mutex); + zwlr_data_control_source_v1_destroy(source); + if (source == data_device->zwlr_data_control_source_v1) + data_device->zwlr_data_control_source_v1 = NULL; + pthread_mutex_unlock(&data_device->mutex); +} + +static const struct zwlr_data_control_source_v1_listener data_control_source_listener = +{ + data_control_source_send, + data_control_source_cancelled, +};
void wayland_data_device_init(void) { struct wayland_data_device *data_device = &process_wayland.data_device;
+ pthread_mutex_lock(&data_device->mutex); if (data_device->zwlr_data_control_device_v1) zwlr_data_control_device_v1_destroy(data_device->zwlr_data_control_device_v1); data_device->zwlr_data_control_device_v1 = zwlr_data_control_manager_v1_get_data_device( process_wayland.zwlr_data_control_manager_v1, process_wayland.seat.wl_seat); + pthread_mutex_unlock(&data_device->mutex); +} + +static void clipboard_update(void) +{ + struct wayland_data_device *data_device = &process_wayland.data_device; + struct zwlr_data_control_source_v1 *source; + UINT *formats, formats_size = 256, i; + + TRACE("\n"); + + if (!process_wayland.zwlr_data_control_manager_v1) return; + + source = zwlr_data_control_manager_v1_create_data_source( + process_wayland.zwlr_data_control_manager_v1); + if (!source) + { + ERR("failed to create data source\n"); + return; + } + + for (;;) + { + if (!(formats = malloc(formats_size * sizeof(*formats)))) break; + if (NtUserGetUpdatedClipboardFormats(formats, formats_size, &formats_size)) break; + free(formats); + formats = NULL; + if (RtlGetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) break; + } + + if (!formats && formats_size) + { + ERR("failed to get clipboard formats\n"); + zwlr_data_control_source_v1_destroy(source); + return; + } + + for (i = 0; i < formats_size; ++i) + { + if (formats[i] == CF_UNICODETEXT) + zwlr_data_control_source_v1_offer(source, "text/plain;charset=utf-8"); + } + + free(formats); + + zwlr_data_control_source_v1_add_listener(source, &data_control_source_listener, data_device); + + pthread_mutex_lock(&data_device->mutex); + if (data_device->zwlr_data_control_device_v1) + zwlr_data_control_device_v1_set_selection(data_device->zwlr_data_control_device_v1, source); + /* Destroy any previous source only after setting the new source, to + * avoid spurious 'selection(nil)' events. */ + if (data_device->zwlr_data_control_source_v1) + zwlr_data_control_source_v1_destroy(data_device->zwlr_data_control_source_v1); + data_device->zwlr_data_control_source_v1 = source; + pthread_mutex_unlock(&data_device->mutex); + + wl_display_flush(process_wayland.wl_display); }
LRESULT WAYLAND_ClipboardWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) @@ -43,9 +255,14 @@ LRESULT WAYLAND_ClipboardWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM l switch (msg) { case WM_NCCREATE: + clipboard_hwnd = hwnd; + NtUserAddClipboardFormatListener(hwnd); if (process_wayland.seat.wl_seat && process_wayland.zwlr_data_control_manager_v1) wayland_data_device_init(); return TRUE; + case WM_CLIPBOARDUPDATE: + clipboard_update(); + 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 22f4778eb56..8066e2f29c6 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -122,6 +122,8 @@ struct wayland_seat 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; + pthread_mutex_t mutex; };
struct wayland