This MR adds support for handling `wl_pointer` devices:
1. Handle enter/leave/(absolute) motion/button/axis events to allow users to interact with applications using such devices, e.g., a mouse. 2. Update the cursor image used for Wayland surfaces based on the Windows cursor bitmap data.
A few notes:
1. A single `wl_seat`/`wl_pointer` is supported for now. 2. Many of the input interactions require window management support that hasn't been implemented yet (e.g., popup positioning, resizing, max/fullscreen). 3. There is some potential for bad interactions between the input event dispatching mechanism and the `win32u` flushing logic for `window_surface` , which can lead to visual glitches. Although I don't think this is blocker for this MR, I would like to start a discussion about the problem and mitigation ideas in a [comment](https://gitlab.winehq.org/wine/wine/-/merge_requests/3686#note_43830) below.
Thanks!
-- v2: winewayland.drv: Implement SetCursor using cursor bitmap data. winewayland.drv: Handle pointer button and scroll events. winewayland.drv: Handle pointer motion events. winewayland.drv: Handle pointer focus events.
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Track the availability of a pointer device for a seat. For now we assume only a single seat and pointer. --- dlls/winewayland.drv/Makefile.in | 1 + dlls/winewayland.drv/wayland.c | 43 +++++++++++ dlls/winewayland.drv/wayland_pointer.c | 98 ++++++++++++++++++++++++++ dlls/winewayland.drv/waylanddrv.h | 9 +++ 4 files changed, 151 insertions(+) create mode 100644 dlls/winewayland.drv/wayland_pointer.c
diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index ef75c289b76..6bc03187d61 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -9,6 +9,7 @@ SOURCES = \ version.rc \ wayland.c \ wayland_output.c \ + wayland_pointer.c \ wayland_surface.c \ waylanddrv_main.c \ window.c \ diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index d35bd061b7e..6ea4a94551d 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -53,6 +53,29 @@ static const struct xdg_wm_base_listener xdg_wm_base_listener = xdg_wm_base_handle_ping };
+/********************************************************************** + * wl_seat handling + */ + +static void wl_seat_handle_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) +{ + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !process_wayland.wl_pointer) + wayland_pointer_init(wl_seat_get_pointer(seat)); + else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && process_wayland.wl_pointer) + wayland_pointer_deinit(); +} + +static void wl_seat_handle_name(void *data, struct wl_seat *seat, const char *name) +{ +} + +static const struct wl_seat_listener seat_listener = +{ + wl_seat_handle_capabilities, + wl_seat_handle_name +}; + /********************************************************************** * Registry handling */ @@ -98,6 +121,17 @@ static void registry_handle_global(void *data, struct wl_registry *registry, { process_wayland.wl_shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); } + else if (strcmp(interface, "wl_seat") == 0) + { + if (process_wayland.wl_seat) + { + WARN("Only a single seat is currently supported, ignoring additional seats.\n"); + return; + } + process_wayland.wl_seat = wl_registry_bind(registry, id, &wl_seat_interface, + version < 5 ? version : 5); + wl_seat_add_listener(process_wayland.wl_seat, &seat_listener, NULL); + } }
static void registry_handle_global_remove(void *data, struct wl_registry *registry, @@ -116,6 +150,15 @@ static void registry_handle_global_remove(void *data, struct wl_registry *regist return; } } + + if (process_wayland.wl_seat && + wl_proxy_get_id((struct wl_proxy *)process_wayland.wl_seat) == id) + { + TRACE("removing seat\n"); + if (process_wayland.wl_pointer) wayland_pointer_deinit(); + wl_seat_release(process_wayland.wl_seat); + process_wayland.wl_seat = NULL; + } }
static const struct wl_registry_listener registry_listener = { diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c new file mode 100644 index 00000000000..10dacfa03b0 --- /dev/null +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -0,0 +1,98 @@ +/* + * Wayland pointer handling + * + * Copyright (c) 2020 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" + +static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t sx, wl_fixed_t sy) +{ +} + +static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *wl_surface, + wl_fixed_t sx, wl_fixed_t sy) +{ +} + +static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *wl_surface) +{ +} + +static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, + uint32_t state) +{ +} + +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ +} + +static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) +{ +} + +static void pointer_handle_axis_source(void *data, struct wl_pointer *wl_pointer, + uint32_t axis_source) +{ +} + +static void pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis) +{ +} + +static void pointer_handle_axis_discrete(void *data, struct wl_pointer *wl_pointer, + uint32_t axis, int32_t discrete) +{ +} + +static const struct wl_pointer_listener pointer_listener = +{ + pointer_handle_enter, + pointer_handle_leave, + pointer_handle_motion, + pointer_handle_button, + pointer_handle_axis, + pointer_handle_frame, + pointer_handle_axis_source, + pointer_handle_axis_stop, + pointer_handle_axis_discrete +}; + +void wayland_pointer_init(struct wl_pointer *wl_pointer) +{ + process_wayland.wl_pointer = wl_pointer; + wl_pointer_add_listener(process_wayland.wl_pointer, &pointer_listener, NULL); +} + +void wayland_pointer_deinit(void) +{ + wl_pointer_release(process_wayland.wl_pointer); + process_wayland.wl_pointer = NULL; +} diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 0fd2a7a6c77..8932c2c9e8d 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -66,6 +66,8 @@ struct wayland struct wl_compositor *wl_compositor; struct xdg_wm_base *xdg_wm_base; struct wl_shm *wl_shm; + struct wl_seat *wl_seat; + struct wl_pointer *wl_pointer; struct wl_list output_list; /* Protects the output_list and the wayland_output.current states. */ pthread_mutex_t output_mutex; @@ -167,6 +169,13 @@ void wayland_window_surface_update_wayland_surface(struct window_surface *surfac struct wayland_surface *wayland_surface) DECLSPEC_HIDDEN; void wayland_window_flush(HWND hwnd) DECLSPEC_HIDDEN;
+/********************************************************************** + * Wayland pointer + */ + +void wayland_pointer_init(struct wl_pointer *wl_pointer) DECLSPEC_HIDDEN; +void wayland_pointer_deinit(void) DECLSPEC_HIDDEN; + /********************************************************************** * Helpers */
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Handle wl_pointer enter/leave events and maintain information about the focused HWND. Since pointer information will be accessed by any UI capable thread, ensure proper proper locking is in place. --- dlls/winewayland.drv/wayland.c | 7 +++-- dlls/winewayland.drv/wayland_pointer.c | 43 +++++++++++++++++++++++--- dlls/winewayland.drv/wayland_surface.c | 6 ++++ dlls/winewayland.drv/waylanddrv.h | 9 +++++- 4 files changed, 57 insertions(+), 8 deletions(-)
diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index 6ea4a94551d..51e36b5ba60 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -34,6 +34,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(waylanddrv);
struct wayland process_wayland = { + .pointer.mutex = PTHREAD_MUTEX_INITIALIZER, .output_list = {&process_wayland.output_list, &process_wayland.output_list}, .output_mutex = PTHREAD_MUTEX_INITIALIZER }; @@ -60,9 +61,9 @@ static const struct xdg_wm_base_listener xdg_wm_base_listener = static void wl_seat_handle_capabilities(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { - if ((caps & WL_SEAT_CAPABILITY_POINTER) && !process_wayland.wl_pointer) + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !process_wayland.pointer.wl_pointer) wayland_pointer_init(wl_seat_get_pointer(seat)); - else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && process_wayland.wl_pointer) + else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && process_wayland.pointer.wl_pointer) wayland_pointer_deinit(); }
@@ -155,7 +156,7 @@ static void registry_handle_global_remove(void *data, struct wl_registry *regist wl_proxy_get_id((struct wl_proxy *)process_wayland.wl_seat) == id) { TRACE("removing seat\n"); - if (process_wayland.wl_pointer) wayland_pointer_deinit(); + if (process_wayland.pointer.wl_pointer) wayland_pointer_deinit(); wl_seat_release(process_wayland.wl_seat); process_wayland.wl_seat = NULL; } diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index 10dacfa03b0..cb9091f0cf2 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -25,6 +25,9 @@ #include "config.h"
#include "waylanddrv.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(waylanddrv);
static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) @@ -35,11 +38,33 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *wl_surface, wl_fixed_t sx, wl_fixed_t sy) { + struct wayland_pointer *pointer = &process_wayland.pointer; + HWND hwnd; + + if (!wl_surface) return; + /* The wl_surface user data remains valid and immutable for the whole + * lifetime of the object, so it's safe to access without locking. */ + hwnd = wl_surface_get_user_data(wl_surface); + + TRACE("hwnd=%p\n", hwnd); + + pthread_mutex_lock(&pointer->mutex); + pointer->focused_hwnd = hwnd; + pthread_mutex_unlock(&pointer->mutex); }
static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *wl_surface) { + struct wayland_pointer *pointer = &process_wayland.pointer; + + if (!wl_surface) return; + + TRACE("hwnd=%p\n", wl_surface_get_user_data(wl_surface)); + + pthread_mutex_lock(&pointer->mutex); + pointer->focused_hwnd = NULL; + pthread_mutex_unlock(&pointer->mutex); }
static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, @@ -87,12 +112,22 @@ static const struct wl_pointer_listener pointer_listener =
void wayland_pointer_init(struct wl_pointer *wl_pointer) { - process_wayland.wl_pointer = wl_pointer; - wl_pointer_add_listener(process_wayland.wl_pointer, &pointer_listener, NULL); + struct wayland_pointer *pointer = &process_wayland.pointer; + + pthread_mutex_lock(&pointer->mutex); + pointer->wl_pointer = wl_pointer; + pointer->focused_hwnd = NULL; + pthread_mutex_unlock(&pointer->mutex); + wl_pointer_add_listener(pointer->wl_pointer, &pointer_listener, NULL); }
void wayland_pointer_deinit(void) { - wl_pointer_release(process_wayland.wl_pointer); - process_wayland.wl_pointer = NULL; + struct wayland_pointer *pointer = &process_wayland.pointer; + + pthread_mutex_lock(&pointer->mutex); + wl_pointer_release(pointer->wl_pointer); + pointer->wl_pointer = NULL; + pointer->focused_hwnd = NULL; + pthread_mutex_unlock(&pointer->mutex); } diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index fbc49191d9e..bc42da72b1e 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -108,6 +108,7 @@ struct wayland_surface *wayland_surface_create(HWND hwnd) ERR("Failed to create wl_surface Wayland surface\n"); goto err; } + wl_surface_set_user_data(surface->wl_surface, hwnd);
return surface;
@@ -123,6 +124,11 @@ err: */ void wayland_surface_destroy(struct wayland_surface *surface) { + pthread_mutex_lock(&process_wayland.pointer.mutex); + if (process_wayland.pointer.focused_hwnd == surface->hwnd) + process_wayland.pointer.focused_hwnd = NULL; + pthread_mutex_unlock(&process_wayland.pointer.mutex); + pthread_mutex_lock(&xdg_data_mutex); pthread_mutex_lock(&surface->mutex);
diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 8932c2c9e8d..e5c456d19e8 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -56,6 +56,13 @@ enum wayland_window_message WM_WAYLAND_INIT_DISPLAY_DEVICES = 0x80001000 };
+struct wayland_pointer +{ + struct wl_pointer *wl_pointer; + HWND focused_hwnd; + pthread_mutex_t mutex; +}; + struct wayland { BOOL initialized; @@ -67,7 +74,7 @@ struct wayland struct xdg_wm_base *xdg_wm_base; struct wl_shm *wl_shm; struct wl_seat *wl_seat; - struct wl_pointer *wl_pointer; + struct wayland_pointer pointer; struct wl_list output_list; /* Protects the output_list and the wayland_output.current states. */ pthread_mutex_t output_mutex;
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Also emit a synthetic motion event on pointer entry, to handle cases where the compositor doesn't send an initial motion event itself. --- dlls/winewayland.drv/Makefile.in | 2 +- dlls/winewayland.drv/wayland_pointer.c | 46 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index 6bc03187d61..e1019ad8348 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -1,7 +1,7 @@ MODULE = winewayland.drv UNIXLIB = winewayland.so UNIX_CFLAGS = $(WAYLAND_CLIENT_CFLAGS) -UNIX_LIBS = -lwin32u $(WAYLAND_CLIENT_LIBS) $(PTHREAD_LIBS) +UNIX_LIBS = -lwin32u $(WAYLAND_CLIENT_LIBS) $(PTHREAD_LIBS) -lm
SOURCES = \ display.c \ diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index cb9091f0cf2..fdae963439a 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -24,14 +24,55 @@
#include "config.h"
+#include <math.h> + #include "waylanddrv.h" #include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(waylanddrv);
+static HWND wayland_pointer_get_focused_hwnd(void) +{ + struct wayland_pointer *pointer = &process_wayland.pointer; + HWND hwnd; + + pthread_mutex_lock(&pointer->mutex); + hwnd = pointer->focused_hwnd; + pthread_mutex_unlock(&pointer->mutex); + + return hwnd; +} + static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { + INPUT input = {0}; + RECT window_rect; + HWND hwnd; + int screen_x, screen_y; + + if (!(hwnd = wayland_pointer_get_focused_hwnd())) return; + if (!NtUserGetWindowRect(hwnd, &window_rect)) return; + + screen_x = round(wl_fixed_to_double(sx)) + window_rect.left; + screen_y = round(wl_fixed_to_double(sy)) + window_rect.top; + /* Sometimes, due to rounding, we may end up with pointer coordinates + * slightly outside the target window, so bring them within bounds. */ + if (screen_x >= window_rect.right) screen_x = window_rect.right - 1; + else if (screen_x < window_rect.left) screen_x = window_rect.left; + if (screen_y >= window_rect.bottom) screen_y = window_rect.bottom - 1; + else if (screen_y < window_rect.top) screen_y = window_rect.top; + + input.type = INPUT_MOUSE; + input.mi.dx = screen_x; + input.mi.dy = screen_y; + input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; + + TRACE("hwnd=%p wayland_xy=%.2f,%.2f screen_xy=%d,%d\n", + hwnd, wl_fixed_to_double(sx), wl_fixed_to_double(sy), + screen_x, screen_y); + + __wine_send_input(hwnd, &input, NULL); }
static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, @@ -51,6 +92,11 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, pthread_mutex_lock(&pointer->mutex); pointer->focused_hwnd = hwnd; pthread_mutex_unlock(&pointer->mutex); + + /* Handle the enter as a motion, to account for cases where the + * window first appears beneath the pointer and won't get a separate + * motion event. */ + pointer_handle_motion(data, wl_pointer, 0, sx, sy); }
static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer,
From: Alexandros Frantzis alexandros.frantzis@collabora.com
--- dlls/winewayland.drv/wayland_pointer.c | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index fdae963439a..222c35f6692 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -24,6 +24,8 @@
#include "config.h"
+#include <linux/input.h> +#undef SW_MAX /* Also defined in winuser.rh */ #include <math.h>
#include "waylanddrv.h" @@ -117,6 +119,26 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + INPUT input = {0}; + HWND hwnd; + + if (!(hwnd = wayland_pointer_get_focused_hwnd())) return; + + input.type = INPUT_MOUSE; + + switch (button) + { + case BTN_LEFT: input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; break; + case BTN_RIGHT: input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; break; + case BTN_MIDDLE: input.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN; break; + default: break; + } + + if (state == WL_POINTER_BUTTON_STATE_RELEASED) input.mi.dwFlags <<= 1; + + TRACE("hwnd=%p button=%#x state=%u\n", hwnd, button, state); + + __wine_send_input(hwnd, &input, NULL); }
static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, @@ -141,6 +163,29 @@ static void pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, static void pointer_handle_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { + INPUT input = {0}; + HWND hwnd; + + if (!(hwnd = wayland_pointer_get_focused_hwnd())) return; + + input.type = INPUT_MOUSE; + + switch (axis) + { + case WL_POINTER_AXIS_VERTICAL_SCROLL: + input.mi.dwFlags = MOUSEEVENTF_WHEEL; + input.mi.mouseData = -WHEEL_DELTA * discrete; + break; + case WL_POINTER_AXIS_HORIZONTAL_SCROLL: + input.mi.dwFlags = MOUSEEVENTF_HWHEEL; + input.mi.mouseData = WHEEL_DELTA * discrete; + break; + default: break; + } + + TRACE("hwnd=%p axis=%u discrete=%d\n", hwnd, axis, discrete); + + __wine_send_input(hwnd, &input, NULL); }
static const struct wl_pointer_listener pointer_listener =
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Set the cursor image used for Wayland surfaces by using the Windows cursor bitmap data. --- dlls/winewayland.drv/wayland_pointer.c | 286 +++++++++++++++++++++++++ dlls/winewayland.drv/wayland_surface.c | 3 + dlls/winewayland.drv/waylanddrv.h | 10 + dlls/winewayland.drv/waylanddrv_main.c | 1 + 4 files changed, 300 insertions(+)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index 222c35f6692..69f3a7c16a7 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -27,6 +27,7 @@ #include <linux/input.h> #undef SW_MAX /* Also defined in winuser.rh */ #include <math.h> +#include <stdlib.h>
#include "waylanddrv.h" #include "wine/debug.h" @@ -93,6 +94,14 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer,
pthread_mutex_lock(&pointer->mutex); pointer->focused_hwnd = hwnd; + pointer->enter_serial = serial; + /* The cursor is undefined at every enter, so we set it again with + * the latest information we have. */ + wl_pointer_set_cursor(pointer->wl_pointer, + pointer->enter_serial, + pointer->cursor.wl_surface, + pointer->cursor.hotspot_x, + pointer->cursor.hotspot_y); pthread_mutex_unlock(&pointer->mutex);
/* Handle the enter as a motion, to account for cases where the @@ -112,6 +121,7 @@ static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer,
pthread_mutex_lock(&pointer->mutex); pointer->focused_hwnd = NULL; + pointer->enter_serial = 0; pthread_mutex_unlock(&pointer->mutex); }
@@ -208,6 +218,7 @@ void wayland_pointer_init(struct wl_pointer *wl_pointer) pthread_mutex_lock(&pointer->mutex); pointer->wl_pointer = wl_pointer; pointer->focused_hwnd = NULL; + pointer->enter_serial = 0; pthread_mutex_unlock(&pointer->mutex); wl_pointer_add_listener(pointer->wl_pointer, &pointer_listener, NULL); } @@ -220,5 +231,280 @@ void wayland_pointer_deinit(void) wl_pointer_release(pointer->wl_pointer); pointer->wl_pointer = NULL; pointer->focused_hwnd = NULL; + pointer->enter_serial = 0; + pthread_mutex_unlock(&pointer->mutex); +} + +/*********************************************************************** + * create_mono_cursor_buffer + * + * Create a wayland_shm_buffer for a monochrome cursor bitmap. + * + * Adapted from wineandroid.drv code. + */ +static struct wayland_shm_buffer *create_mono_cursor_buffer(HBITMAP bmp) +{ + struct wayland_shm_buffer *shm_buffer = NULL; + BITMAP bm; + char *mask = NULL; + unsigned int i, j, stride, mask_size, *ptr; + + if (!NtGdiExtGetObjectW(bmp, sizeof(bm), &bm)) goto done; + stride = ((bm.bmWidth + 15) >> 3) & ~1; + mask_size = stride * bm.bmHeight; + if (!(mask = malloc(mask_size))) goto done; + if (!NtGdiGetBitmapBits(bmp, mask_size, mask)) goto done; + + bm.bmHeight /= 2; + shm_buffer = wayland_shm_buffer_create(bm.bmWidth, bm.bmHeight, + WL_SHM_FORMAT_ARGB8888); + if (!shm_buffer) goto done; + + ptr = shm_buffer->map_data; + for (i = 0; i < bm.bmHeight; i++) + { + for (j = 0; j < bm.bmWidth; j++, ptr++) + { + int and = ((mask[i * stride + j / 8] << (j % 8)) & 0x80); + int xor = ((mask[(i + bm.bmHeight) * stride + j / 8] << (j % 8)) & 0x80); + if (!xor && and) + *ptr = 0; + else if (xor && !and) + *ptr = 0xffffffff; + else + /* we can't draw "invert" pixels, so render them as black instead */ + *ptr = 0xff000000; + } + } + +done: + free(mask); + return shm_buffer; +} + +/*********************************************************************** + * create_color_cursor_buffer + * + * Create a wayland_shm_buffer for a color cursor bitmap. + * + * Adapted from wineandroid.drv code. + */ +static struct wayland_shm_buffer *create_color_cursor_buffer(HDC hdc, HBITMAP color, + HBITMAP mask) +{ + struct wayland_shm_buffer *shm_buffer = NULL; + char buffer[FIELD_OFFSET(BITMAPINFO, bmiColors[256])]; + BITMAPINFO *info = (BITMAPINFO *)buffer; + BITMAP bm; + unsigned int *ptr, *bits = NULL; + unsigned char *mask_bits = NULL; + int i, j; + BOOL has_alpha = FALSE; + + if (!NtGdiExtGetObjectW(color, sizeof(bm), &bm)) goto failed; + + shm_buffer = wayland_shm_buffer_create(bm.bmWidth, bm.bmHeight, + WL_SHM_FORMAT_ARGB8888); + if (!shm_buffer) goto failed; + bits = shm_buffer->map_data; + + info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info->bmiHeader.biWidth = bm.bmWidth; + info->bmiHeader.biHeight = -bm.bmHeight; + info->bmiHeader.biPlanes = 1; + info->bmiHeader.biBitCount = 32; + info->bmiHeader.biCompression = BI_RGB; + info->bmiHeader.biSizeImage = bm.bmWidth * bm.bmHeight * 4; + info->bmiHeader.biXPelsPerMeter = 0; + info->bmiHeader.biYPelsPerMeter = 0; + info->bmiHeader.biClrUsed = 0; + info->bmiHeader.biClrImportant = 0; + + if (!NtGdiGetDIBitsInternal(hdc, color, 0, bm.bmHeight, bits, info, + DIB_RGB_COLORS, 0, 0)) + goto failed; + + for (i = 0; i < bm.bmWidth * bm.bmHeight; i++) + if ((has_alpha = (bits[i] & 0xff000000) != 0)) break; + + if (!has_alpha) + { + unsigned int width_bytes = (bm.bmWidth + 31) / 32 * 4; + /* generate alpha channel from the mask */ + info->bmiHeader.biBitCount = 1; + info->bmiHeader.biSizeImage = width_bytes * bm.bmHeight; + if (!(mask_bits = malloc(info->bmiHeader.biSizeImage))) goto failed; + if (!NtGdiGetDIBitsInternal(hdc, mask, 0, bm.bmHeight, mask_bits, + info, DIB_RGB_COLORS, 0, 0)) + goto failed; + ptr = bits; + for (i = 0; i < bm.bmHeight; i++) + { + for (j = 0; j < bm.bmWidth; j++, ptr++) + { + if (!((mask_bits[i * width_bytes + j / 8] << (j % 8)) & 0x80)) + *ptr |= 0xff000000; + } + } + free(mask_bits); + } + + /* Wayland requires pre-multiplied alpha values */ + for (ptr = bits, i = 0; i < bm.bmWidth * bm.bmHeight; ptr++, i++) + { + unsigned char alpha = *ptr >> 24; + if (alpha == 0) + { + *ptr = 0; + } + else if (alpha != 255) + { + *ptr = (alpha << 24) | + (((BYTE)(*ptr >> 16) * alpha / 255) << 16) | + (((BYTE)(*ptr >> 8) * alpha / 255) << 8) | + (((BYTE)*ptr * alpha / 255)); + } + } + + return shm_buffer; + +failed: + if (shm_buffer) wayland_shm_buffer_unref(shm_buffer); + free(mask_bits); + return NULL; +} + +/*********************************************************************** + * get_icon_info + * + * Local GetIconInfoExW helper implementation. + */ +static BOOL get_icon_info(HICON handle, ICONINFOEXW *ret) +{ + UNICODE_STRING module, res_name; + ICONINFO info; + + module.Buffer = ret->szModName; + module.MaximumLength = sizeof(ret->szModName) - sizeof(WCHAR); + res_name.Buffer = ret->szResName; + res_name.MaximumLength = sizeof(ret->szResName) - sizeof(WCHAR); + if (!NtUserGetIconInfo(handle, &info, &module, &res_name, NULL, 0)) return FALSE; + ret->fIcon = info.fIcon; + ret->xHotspot = info.xHotspot; + ret->yHotspot = info.yHotspot; + ret->hbmColor = info.hbmColor; + ret->hbmMask = info.hbmMask; + ret->wResID = res_name.Length ? 0 : LOWORD(res_name.Buffer); + ret->szModName[module.Length] = 0; + ret->szResName[res_name.Length] = 0; + return TRUE; +} + +static void wayland_pointer_update_cursor(HCURSOR hcursor) +{ + struct wayland_cursor *cursor = &process_wayland.pointer.cursor; + ICONINFOEXW info = {0}; + + if (!hcursor) goto clear_cursor; + + /* Create a new buffer for the specified cursor. */ + if (cursor->shm_buffer) + { + wayland_shm_buffer_unref(cursor->shm_buffer); + cursor->shm_buffer = NULL; + } + + if (!get_icon_info(hcursor, &info)) + { + ERR("Failed to get icon info for cursor=%p\n", hcursor); + goto clear_cursor; + } + + if (info.hbmColor) + { + HDC hdc = NtGdiCreateCompatibleDC(0); + cursor->shm_buffer = + create_color_cursor_buffer(hdc, info.hbmColor, info.hbmMask); + NtGdiDeleteObjectApp(hdc); + } + else + { + cursor->shm_buffer = create_mono_cursor_buffer(info.hbmMask); + } + + if (info.hbmColor) NtGdiDeleteObjectApp(info.hbmColor); + if (info.hbmMask) NtGdiDeleteObjectApp(info.hbmMask); + + cursor->hotspot_x = info.xHotspot; + cursor->hotspot_y = info.yHotspot; + + if (!cursor->shm_buffer) + { + ERR("Failed to create shm_buffer for cursor=%p\n", hcursor); + goto clear_cursor; + } + + /* Make sure the hotspot is valid. */ + if (cursor->hotspot_x >= cursor->shm_buffer->width || + cursor->hotspot_y >= cursor->shm_buffer->height) + { + cursor->hotspot_x = cursor->shm_buffer->width / 2; + cursor->hotspot_y = cursor->shm_buffer->height / 2; + } + + if (!cursor->wl_surface) + { + cursor->wl_surface = + wl_compositor_create_surface(process_wayland.wl_compositor); + if (!cursor->wl_surface) + { + ERR("Failed to create wl_surface for cursor\n"); + goto clear_cursor; + } + } + + /* Commit the cursor buffer to the cursor surface. */ + wl_surface_attach(cursor->wl_surface, + cursor->shm_buffer->wl_buffer, 0, 0); + wl_surface_damage_buffer(cursor->wl_surface, 0, 0, + cursor->shm_buffer->width, + cursor->shm_buffer->height); + wl_surface_commit(cursor->wl_surface); + + return; + +clear_cursor: + if (cursor->shm_buffer) + { + wayland_shm_buffer_unref(cursor->shm_buffer); + cursor->shm_buffer = NULL; + } + if (cursor->wl_surface) + { + wl_surface_destroy(cursor->wl_surface); + cursor->wl_surface = NULL; + } +} + +/*********************************************************************** + * WAYLAND_SetCursor + */ +void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor) +{ + struct wayland_pointer *pointer = &process_wayland.pointer; + + TRACE("hwnd=%p hcursor=%p\n", hwnd, hcursor); + + pthread_mutex_lock(&pointer->mutex); + if (pointer->focused_hwnd == hwnd) + { + wayland_pointer_update_cursor(hcursor); + wl_pointer_set_cursor(pointer->wl_pointer, + pointer->enter_serial, + pointer->cursor.wl_surface, + pointer->cursor.hotspot_x, + pointer->cursor.hotspot_y); + wl_display_flush(process_wayland.wl_display); + } pthread_mutex_unlock(&pointer->mutex); } diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index bc42da72b1e..7e77c9ba55f 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -126,7 +126,10 @@ void wayland_surface_destroy(struct wayland_surface *surface) { pthread_mutex_lock(&process_wayland.pointer.mutex); if (process_wayland.pointer.focused_hwnd == surface->hwnd) + { process_wayland.pointer.focused_hwnd = NULL; + process_wayland.pointer.enter_serial = 0; + } pthread_mutex_unlock(&process_wayland.pointer.mutex);
pthread_mutex_lock(&xdg_data_mutex); diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index e5c456d19e8..f4292e0ae28 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -56,10 +56,19 @@ enum wayland_window_message WM_WAYLAND_INIT_DISPLAY_DEVICES = 0x80001000 };
+struct wayland_cursor +{ + struct wayland_shm_buffer *shm_buffer; + struct wl_surface *wl_surface; + int hotspot_x, hotspot_y; +}; + struct wayland_pointer { struct wl_pointer *wl_pointer; HWND focused_hwnd; + uint32_t enter_serial; + struct wayland_cursor cursor; pthread_mutex_t mutex; };
@@ -204,6 +213,7 @@ RGNDATA *get_region_data(HRGN region) DECLSPEC_HIDDEN;
LRESULT WAYLAND_DesktopWindowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) DECLSPEC_HIDDEN; void WAYLAND_DestroyWindow(HWND hwnd) DECLSPEC_HIDDEN; +void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor) DECLSPEC_HIDDEN; BOOL WAYLAND_UpdateDisplayDevices(const struct gdi_device_manager *device_manager, BOOL force, void *param) DECLSPEC_HIDDEN; LRESULT WAYLAND_WindowMessage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) DECLSPEC_HIDDEN; diff --git a/dlls/winewayland.drv/waylanddrv_main.c b/dlls/winewayland.drv/waylanddrv_main.c index 530cdd83d90..eb54efe1180 100644 --- a/dlls/winewayland.drv/waylanddrv_main.c +++ b/dlls/winewayland.drv/waylanddrv_main.c @@ -33,6 +33,7 @@ static const struct user_driver_funcs waylanddrv_funcs = { .pDesktopWindowProc = WAYLAND_DesktopWindowProc, .pDestroyWindow = WAYLAND_DestroyWindow, + .pSetCursor = WAYLAND_SetCursor, .pUpdateDisplayDevices = WAYLAND_UpdateDisplayDevices, .pWindowMessage = WAYLAND_WindowMessage, .pWindowPosChanged = WAYLAND_WindowPosChanged,
This is also my understanding [...]
Thanks, I have updated the MR to use the simpler approach, as discussed above.
Fwiw you could perhaps also do wl_surface_set_user_data(wl_surface, 0) when it is destroyed, but I don't know how whether it synchronizes properly or if it would then require locking.
Access to Wayland object user data is not internally synchronized, and would require locking similar to what we do with the `xdg_surface` user data. In addition, I don't find the benefit to be substantial enough: even if we clear the HWND on `wl_surface` destruction, we are only decreasing, not eliminating, the possibility of an event handler using an invalid HWND value (e.g., the window may be destroyed right after we read the HWND in `pointer_handle_enter`)
Rémi Bernon (@rbernon) commented about dlls/winewayland.drv/wayland_pointer.c:
uint32_t serial, struct wl_surface *wl_surface, wl_fixed_t sx, wl_fixed_t sy)
{
- struct wayland_pointer *pointer = &process_wayland.pointer;
- HWND hwnd;
- if (!wl_surface) return;
Is it really allowed to have NULL surface?
Looks okay I think. (I'm assuming the wl_surface checks are there for a reason)
This merge request was approved by Rémi Bernon.
On Thu Sep 7 14:32:47 2023 +0000, Alexandros Frantzis wrote:
changed this line in [version 2 of the diff](/wine/wine/-/merge_requests/3686/diffs?diff_id=67470&start_sha=605c29e1eb3a3ce464c32c90860c7d8e042a65e7#eb7d7a7ef7f5aace0d92d509eb6d01faa73cd029_39_39)
Renames removed in version 2.
On Thu Sep 7 17:32:02 2023 +0000, Rémi Bernon wrote:
Is it really allowed to have NULL surface?
Yes, if the `wl_surface` (and more generally any wayland object that is used as an argument to an event) is destroyed by the client before the client queues the respective event that references it, we will get [a NULL value](https://gitlab.freedesktop.org/wayland/wayland/-/blob/edb943dc6464697ba13d7d...) for it. It is during the queuing process that libwayland [acquires an additional reference](https://gitlab.freedesktop.org/wayland/wayland/-/blob/edb943dc6464697ba13d7d...) to any object arguments to keep them alive until dispatch.
On Fri Sep 8 12:02:46 2023 +0000, Alexandros Frantzis wrote:
Indeed it does, but this is an actual value I have seen on my system (with the `light` theme). It's not always that bad, although a quick test shows that the mean duration of `UXTHEME_ScrollBarDraw` is ~13ms with Wayland and ~10ms with X11, which is still a lot. FWIW, the majority of the time is spent drawing the top and bottom arrows of the scrollbar. There is certainly room for optimization here, but the timing of this particular scenario is just an example. Any frequency of motion events/messages that's higher than the message loop frequency will cause the loop to not become idle, and will lead to artifacts if sustained long enough (e.g., consider motion events every 2ms with a 3ms draw/loop period). I suspect we will have a similar problem with sustained resize events.
For this I think maybe GetMessage should be in charge of making sure surfaces are flushed often enough. Maybe just a matter of calling `flush_window_surfaces( FALSE )` unconditionally in `check_for_driver_events`, as it's throttled internally already.