This MR uses the cursor-shapes-v1 protocol to tell the compositor which system cursor shape to use. If a shape match is not found (or cursor-shapes-v1 is not available) we fall back to setting the cursor buffer from the Windows cursor data as before.
The second commit implements support for a "UseSystemCursors" driver option, similar to what winex11 has. Since this is the first winewayland driver option we also introduce all the related registry reading code.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=57648
--
Side note: The registry code is a copy from winex11, and is the third copy in the codebase (x11, mac, wayland). Perhaps it's worth introducing a common ntuser function to perform the option reading for the drivers, something like `NtUserGetDriverOption("X11 Driver", buffer, buffer_size, TRUE /* whether to read app specific option if present */)`. With such a function there is the concern about each call reopening the registry, but we can see if that's actually a problem and how a different API (or some sort of caching) may help.
-- v5: winewayland: Support "UseSystemCursors" driver option. winewayland: Use system cursor shapes when possible.
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Use the cursor-shapes-v1 protocol to tell the compositor which system cursor shape to use. If a shape match is not found (or cursor-shapes-v1 is not available) we fall back to setting the cursor buffer from the Windows cursor data as before.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=57648 --- dlls/winewayland.drv/Makefile.in | 1 + dlls/winewayland.drv/cursor-shape-v1.xml | 162 ++++++++++++++++ dlls/winewayland.drv/wayland.c | 6 + dlls/winewayland.drv/wayland_pointer.c | 223 ++++++++++++++++++++--- dlls/winewayland.drv/waylanddrv.h | 3 + 5 files changed, 367 insertions(+), 28 deletions(-) create mode 100644 dlls/winewayland.drv/cursor-shape-v1.xml
diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index e7b1bfb90eb..10915b10334 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -5,6 +5,7 @@ UNIX_LIBS = -lwin32u $(WAYLAND_CLIENT_LIBS) $(WAYLAND_EGL_LIBS) $(XKBCOMMON_LIBS IMPORTS = user32 win32u
SOURCES = \ + cursor-shape-v1.xml \ display.c \ dllmain.c \ opengl.c \ diff --git a/dlls/winewayland.drv/cursor-shape-v1.xml b/dlls/winewayland.drv/cursor-shape-v1.xml new file mode 100644 index 00000000000..64b2f9b2c87 --- /dev/null +++ b/dlls/winewayland.drv/cursor-shape-v1.xml @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="cursor_shape_v1"> + <copyright> + Copyright 2018 The Chromium Authors + Copyright 2023 Simon Ser + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + </copyright> + + <interface name="wp_cursor_shape_manager_v1" version="2"> + <description summary="cursor shape manager"> + This global offers an alternative, optional way to set cursor images. This + new way uses enumerated cursors instead of a wl_surface like + wl_pointer.set_cursor does. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the manager"> + Destroy the cursor shape manager. + </description> + </request> + + <request name="get_pointer"> + <description summary="manage the cursor shape of a pointer device"> + Obtain a wp_cursor_shape_device_v1 for a wl_pointer object. + + When the pointer capability is removed from the wl_seat, the + wp_cursor_shape_device_v1 object becomes inert. + </description> + <arg name="cursor_shape_device" type="new_id" interface="wp_cursor_shape_device_v1"/> + <arg name="pointer" type="object" interface="wl_pointer"/> + </request> + + <request name="get_tablet_tool_v2"> + <description summary="manage the cursor shape of a tablet tool device"> + Obtain a wp_cursor_shape_device_v1 for a zwp_tablet_tool_v2 object. + + When the zwp_tablet_tool_v2 is removed, the wp_cursor_shape_device_v1 + object becomes inert. + </description> + <arg name="cursor_shape_device" type="new_id" interface="wp_cursor_shape_device_v1"/> + <arg name="tablet_tool" type="object" interface="zwp_tablet_tool_v2"/> + </request> + </interface> + + <interface name="wp_cursor_shape_device_v1" version="2"> + <description summary="cursor shape for a device"> + This interface allows clients to set the cursor shape. + </description> + + <enum name="shape"> + <description summary="cursor shapes"> + This enum describes cursor shapes. + + The names are taken from the CSS W3C specification: + https://w3c.github.io/csswg-drafts/css-ui/#cursor + with a few additions. + + Note that there are some groups of cursor shapes that are related: + The first group is drag-and-drop cursors which are used to indicate + the selected action during dnd operations. The second group is resize + cursors which are used to indicate resizing and moving possibilities + on window borders. It is recommended that the shapes in these groups + should use visually compatible images and metaphors. + </description> + <entry name="default" value="1" summary="default cursor"/> + <entry name="context_menu" value="2" summary="a context menu is available for the object under the cursor"/> + <entry name="help" value="3" summary="help is available for the object under the cursor"/> + <entry name="pointer" value="4" summary="pointer that indicates a link or another interactive element"/> + <entry name="progress" value="5" summary="progress indicator"/> + <entry name="wait" value="6" summary="program is busy, user should wait"/> + <entry name="cell" value="7" summary="a cell or set of cells may be selected"/> + <entry name="crosshair" value="8" summary="simple crosshair"/> + <entry name="text" value="9" summary="text may be selected"/> + <entry name="vertical_text" value="10" summary="vertical text may be selected"/> + <entry name="alias" value="11" summary="drag-and-drop: alias of/shortcut to something is to be created"/> + <entry name="copy" value="12" summary="drag-and-drop: something is to be copied"/> + <entry name="move" value="13" summary="drag-and-drop: something is to be moved"/> + <entry name="no_drop" value="14" summary="drag-and-drop: the dragged item cannot be dropped at the current cursor location"/> + <entry name="not_allowed" value="15" summary="drag-and-drop: the requested action will not be carried out"/> + <entry name="grab" value="16" summary="drag-and-drop: something can be grabbed"/> + <entry name="grabbing" value="17" summary="drag-and-drop: something is being grabbed"/> + <entry name="e_resize" value="18" summary="resizing: the east border is to be moved"/> + <entry name="n_resize" value="19" summary="resizing: the north border is to be moved"/> + <entry name="ne_resize" value="20" summary="resizing: the north-east corner is to be moved"/> + <entry name="nw_resize" value="21" summary="resizing: the north-west corner is to be moved"/> + <entry name="s_resize" value="22" summary="resizing: the south border is to be moved"/> + <entry name="se_resize" value="23" summary="resizing: the south-east corner is to be moved"/> + <entry name="sw_resize" value="24" summary="resizing: the south-west corner is to be moved"/> + <entry name="w_resize" value="25" summary="resizing: the west border is to be moved"/> + <entry name="ew_resize" value="26" summary="resizing: the east and west borders are to be moved"/> + <entry name="ns_resize" value="27" summary="resizing: the north and south borders are to be moved"/> + <entry name="nesw_resize" value="28" summary="resizing: the north-east and south-west corners are to be moved"/> + <entry name="nwse_resize" value="29" summary="resizing: the north-west and south-east corners are to be moved"/> + <entry name="col_resize" value="30" summary="resizing: that the item/column can be resized horizontally"/> + <entry name="row_resize" value="31" summary="resizing: that the item/row can be resized vertically"/> + <entry name="all_scroll" value="32" summary="something can be scrolled in any direction"/> + <entry name="zoom_in" value="33" summary="something can be zoomed in"/> + <entry name="zoom_out" value="34" summary="something can be zoomed out"/> + <entry name="dnd_ask" value="35" summary="drag-and-drop: the user will select which action will be carried out (non-css value)" since="2"/> + <entry name="all_resize" value="36" summary="resizing: something can be moved or resized in any direction (non-css value)" since="2"/> + </enum> + + <enum name="error"> + <entry name="invalid_shape" value="1" + summary="the specified shape value is invalid"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy the cursor shape device"> + Destroy the cursor shape device. + + The device cursor shape remains unchanged. + </description> + </request> + + <request name="set_shape"> + <description summary="set device cursor to the shape"> + Sets the device cursor to the specified shape. The compositor will + change the cursor image based on the specified shape. + + The cursor actually changes only if the input device focus is one of + the requesting client's surfaces. If any, the previous cursor image + (surface or shape) is replaced. + + The "shape" argument must be a valid enum entry, otherwise the + invalid_shape protocol error is raised. + + This is similar to the wl_pointer.set_cursor and + zwp_tablet_tool_v2.set_cursor requests, but this request accepts a + shape instead of contents in the form of a surface. Clients can mix + set_cursor and set_shape requests. + + The serial parameter must match the latest wl_pointer.enter or + zwp_tablet_tool_v2.proximity_in serial number sent to the client. + Otherwise the request will be ignored. + </description> + <arg name="serial" type="uint" summary="serial number of the enter event"/> + <arg name="shape" type="uint" enum="shape"/> + </request> + </interface> +</protocol> diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index ba09d32ed89..acc340d98c7 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -194,6 +194,12 @@ static void registry_handle_global(void *data, struct wl_registry *registry, process_wayland.xdg_toplevel_icon_manager_v1 = wl_registry_bind(registry, id, &xdg_toplevel_icon_manager_v1_interface, 1); } + else if (strcmp(interface, "wp_cursor_shape_manager_v1") == 0) + { + process_wayland.wp_cursor_shape_manager_v1 = + wl_registry_bind(registry, id, &wp_cursor_shape_manager_v1_interface, + version < 2 ? version : 2); + } }
static void registry_handle_global_remove(void *data, struct wl_registry *registry, diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index beb9cc06702..070f7d8b77e 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -29,11 +29,86 @@ #include <math.h> #include <stdlib.h>
+#define OEMRESOURCE + #include "waylanddrv.h" #include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(waylanddrv);
+/* The cursor-shape-v1 protocol file references the zwp_tablet_tool_v2 + * interface object. Since we don't currently use the tablet protocol, + * provide a dummy object here to avoid linking errors. */ +void *zwp_tablet_tool_v2_interface = NULL; + +struct system_cursors +{ + WORD id; + enum wp_cursor_shape_device_v1_shape shape; +}; + +static const struct system_cursors user32_cursors[] = +{ + {OCR_NORMAL, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT}, + {OCR_IBEAM, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT}, + {OCR_WAIT, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT}, + {OCR_CROSS, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR}, + {OCR_SIZE, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE}, + {OCR_SIZENWSE, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE}, + {OCR_SIZENESW, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE}, + {OCR_SIZEWE, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE}, + {OCR_SIZENS, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE}, + {OCR_SIZEALL, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE}, + {OCR_NO, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED}, + {OCR_HAND, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER}, + {OCR_APPSTARTING, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS}, + {OCR_HELP, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP}, + {OCR_RDR2DIM, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL}, + {0} +}; + +static const struct system_cursors comctl32_cursors[] = +{ + {102, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE}, + {104, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY}, + {105, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT}, + {106, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE}, + {107, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE}, + {108, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER}, + {135, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE}, + {0} +}; + +static const struct system_cursors ole32_cursors[] = +{ + {1, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP}, + {2, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE}, + {3, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY}, + {4, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS}, + {0} +}; + +static const struct system_cursors riched20_cursors[] = +{ + {105, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER}, + {109, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY}, + {110, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE}, + {111, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP}, + {0} +}; + +static const struct +{ + const struct system_cursors *cursors; + WCHAR name[16]; +} module_cursors[] = +{ + {user32_cursors, {'u','s','e','r','3','2','.','d','l','l',0}}, + {comctl32_cursors, {'c','o','m','c','t','l','3','2','.','d','l','l',0}}, + {ole32_cursors, {'o','l','e','3','2','.','d','l','l',0}}, + {riched20_cursors, {'r','i','c','h','e','d','2','0','.','d','l','l',0}} +}; + static HWND wayland_pointer_get_focused_hwnd(void) { struct wayland_pointer *pointer = &process_wayland.pointer; @@ -350,6 +425,11 @@ void wayland_pointer_deinit(void) zwp_relative_pointer_v1_destroy(pointer->zwp_relative_pointer_v1); pointer->zwp_relative_pointer_v1 = NULL; } + if (pointer->wp_cursor_shape_device_v1) + { + wp_cursor_shape_device_v1_destroy(pointer->wp_cursor_shape_device_v1); + pointer->wp_cursor_shape_device_v1 = NULL; + } wl_pointer_release(pointer->wl_pointer); pointer->wl_pointer = NULL; pointer->focused_hwnd = NULL; @@ -509,6 +589,27 @@ clear_cursor: } }
+static void wayland_pointer_clear_cursor_surface(void) +{ + struct wayland_cursor *cursor = &process_wayland.pointer.cursor; + + if (cursor->wp_viewport) + { + wp_viewport_destroy(cursor->wp_viewport); + cursor->wp_viewport = NULL; + } + if (cursor->wl_surface) + { + wl_surface_destroy(cursor->wl_surface); + cursor->wl_surface = NULL; + } + if (cursor->shm_buffer) + { + wayland_shm_buffer_unref(cursor->shm_buffer); + cursor->shm_buffer = NULL; + } +} + static void wayland_pointer_update_cursor_surface(double scale) { struct wayland_cursor *cursor = &process_wayland.pointer.cursor; @@ -557,21 +658,7 @@ static void wayland_pointer_update_cursor_surface(double scale) return;
clear_cursor: - if (cursor->shm_buffer) - { - wayland_shm_buffer_unref(cursor->shm_buffer); - cursor->shm_buffer = NULL; - } - if (cursor->wp_viewport) - { - wp_viewport_destroy(cursor->wp_viewport); - cursor->wp_viewport = NULL; - } - if (cursor->wl_surface) - { - wl_surface_destroy(cursor->wl_surface); - cursor->wl_surface = NULL; - } + wayland_pointer_clear_cursor_surface(); }
static void reapply_cursor_clipping(void) @@ -582,6 +669,76 @@ static void reapply_cursor_clipping(void) NtUserSetThreadDpiAwarenessContext(context); }
+static enum wp_cursor_shape_device_v1_shape cursor_shape_from_info(ICONINFOEXW *info, + uint32_t proto_version) +{ + const struct system_cursors *cursors; + const WCHAR *module; + unsigned int i; + enum wp_cursor_shape_device_v1_shape shape = 0; + + if (!info->szModName[0]) return 0; + if ((module = wcsrchr(info->szModName, '\'))) module++; + else module = info->szModName; + for (i = 0; i < ARRAY_SIZE(module_cursors); i++) + if (!wcsicmp(module, module_cursors[i].name)) break; + if (i == ARRAY_SIZE(module_cursors)) return 0; + + cursors = module_cursors[i].cursors; + for (i = 0; cursors[i].id; i++) + { + if (cursors[i].id == info->wResID) + { + shape = cursors[i].shape; + break; + } + } + + if (shape >= WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK && proto_version < 2) + shape = 0; + + return shape; +} + +static BOOL wayland_pointer_set_cursor_shape(HCURSOR hcursor) +{ + struct wayland_pointer *pointer = &process_wayland.pointer; + ICONINFOEXW info = {0}; + enum wp_cursor_shape_device_v1_shape shape = 0; + uint32_t proto_version; + + if (!process_wayland.wp_cursor_shape_manager_v1) return FALSE; + if (!hcursor) return FALSE; + if (!get_icon_info(hcursor, &info)) return FALSE; + proto_version = wp_cursor_shape_manager_v1_get_version( + process_wayland.wp_cursor_shape_manager_v1); + if (!(shape = cursor_shape_from_info(&info, proto_version))) return FALSE; + + if (!pointer->wp_cursor_shape_device_v1) + { + pointer->wp_cursor_shape_device_v1 = + wp_cursor_shape_manager_v1_get_pointer( + process_wayland.wp_cursor_shape_manager_v1, pointer->wl_pointer); + if (!pointer->wp_cursor_shape_device_v1) return FALSE; + } + + wp_cursor_shape_device_v1_set_shape(pointer->wp_cursor_shape_device_v1, + pointer->enter_serial, shape); + + return TRUE; +} + +static void wayland_pointer_clear_cursor_shape(void) +{ + struct wayland_pointer *pointer = &process_wayland.pointer; + + if (pointer->wp_cursor_shape_device_v1) + { + wp_cursor_shape_device_v1_destroy(pointer->wp_cursor_shape_device_v1); + pointer->wp_cursor_shape_device_v1 = NULL; + } +} + static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor) { struct wayland_pointer *pointer = &process_wayland.pointer; @@ -611,13 +768,22 @@ static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor) pthread_mutex_lock(&pointer->mutex); if (pointer->focused_hwnd == hwnd) { - if (use_hcursor) wayland_pointer_update_cursor_buffer(hcursor, scale); - wayland_pointer_update_cursor_surface(scale); - wl_pointer_set_cursor(pointer->wl_pointer, - pointer->enter_serial, - pointer->cursor.wl_surface, - pointer->cursor.hotspot_x, - pointer->cursor.hotspot_y); + if ((!use_hcursor && pointer->wp_cursor_shape_device_v1) || + (use_hcursor && hcursor && wayland_pointer_set_cursor_shape(hcursor))) + { + wayland_pointer_clear_cursor_surface(); + } + else + { + if (use_hcursor) wayland_pointer_update_cursor_buffer(hcursor, scale); + wayland_pointer_update_cursor_surface(scale); + wl_pointer_set_cursor(pointer->wl_pointer, + pointer->enter_serial, + pointer->cursor.wl_surface, + pointer->cursor.hotspot_x, + pointer->cursor.hotspot_y); + wayland_pointer_clear_cursor_shape(); + } wl_display_flush(process_wayland.wl_display); reapply_clip = TRUE; } @@ -700,6 +866,7 @@ static void wayland_pointer_update_constraint(struct wl_surface *wl_surface, { struct wayland_pointer *pointer = &process_wayland.pointer; BOOL needs_relative, needs_lock, needs_confine; + BOOL is_visible; static unsigned int once;
if (!process_wayland.zwp_pointer_constraints_v1) @@ -709,11 +876,12 @@ static void wayland_pointer_update_constraint(struct wl_surface *wl_surface, return; }
- needs_lock = wl_surface && (((confine_rect || covers_vscreen) && - !pointer->cursor.wl_surface) || force_lock) && + is_visible = pointer->cursor.wl_surface || pointer->wp_cursor_shape_device_v1; + needs_lock = wl_surface && + (((confine_rect || covers_vscreen) && !is_visible) || force_lock) && pointer->wl_pointer; - needs_confine = wl_surface && confine_rect && pointer->cursor.wl_surface && - !force_lock && pointer->wl_pointer; + needs_confine = wl_surface && confine_rect && is_visible && !force_lock && + pointer->wl_pointer;
if (!needs_confine && pointer->zwp_confined_pointer_v1) { @@ -795,8 +963,7 @@ static void wayland_pointer_update_constraint(struct wl_surface *wl_surface, return; }
- needs_relative = !pointer->cursor.wl_surface && - pointer->constraint_hwnd && + needs_relative = !is_visible && pointer->constraint_hwnd && pointer->constraint_hwnd == pointer->focused_hwnd;
if (needs_relative && !pointer->zwp_relative_pointer_v1) diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index e0be6970b05..6fdcbacc336 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -29,6 +29,7 @@ #include <wayland-client.h> #include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbregistry.h> +#include "cursor-shape-v1-client-protocol.h" #include "pointer-constraints-unstable-v1-client-protocol.h" #include "relative-pointer-unstable-v1-client-protocol.h" #include "text-input-unstable-v3-client-protocol.h" @@ -106,6 +107,7 @@ struct wayland_pointer struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1; struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1; struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1; + struct wp_cursor_shape_device_v1 *wp_cursor_shape_device_v1; HWND focused_hwnd; HWND constraint_hwnd; BOOL pending_warp; @@ -175,6 +177,7 @@ struct wayland struct zwlr_data_control_manager_v1 *zwlr_data_control_manager_v1; struct wl_data_device_manager *wl_data_device_manager; struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1; + struct wp_cursor_shape_manager_v1 *wp_cursor_shape_manager_v1; struct wayland_seat seat; struct wayland_keyboard keyboard; struct wayland_pointer pointer;
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Allow users to disable system cursor shapes through a driver option. --- dlls/winewayland.drv/wayland_pointer.c | 1 + dlls/winewayland.drv/waylanddrv.h | 1 + dlls/winewayland.drv/waylanddrv_main.c | 146 +++++++++++++++++++++++++ 3 files changed, 148 insertions(+)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index 070f7d8b77e..ea7dc05284e 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -707,6 +707,7 @@ static BOOL wayland_pointer_set_cursor_shape(HCURSOR hcursor) enum wp_cursor_shape_device_v1_shape shape = 0; uint32_t proto_version;
+ if (!option_use_system_cursors) return FALSE; if (!process_wayland.wp_cursor_shape_manager_v1) return FALSE; if (!hcursor) return FALSE; if (!get_icon_info(hcursor, &info)) return FALSE; diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 6fdcbacc336..b7854d718cb 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -57,6 +57,7 @@
extern char *process_name; extern struct wayland process_wayland; +extern BOOL option_use_system_cursors;
/********************************************************************** * Definitions for wayland types diff --git a/dlls/winewayland.drv/waylanddrv_main.c b/dlls/winewayland.drv/waylanddrv_main.c index 1c6a0d95d58..85ba49f15f5 100644 --- a/dlls/winewayland.drv/waylanddrv_main.c +++ b/dlls/winewayland.drv/waylanddrv_main.c @@ -31,7 +31,11 @@
#include "waylanddrv.h"
+#define IS_OPTION_TRUE(ch) \ + ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1') + char *process_name = NULL; +BOOL option_use_system_cursors = TRUE;
static const struct user_driver_funcs waylanddrv_funcs = { @@ -56,6 +60,147 @@ static const struct user_driver_funcs waylanddrv_funcs = .pOpenGLInit = WAYLAND_OpenGLInit, };
+static inline void ascii_to_unicode(WCHAR *dst, const char *src, size_t len) +{ + while (len--) *dst++ = (unsigned char)*src++; +} + +static inline UINT asciiz_to_unicode(WCHAR *dst, const char *src) +{ + WCHAR *p = dst; + while ((*p++ = *src++)); + return (p - dst) * sizeof(WCHAR); +} + +static HKEY reg_open_key(HKEY root, const WCHAR *name, ULONG name_len) +{ + UNICODE_STRING nameW = {name_len, name_len, (WCHAR *)name}; + OBJECT_ATTRIBUTES attr; + HANDLE ret; + + attr.Length = sizeof(attr); + attr.RootDirectory = root; + attr.ObjectName = &nameW; + attr.Attributes = 0; + attr.SecurityDescriptor = NULL; + attr.SecurityQualityOfService = NULL; + + return NtOpenKeyEx(&ret, MAXIMUM_ALLOWED, &attr, 0) ? 0 : ret; +} + +static HKEY open_hkcu_key(const char *name) +{ + WCHAR bufferW[256]; + static HKEY hkcu; + + if (!hkcu) + { + char buffer[256]; + DWORD_PTR sid_data[(sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE) / sizeof(DWORD_PTR)]; + DWORD i, len = sizeof(sid_data); + SID *sid; + + if (NtQueryInformationToken(GetCurrentThreadEffectiveToken(), TokenUser, sid_data, len, &len)) + return 0; + + sid = ((TOKEN_USER *)sid_data)->User.Sid; + len = sprintf(buffer, "\Registry\User\S-%u-%u", sid->Revision, + MAKELONG(MAKEWORD(sid->IdentifierAuthority.Value[5], + sid->IdentifierAuthority.Value[4]), + MAKEWORD(sid->IdentifierAuthority.Value[3], + sid->IdentifierAuthority.Value[2]))); + for (i = 0; i < sid->SubAuthorityCount; i++) + len += sprintf(buffer + len, "-%u", sid->SubAuthority[i]); + + ascii_to_unicode(bufferW, buffer, len); + hkcu = reg_open_key(NULL, bufferW, len * sizeof(WCHAR)); + } + + return reg_open_key(hkcu, bufferW, asciiz_to_unicode(bufferW, name) - sizeof(WCHAR)); +} + +static ULONG query_reg_value(HKEY hkey, const WCHAR *name, + KEY_VALUE_PARTIAL_INFORMATION *info, ULONG size) +{ + unsigned int name_size = name ? lstrlenW(name) * sizeof(WCHAR) : 0; + UNICODE_STRING nameW = {name_size, name_size, (WCHAR *)name}; + + if (NtQueryValueKey(hkey, &nameW, KeyValuePartialInformation, + info, size, &size)) + return 0; + + return size - FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data); +} + +static inline DWORD get_config_key(HKEY defkey, HKEY appkey, const char *name, + WCHAR *buffer, DWORD size) +{ + WCHAR nameW[128]; + char buf[2048]; + KEY_VALUE_PARTIAL_INFORMATION *info = (void *)buf; + + asciiz_to_unicode(nameW, name); + + if (appkey && query_reg_value(appkey, nameW, info, sizeof(buf))) + { + size = min(info->DataLength, size - sizeof(WCHAR)); + memcpy(buffer, info->Data, size); + buffer[size / sizeof(WCHAR)] = 0; + return 0; + } + + if (defkey && query_reg_value(defkey, nameW, info, sizeof(buf))) + { + size = min(info->DataLength, size - sizeof(WCHAR)); + memcpy(buffer, info->Data, size); + buffer[size / sizeof(WCHAR)] = 0; + return 0; + } + + return ERROR_FILE_NOT_FOUND; +} + +static void wayland_init_options(void) +{ + static const WCHAR waylanddriverW[] = {'\','W','a','y','l','a','n','d',' ','D','r','i','v','e','r',0}; + WCHAR buffer[MAX_PATH+16], *p, *appname; + HKEY hkey, appkey = 0; + DWORD len; + + /* @@ Wine registry key: HKCU\Software\Wine\Wayland Driver */ + hkey = open_hkcu_key("Software\Wine\Wayland Driver"); + + /* open the app-specific key */ + appname = NtCurrentTeb()->Peb->ProcessParameters->ImagePathName.Buffer; + if ((p = wcsrchr(appname, '/'))) appname = p + 1; + if ((p = wcsrchr(appname, '\'))) appname = p + 1; + len = lstrlenW(appname); + + if (len && len < MAX_PATH) + { + HKEY tmpkey; + int i; + for (i = 0; appname[i]; i++) buffer[i] = RtlDowncaseUnicodeChar(appname[i]); + buffer[i] = 0; + appname = buffer; + if ((process_name = malloc(len * 3 + 1))) + ntdll_wcstoumbs(appname, len + 1, process_name, len * 3 + 1, FALSE); + memcpy(appname + i, waylanddriverW, sizeof(waylanddriverW)); + /* @@ Wine registry key: HKCU\Software\Wine\AppDefaults\app.exe\Wayland Driver */ + if ((tmpkey = open_hkcu_key("Software\Wine\AppDefaults"))) + { + appkey = reg_open_key(tmpkey, appname, lstrlenW(appname) * sizeof(WCHAR)); + NtClose(tmpkey); + } + } + + if (!get_config_key(hkey, appkey, "UseSystemCursors", buffer, sizeof(buffer))) + option_use_system_cursors = IS_OPTION_TRUE(buffer[0]); + + NtClose(appkey); + NtClose(hkey); +} + static void wayland_init_process_name(void) { WCHAR *p, *appname; @@ -91,6 +236,7 @@ static NTSTATUS waylanddrv_unix_init(void *arg) __wine_set_user_driver(&waylanddrv_funcs, WINE_GDI_DRIVER_VERSION);
wayland_init_process_name(); + wayland_init_options();
if (!wayland_process_init()) goto err;
v5: Rebase on latest upstream.
@rbernon May I bother you to review this when you have some space cycles?
Sorry I missed that, please assign me as a reviewer whenever you need it.
Rémi Bernon (@rbernon) commented about dlls/winewayland.drv/waylanddrv_main.c:
- }
- if (defkey && query_reg_value(defkey, nameW, info, sizeof(buf)))
- {
size = min(info->DataLength, size - sizeof(WCHAR));
memcpy(buffer, info->Data, size);
buffer[size / sizeof(WCHAR)] = 0;
return 0;
- }
- return ERROR_FILE_NOT_FOUND;
+}
+static void wayland_init_options(void) +{
- static const WCHAR waylanddriverW[] = {'\','W','a','y','l','a','n','d',' ','D','r','i','v','e','r',0};
I don't think we want to introduce yet another driver specific key, when the trend is to move more things to win32u. Especially when the same option exists already under "X11 Driver".
The question might be: do we even need an option here? If really yes, and although a bit awkward I would suggest to use "X11 Driver" here too, similarly to how win32u does the same for options that are now usable on a wider choice of backends. The idea is at some point to change the name to something generic but I'm not sure how to do that properly.
Rémi Bernon (@rbernon) commented about dlls/winewayland.drv/wayland_pointer.c:
{ struct wayland_pointer *pointer = &process_wayland.pointer; BOOL needs_relative, needs_lock, needs_confine;
- BOOL is_visible;
```suggestion:-1+0 BOOL needs_relative, needs_lock, needs_confine, is_visible; ```
If really yes, and although a bit awkward I would suggest to use "X11 Driver" here too, similarly to how win32u does the same for options that are now usable on a wider choice of backends. The idea is at some point to change the name to something generic but I'm not sure how to do that properly.
It's too confusing for users. I think either open a new MR to add a generic key (leave "X11 Driver" key for backward compatibility) and then merge this MR after that one, or just add "Wayland Driver" key in this MR and add generic key in future (leave both "X11 Driver" and "Wayland Driver" keys for backward compatibility). It is inconceivable for users that you will need to modify a key under "X11 Driver" to control wayland behavior.
On Tue Jul 1 10:29:42 2025 +0000, Celeste Liu wrote:
If really yes, and although a bit awkward I would suggest to use "X11
Driver" here too, similarly to how win32u does the same for options that are now usable on a wider choice of backends. The idea is at some point to change the name to something generic but I'm not sure how to do that properly. It's too confusing for users. I think either open a new MR to add a generic key (leave "X11 Driver" key for backward compatibility) and then merge this MR after that one, or just add "Wayland Driver" key in this MR and add generic key in future (leave both "X11 Driver" and "Wayland Driver" keys for backward compatibility). It is inconceivable for users that you will need to modify a key under "X11 Driver" to control wayland behavior.
Well, the same is true for some other options already. But like I said first, I don't think that option is needed at all.
On Tue Jul 1 10:50:40 2025 +0000, Rémi Bernon wrote:
Well, the same is true for some other options already. But like I said first, I don't think that option is needed at all.
I included the option mainly because winex11 also has it, so I assumed it must be useful for some people. Perhaps it's a matter of preferring a more consistent look within the win32 app (rather than better integration with the native desktop environment). In any case, given the concerns around the option (including registry key naming, but also the duplication of the code to query it), I am fine leaving it out of this MR and revisiting as needed (if there are strong functional reasons to have the option let me know).
On Tue Jul 1 11:57:36 2025 +0000, Alexandros Frantzis wrote:
I included the option mainly because winex11 also has it, so I assumed it must be useful for some people. Perhaps it's a matter of preferring a more consistent look within the win32 app (rather than better integration with the native desktop environment). In any case, given the concerns around the option (including registry key naming, but also the duplication of the code to query it), I am fine leaving it out of this MR and revisiting as needed (if there are strong functional reasons to have the option let me know).
There is no evidence that the option is useful, it's undocumented, it doesn't reference any bug either and no bug seem to reference it either. I think it has been added when the system cursor were first implemented, in case it broke something, but now it should probably be removed from winex11 too.
On Tue Jul 1 12:01:23 2025 +0000, Rémi Bernon wrote:
There is no evidence that the option is useful, it's undocumented, it doesn't reference any bug either and no bug seem to reference it either. I think it has been added when the system cursor were first implemented, in case it broke something, but now it should probably be removed from winex11 too.
It was useful for some old apps, like PaintBrush that uses custom cursors depending on the paint tool that you select.
At this point it's fine to not implement the option and wait to see if users request it.