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. --- dlls/winewayland.drv/Makefile.in | 1 + dlls/winewayland.drv/cursor-shape-v1.xml | 162 +++++++++++++++++ dlls/winewayland.drv/wayland.c | 5 + dlls/winewayland.drv/wayland_pointer.c | 222 ++++++++++++++++++++--- dlls/winewayland.drv/waylanddrv.h | 3 + 5 files changed, 365 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 4141e36c9a8..d8062a440d4 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 7caf33c872e..5b4e1747180 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -189,6 +189,11 @@ static void registry_handle_global(void *data, struct wl_registry *registry, process_wayland.wl_data_device_manager = wl_registry_bind(registry, id, &wl_data_device_manager_interface, 2); } + 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, 1); + } }
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 457c3675cf1..95b8948bfbb 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; @@ -328,6 +403,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; @@ -579,6 +659,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; @@ -627,21 +728,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) @@ -652,6 +739,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; @@ -681,13 +838,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; } @@ -770,6 +936,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) @@ -779,10 +946,10 @@ 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); - needs_confine = wl_surface && confine_rect && 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); + needs_confine = wl_surface && confine_rect && is_visible && !force_lock;
if (!needs_confine && pointer->zwp_confined_pointer_v1) { @@ -864,8 +1031,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 065d4d31873..8deace46498 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" @@ -105,6 +106,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; @@ -168,6 +170,7 @@ struct wayland struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3; struct zwlr_data_control_manager_v1 *zwlr_data_control_manager_v1; struct wl_data_device_manager *wl_data_device_manager; + struct wp_cursor_shape_manager_v1 *wp_cursor_shape_manager_v1; struct wayland_seat seat; struct wayland_keyboard keyboard; struct wayland_pointer pointer;