Browse Source

Merge pull request #106251 from ArchercatNEO/wayland-cursor-shape

[Wayland] Implement the cursor-shape-v1 protocol
Thaddeus Crews 4 months ago
parent
commit
202b1176a4

+ 9 - 0
platform/linuxbsd/wayland/SCsub

@@ -46,6 +46,14 @@ env.NoCache(
         "protocol/viewporter.gen.h", "#thirdparty/wayland-protocols/stable/viewporter/viewporter.xml"
     ),
     env.WAYLAND_API_CODE("protocol/viewporter.gen.c", "#thirdparty/wayland-protocols/stable/viewporter/viewporter.xml"),
+    env.WAYLAND_API_HEADER(
+        "protocol/cursor_shape.gen.h",
+        "#thirdparty/wayland-protocols/staging/cursor-shape/cursor-shape-v1.xml",
+    ),
+    env.WAYLAND_API_CODE(
+        "protocol/cursor_shape.gen.c",
+        "#thirdparty/wayland-protocols/staging/cursor-shape/cursor-shape-v1.xml",
+    ),
     env.WAYLAND_API_HEADER(
         "protocol/fractional_scale.gen.h",
         "#thirdparty/wayland-protocols/staging/fractional-scale/fractional-scale-v1.xml",
@@ -158,6 +166,7 @@ env.NoCache(
 source_files = [
     "protocol/wayland.gen.c",
     "protocol/viewporter.gen.c",
+    "protocol/cursor_shape.gen.c",
     "protocol/fractional_scale.gen.c",
     "protocol/xdg_shell.gen.c",
     "protocol/xdg_system_bell.gen.c",

+ 48 - 0
platform/linuxbsd/wayland/wayland_thread.cpp

@@ -547,6 +547,12 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re
 		registry->wp_viewporter_name = name;
 	}
 
+	if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {
+		registry->wp_cursor_shape_manager = (struct wp_cursor_shape_manager_v1 *)wl_registry_bind(wl_registry, name, &wp_cursor_shape_manager_v1_interface, 1);
+		registry->wp_cursor_shape_manager_name = name;
+		return;
+	}
+
 	if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) {
 		registry->wp_fractional_scale_manager = (struct wp_fractional_scale_manager_v1 *)wl_registry_bind(wl_registry, name, &wp_fractional_scale_manager_v1_interface, 1);
 		registry->wp_fractional_scale_manager_name = name;
@@ -753,6 +759,25 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry
 		return;
 	}
 
+	if (name == registry->wp_cursor_shape_manager_name) {
+		if (registry->wp_cursor_shape_manager) {
+			wp_cursor_shape_manager_v1_destroy(registry->wp_cursor_shape_manager);
+			registry->wp_cursor_shape_manager = nullptr;
+		}
+
+		registry->wp_cursor_shape_manager_name = 0;
+
+		for (struct wl_seat *wl_seat : registry->wl_seats) {
+			SeatState *ss = wl_seat_get_seat_state(wl_seat);
+			ERR_FAIL_NULL(ss);
+
+			if (ss->wp_cursor_shape_device) {
+				wp_cursor_shape_device_v1_destroy(ss->wp_cursor_shape_device);
+				ss->wp_cursor_shape_device = nullptr;
+			}
+		}
+	}
+
 	if (name == registry->wp_fractional_scale_manager_name) {
 		for (KeyValue<DisplayServer::WindowID, WindowState> &pair : registry->wayland_thread->windows) {
 			WindowState ws = pair.value;
@@ -1454,6 +1479,10 @@ void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat
 			ss->wl_pointer = wl_seat_get_pointer(wl_seat);
 			wl_pointer_add_listener(ss->wl_pointer, &wl_pointer_listener, ss);
 
+			if (ss->registry->wp_cursor_shape_manager) {
+				ss->wp_cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(ss->registry->wp_cursor_shape_manager, ss->wl_pointer);
+			}
+
 			if (ss->registry->wp_relative_pointer_manager) {
 				ss->wp_relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(ss->registry->wp_relative_pointer_manager, ss->wl_pointer);
 				zwp_relative_pointer_v1_add_listener(ss->wp_relative_pointer, &wp_relative_pointer_listener, ss);
@@ -1485,6 +1514,11 @@ void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat
 			ss->wl_pointer = nullptr;
 		}
 
+		if (ss->wp_cursor_shape_device) {
+			wp_cursor_shape_device_v1_destroy(ss->wp_cursor_shape_device);
+			ss->wp_cursor_shape_device = nullptr;
+		}
+
 		if (ss->wp_relative_pointer) {
 			zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
 			ss->wp_relative_pointer = nullptr;
@@ -3330,6 +3364,12 @@ void WaylandThread::seat_state_update_cursor(SeatState *p_ss) {
 			// We can't really reasonably scale custom cursors, so we'll let the
 			// compositor do it for us (badly).
 			scale = 1;
+		} else if (thread->registry.wp_cursor_shape_manager) {
+			wp_cursor_shape_device_v1_shape wp_shape = thread->standard_cursors[shape];
+			wp_cursor_shape_device_v1_set_shape(p_ss->wp_cursor_shape_device, p_ss->pointer_enter_serial, wp_shape);
+
+			// We should avoid calling the `wl_pointer_set_cursor` at the end of this method.
+			return;
 		} else {
 			struct wl_cursor *wl_cursor = thread->wl_cursors[shape];
 
@@ -4892,6 +4932,10 @@ void WaylandThread::destroy() {
 			wl_data_device_destroy(ss->wl_data_device);
 		}
 
+		if (ss->wp_cursor_shape_device) {
+			wp_cursor_shape_device_v1_destroy(ss->wp_cursor_shape_device);
+		}
+
 		if (ss->wp_relative_pointer) {
 			zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
 		}
@@ -4959,6 +5003,10 @@ void WaylandThread::destroy() {
 		zxdg_decoration_manager_v1_destroy(registry.xdg_decoration_manager);
 	}
 
+	if (registry.wp_cursor_shape_manager) {
+		wp_cursor_shape_manager_v1_destroy(registry.wp_cursor_shape_manager);
+	}
+
 	if (registry.wp_fractional_scale_manager) {
 		wp_fractional_scale_manager_v1_destroy(registry.wp_fractional_scale_manager);
 	}

+ 30 - 1
platform/linuxbsd/wayland/wayland_thread.h

@@ -51,10 +51,11 @@
 // These must go after the Wayland client include to work properly.
 #include "wayland/protocol/idle_inhibit.gen.h"
 #include "wayland/protocol/primary_selection.gen.h"
-// These three protocol headers name wl_pointer method arguments as `pointer`,
+// These four protocol headers name wl_pointer method arguments as `pointer`,
 // which is the same name as X11's pointer typedef. This trips some very
 // annoying shadowing warnings. A `#define` works around this issue.
 #define pointer wl_pointer
+#include "wayland/protocol/cursor_shape.gen.h"
 #include "wayland/protocol/pointer_constraints.gen.h"
 #include "wayland/protocol/pointer_gestures.gen.h"
 #include "wayland/protocol/relative_pointer.gen.h"
@@ -187,6 +188,9 @@ public:
 		struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager = nullptr;
 		uint32_t wp_fractional_scale_manager_name = 0;
 
+		struct wp_cursor_shape_manager_v1 *wp_cursor_shape_manager = nullptr;
+		uint32_t wp_cursor_shape_manager_name = 0;
+
 		struct zxdg_decoration_manager_v1 *xdg_decoration_manager = nullptr;
 		uint32_t xdg_decoration_manager_name = 0;
 
@@ -416,6 +420,8 @@ public:
 
 		uint32_t pointer_enter_serial = 0;
 
+		struct wp_cursor_shape_device_v1 *wp_cursor_shape_device = nullptr;
+
 		struct zwp_relative_pointer_v1 *wp_relative_pointer = nullptr;
 		struct zwp_locked_pointer_v1 *wp_locked_pointer = nullptr;
 		struct zwp_confined_pointer_v1 *wp_confined_pointer = nullptr;
@@ -547,9 +553,32 @@ private:
 	// especially as usually screen scales don't change continuously.
 	int cursor_scale = 1;
 
+	// Use cursor-shape-v1 protocol if the compositor supports it.
+	wp_cursor_shape_device_v1_shape standard_cursors[DisplayServer::CURSOR_MAX] = {
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT, //CURSOR_ARROW
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT, //CURSOR_IBEAM
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER, //CURSOR_POINTING_HAND
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR, //CURSOR_CROSS
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT, //CURSOR_WAIT
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS, //CURSOR_BUSY
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB, //CURSOR_DRAG
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING, //CURSOR_CAN_DROP
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP, //CURSOR_FORBIDDEN
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE, //CURSOR_VSIZE
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE, //CURSOR_HSIZE
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE, //CURSOR_BDIAGSIZE
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE, //CURSOR_FDIAGSIZE
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE, //CURSOR_MOVE
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE, //CURSOR_VSPLIT
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE, //CURSOR_HSPLIT
+		wp_cursor_shape_device_v1_shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP, //CURSOR_HELP
+	};
+
+	// Fallback to reading $XCURSOR and system themes if the compositor does not.
 	struct wl_cursor_theme *wl_cursor_theme = nullptr;
 	struct wl_cursor *wl_cursors[DisplayServer::CURSOR_MAX] = {};
 
+	// User-defined cursor overrides. Take precedence over standard and wl cursors.
 	HashMap<DisplayServer::CursorShape, CustomCursor> custom_cursors;
 
 	DisplayServer::CursorShape cursor_shape = DisplayServer::CURSOR_ARROW;

+ 4 - 0
thirdparty/wayland-protocols/staging/cursor-shape/README

@@ -0,0 +1,4 @@
+cursor-shape protocol
+
+Maintainers:
+Simon Ser <[email protected]> (@emersion)

+ 162 - 0
thirdparty/wayland-protocols/staging/cursor-shape/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>