File wayland-proto-31-cursor-shape.patch of Package nodejs-electron

From 653716a6838676b6cf257e0d9756eccbf509fabb Mon Sep 17 00:00:00 2001
From: Ilya Bizyaev <ilyabiz@chromium.org>
Date: Fri, 19 Jan 2024 02:02:03 +0000
Subject: [PATCH] Implement cursor-shape-v1
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This CL is largely based on the existing support for the custom
zcr-cursor-shapes-v1 protocol used by Lacros.

cursor-shape-v1 is the new upstream protocol for server-side cursor shapes
based on zcr-cursor-shapes-v1. Of compositors, it is currently
implemented in KDE's KWin (in Plasma 6.0) and Sway (not yet released).

The enum mapping is based on
https://source.chromium.org/chromium/chromium/src/+/main:ui/base/cursor/cursor_factory.cc;l=115;drc=67d90538f11c6b232dbfd716075db52aeb34fd15
— despite the seemingly fewer shapes in cursor-shape-v1's list, the
reduction is the same as in Chromium's own FreeDesktop cursor loading code,
so there's no downside in letting the compositor do the work for us
whenever it offers to. Since the compositor — and, by extension, the
desktop environment — are the source of truth for cursor display
settings, this simplifies complying with the user's preferences.

Unlike Lacros, we cannot be sure at compile time that the compositor has
support for this protocol, so the cursor loading code is changed to not
give up on a shape when is has no bitmap and instead let the update
code try the server-side approach.

Exo does not support cursor-shape-v1, so it will keep using the
zcr-cursor-shapes-v1 branch.

I have tested this CL in a Plasma 6 RC1 session with this CSS demo:
https://www.tutorialrepublic.com/codelab.php?topic=css&file=cursor-property

Low-Coverage-Reason: TRIVIAL_CHANGE Not tested are the enum conversion and getters/setters, which is boilerplate.
Bug: 1427111
Cq-Include-Trybots: luci.chromium.try:linux-lacros-rel,linux-wayland-rel
Change-Id: I50567021e6fd80a641bbc906370c517efe16ffb9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5196950
Commit-Queue: Ilya Bizyaev <ilyabiz@chromium.org>
Reviewed-by: Henrique Ferreiro <hferreiro@igalia.com>
Reviewed-by: Kramer Ge <fangzhoug@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1249191}
---
 third_party/wayland-protocols/BUILD.gn        |   4 +
 ui/ozone/common/bitmap_cursor_factory.cc      |  90 +-------
 .../common/bitmap_cursor_factory_unittest.cc  |  12 +-
 ui/ozone/platform/wayland/BUILD.gn            |   5 +
 .../platform/wayland/common/wayland_object.cc |   3 +
 .../platform/wayland/common/wayland_object.h  |   2 +
 .../wayland/host/wayland_connection.cc        |   3 +
 .../wayland/host/wayland_connection.h         |   7 +
 .../host/wayland_connection_test_api.h        |   5 +
 .../wayland/host/wayland_cursor_shape.cc      | 194 ++++++++++++++++++
 .../wayland/host/wayland_cursor_shape.h       |  53 +++++
 .../platform/wayland/host/wayland_window.cc   |  18 +-
 .../wayland/host/wayland_window_unittest.cc   |  76 +++++++
 .../wayland/host/wayland_zcr_cursor_shapes.cc |   2 -
 14 files changed, 371 insertions(+), 103 deletions(-)
 create mode 100644 ui/ozone/platform/wayland/host/wayland_cursor_shape.cc
 create mode 100644 ui/ozone/platform/wayland/host/wayland_cursor_shape.h

diff --git a/third_party/wayland-protocols/BUILD.gn b/third_party/wayland-protocols/BUILD.gn
index b0c0082769055..db15b95e41dd4 100644
--- a/third_party/wayland-protocols/BUILD.gn
+++ b/third_party/wayland-protocols/BUILD.gn
@@ -15,6 +15,10 @@ wayland_protocol("content_type_protocol") {
   sources = [ "unstable/content-type/content-type-v1.xml" ]
 }
 
+wayland_protocol("cursor_shape_protocol") {
+  sources = [ "src/staging/cursor-shape/cursor-shape-v1.xml" ]
+}
+
 wayland_protocol("cursor_shapes_protocol") {
   sources = [ "unstable/cursor-shapes/cursor-shapes-unstable-v1.xml" ]
 }
diff --git a/ui/ozone/common/bitmap_cursor_factory.cc b/ui/ozone/common/bitmap_cursor_factory.cc
index cf9bb8655a9cb..fd7274ccd28e4 100644
--- a/ui/ozone/common/bitmap_cursor_factory.cc
+++ b/ui/ozone/common/bitmap_cursor_factory.cc
@@ -8,84 +8,11 @@
 
 #include "base/check_op.h"
 #include "base/memory/scoped_refptr.h"
-#include "build/chromeos_buildflags.h"
 #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
 #include "ui/ozone/common/bitmap_cursor.h"
 
 namespace ui {
 
-namespace {
-
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-// Returns true if ozone should use the default cursor for |type|, instead of
-// loading and storing bitmaps for it. Used on Lacros to skip client-side bitmap
-// loading for server-side cursors.
-bool UseDefaultCursorForType(mojom::CursorType type) {
-  using mojom::CursorType;
-  switch (type) {
-    case CursorType::kNull:
-    case CursorType::kPointer:
-    case CursorType::kCross:
-    case CursorType::kHand:
-    case CursorType::kIBeam:
-    case CursorType::kWait:
-    case CursorType::kHelp:
-    case CursorType::kEastResize:
-    case CursorType::kNorthResize:
-    case CursorType::kNorthEastResize:
-    case CursorType::kNorthWestResize:
-    case CursorType::kSouthResize:
-    case CursorType::kSouthEastResize:
-    case CursorType::kSouthWestResize:
-    case CursorType::kWestResize:
-    case CursorType::kNorthSouthResize:
-    case CursorType::kEastWestResize:
-    case CursorType::kNorthEastSouthWestResize:
-    case CursorType::kNorthWestSouthEastResize:
-    case CursorType::kColumnResize:
-    case CursorType::kRowResize:
-    case CursorType::kMiddlePanning:
-    case CursorType::kEastPanning:
-    case CursorType::kNorthPanning:
-    case CursorType::kNorthEastPanning:
-    case CursorType::kNorthWestPanning:
-    case CursorType::kSouthPanning:
-    case CursorType::kSouthEastPanning:
-    case CursorType::kSouthWestPanning:
-    case CursorType::kWestPanning:
-    case CursorType::kMove:
-    case CursorType::kVerticalText:
-    case CursorType::kCell:
-    case CursorType::kContextMenu:
-    case CursorType::kAlias:
-    case CursorType::kProgress:
-    case CursorType::kNoDrop:
-    case CursorType::kCopy:
-    case CursorType::kNotAllowed:
-    case CursorType::kZoomIn:
-    case CursorType::kZoomOut:
-    case CursorType::kGrab:
-    case CursorType::kGrabbing:
-    case CursorType::kDndNone:
-    case CursorType::kDndMove:
-    case CursorType::kDndCopy:
-    case CursorType::kDndLink:
-      return true;
-    case CursorType::kNone:
-    case CursorType::kMiddlePanningVertical:
-    case CursorType::kMiddlePanningHorizontal:
-    case CursorType::kCustom:
-    case CursorType::kEastWestNoResize:
-    case CursorType::kNorthEastSouthWestNoResize:
-    case CursorType::kNorthSouthNoResize:
-    case CursorType::kNorthWestSouthEastNoResize:
-      return false;
-  }
-}
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
-}  // namespace
-
 BitmapCursorFactory::BitmapCursorFactory() = default;
 
 BitmapCursorFactory::~BitmapCursorFactory() = default;
@@ -93,18 +20,11 @@ BitmapCursorFactory::~BitmapCursorFactory() = default;
 scoped_refptr<PlatformCursor> BitmapCursorFactory::GetDefaultCursor(
     mojom::CursorType type) {
   if (!default_cursors_.count(type)) {
-    if (type == mojom::CursorType::kNone
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-        || UseDefaultCursorForType(type)
-#endif
-    ) {
-      // Lacros uses server-side cursors for most types. These cursors don't
-      // need to load bitmap images on the client.
-      // Similarly, the hidden cursor doesn't use any bitmap.
-      default_cursors_[type] = base::MakeRefCounted<BitmapCursor>(type);
-    } else {
-      return nullptr;
-    }
+    // Return a cursor not backed by a bitmap to preserve the type information.
+    // It can still be used to request the compositor to draw a server-side
+    // cursor for the given type.
+    // kNone is handled separately and does not need a bitmap.
+    default_cursors_[type] = base::MakeRefCounted<BitmapCursor>(type);
   }
 
   return default_cursors_[type];
diff --git a/ui/ozone/common/bitmap_cursor_factory_unittest.cc b/ui/ozone/common/bitmap_cursor_factory_unittest.cc
index 905b721d66193..6428821d90c6b 100644
--- a/ui/ozone/common/bitmap_cursor_factory_unittest.cc
+++ b/ui/ozone/common/bitmap_cursor_factory_unittest.cc
@@ -4,7 +4,6 @@
 
 #include "ui/ozone/common/bitmap_cursor_factory.h"
 
-#include "build/chromeos_buildflags.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
 #include "ui/base/cursor/platform_cursor.h"
@@ -24,8 +23,7 @@ TEST(BitmapCursorFactoryTest, InvisibleCursor) {
             CursorType::kNone);
 }
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-TEST(BitmapCursorFactoryTest, LacrosUsesDefaultCursorsForCommonTypes) {
+TEST(BitmapCursorFactoryTest, DefaultCursorsHaveTypeInformation) {
   BitmapCursorFactory factory;
 
   // Verify some common cursor types.
@@ -45,12 +43,4 @@ TEST(BitmapCursorFactoryTest, LacrosUsesDefaultCursorsForCommonTypes) {
             CursorType::kIBeam);
 }
 
-TEST(BitmapCursorFactoryTest, LacrosCustomCursor) {
-  BitmapCursorFactory factory;
-  auto cursor = factory.GetDefaultCursor(CursorType::kCustom);
-  // Custom cursors don't have a default platform cursor.
-  EXPECT_EQ(cursor, nullptr);
-}
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index 8ebc966e1e1c3..d3dc3c736aa3c 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -103,6 +103,8 @@ source_set("wayland") {
     "host/wayland_cursor.h",
     "host/wayland_cursor_position.cc",
     "host/wayland_cursor_position.h",
+    "host/wayland_cursor_shape.cc",
+    "host/wayland_cursor_shape.h",
     "host/wayland_data_device.cc",
     "host/wayland_data_device.h",
     "host/wayland_data_device_base.cc",
@@ -256,6 +258,7 @@ source_set("wayland") {
     "//third_party/wayland:wayland_util",
     "//third_party/wayland-protocols:alpha_compositing_protocol",
     "//third_party/wayland-protocols:content_type_protocol",
+    "//third_party/wayland-protocols:cursor_shape_protocol",
     "//third_party/wayland-protocols:cursor_shapes_protocol",
     "//third_party/wayland-protocols:extended_drag",
     "//third_party/wayland-protocols:fractional_scale_protocol",
@@ -274,6 +277,7 @@ source_set("wayland") {
     "//third_party/wayland-protocols:relative_pointer_protocol",
     "//third_party/wayland-protocols:single_pixel_buffer",
     "//third_party/wayland-protocols:stylus_protocol",
+    "//third_party/wayland-protocols:tablet_protocol",
     "//third_party/wayland-protocols:text_input_extension_protocol",
     "//third_party/wayland-protocols:text_input_protocol",
     "//third_party/wayland-protocols:touchpad_haptics_protocol",
@@ -623,6 +627,7 @@ source_set("wayland_unittests") {
     "//testing/gmock",
     "//testing/gtest",
     "//third_party/wayland:wayland_server",
+    "//third_party/wayland-protocols:cursor_shape_protocol",
     "//third_party/wayland-protocols:cursor_shapes_protocol",
     "//third_party/wayland-protocols:keyboard_extension_protocol",
     "//third_party/wayland-protocols:linux_dmabuf_protocol",
diff --git a/ui/ozone/platform/wayland/common/wayland_object.cc b/ui/ozone/platform/wayland/common/wayland_object.cc
index bcc48aaeb186e..7f09a9f4f67dd 100644
--- a/ui/ozone/platform/wayland/common/wayland_object.cc
+++ b/ui/ozone/platform/wayland/common/wayland_object.cc
@@ -8,6 +8,7 @@
 #include <aura-shell-client-protocol.h>
 #include <chrome-color-management-client-protocol.h>
 #include <content-type-v1-client-protocol.h>
+#include <cursor-shape-v1-client-protocol.h>
 #include <cursor-shapes-unstable-v1-client-protocol.h>
 #include <extended-drag-unstable-v1-client-protocol.h>
 #include <fractional-scale-v1-client-protocol.h>
@@ -228,6 +229,8 @@ IMPLEMENT_WAYLAND_OBJECT_TRAITS(wp_viewport)
 IMPLEMENT_WAYLAND_OBJECT_TRAITS(wp_viewporter)
 IMPLEMENT_WAYLAND_OBJECT_TRAITS(wp_content_type_v1)
 IMPLEMENT_WAYLAND_OBJECT_TRAITS(wp_content_type_manager_v1)
+IMPLEMENT_WAYLAND_OBJECT_TRAITS(wp_cursor_shape_device_v1)
+IMPLEMENT_WAYLAND_OBJECT_TRAITS(wp_cursor_shape_manager_v1)
 IMPLEMENT_WAYLAND_OBJECT_TRAITS(wp_fractional_scale_manager_v1)
 IMPLEMENT_WAYLAND_OBJECT_TRAITS(wp_fractional_scale_v1)
 IMPLEMENT_WAYLAND_OBJECT_TRAITS(xdg_activation_v1)
diff --git a/ui/ozone/platform/wayland/common/wayland_object.h b/ui/ozone/platform/wayland/common/wayland_object.h
index c84c084841ea9..436e7ff5f3910 100644
--- a/ui/ozone/platform/wayland/common/wayland_object.h
+++ b/ui/ozone/platform/wayland/common/wayland_object.h
@@ -146,6 +146,8 @@ DECLARE_WAYLAND_OBJECT_TRAITS(wp_viewport)
 DECLARE_WAYLAND_OBJECT_TRAITS(wp_viewporter)
 DECLARE_WAYLAND_OBJECT_TRAITS(wp_content_type_manager_v1)
 DECLARE_WAYLAND_OBJECT_TRAITS(wp_content_type_v1)
+DECLARE_WAYLAND_OBJECT_TRAITS(wp_cursor_shape_device_v1)
+DECLARE_WAYLAND_OBJECT_TRAITS(wp_cursor_shape_manager_v1)
 DECLARE_WAYLAND_OBJECT_TRAITS(wp_fractional_scale_manager_v1)
 DECLARE_WAYLAND_OBJECT_TRAITS(wp_fractional_scale_v1)
 DECLARE_WAYLAND_OBJECT_TRAITS(xdg_activation_v1)
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.cc b/ui/ozone/platform/wayland/host/wayland_connection.cc
index 5c62c0ead1497..944c9dd6904cd 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.cc
+++ b/ui/ozone/platform/wayland/host/wayland_connection.cc
@@ -41,6 +41,7 @@
 #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
 #include "ui/ozone/platform/wayland/host/wayland_cursor.h"
 #include "ui/ozone/platform/wayland/host/wayland_cursor_position.h"
+#include "ui/ozone/platform/wayland/host/wayland_cursor_shape.h"
 #include "ui/ozone/platform/wayland/host/wayland_data_device_manager.h"
 #include "ui/ozone/platform/wayland/host/wayland_drm.h"
 #include "ui/ozone/platform/wayland/host/wayland_event_source.h"
@@ -165,6 +166,8 @@ bool WaylandConnection::Initialize(bool use_threaded_polling) {
     RegisterGlobalObjectFactory(WaylandZcrColorManager::kInterfaceName,
                                 &WaylandZcrColorManager::Instantiate);
   }
+  RegisterGlobalObjectFactory(WaylandCursorShape::kInterfaceName,
+                              &WaylandCursorShape::Instantiate);
   RegisterGlobalObjectFactory(WaylandZcrCursorShapes::kInterfaceName,
                               &WaylandZcrCursorShapes::Instantiate);
   RegisterGlobalObjectFactory(WaylandZcrTouchpadHaptics::kInterfaceName,
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.h b/ui/ozone/platform/wayland/host/wayland_connection.h
index b0bb930a3de99..13be193482721 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.h
+++ b/ui/ozone/platform/wayland/host/wayland_connection.h
@@ -62,6 +62,7 @@ class WaylandZwpPointerGestures;
 class WaylandZwpRelativePointerManager;
 class WaylandDataDeviceManager;
 class WaylandCursorPosition;
+class WaylandCursorShape;
 class WaylandWindowDragController;
 class GtkPrimarySelectionDeviceManager;
 class GtkShell1;
@@ -209,6 +210,10 @@ class WaylandConnection {
     return zcr_color_manager_.get();
   }
 
+  WaylandCursorShape* wayland_cursor_shape() const {
+    return cursor_shape_.get();
+  }
+
   WaylandZcrCursorShapes* zcr_cursor_shapes() const {
     return zcr_cursor_shapes_.get();
   }
@@ -383,6 +388,7 @@ class WaylandConnection {
   friend class WaylandZwpPointerGestures;
   friend class WaylandZwpRelativePointerManager;
   friend class WaylandZcrColorManager;
+  friend class WaylandCursorShape;
   friend class WaylandZcrCursorShapes;
   friend class XdgActivation;
   friend class XdgForeignWrapper;
@@ -483,6 +489,7 @@ class WaylandConnection {
   std::unique_ptr<WaylandZAuraOutputManager> zaura_output_manager_;
   std::unique_ptr<WaylandZAuraShell> zaura_shell_;
   std::unique_ptr<WaylandZcrColorManager> zcr_color_manager_;
+  std::unique_ptr<WaylandCursorShape> cursor_shape_;
   std::unique_ptr<WaylandZcrCursorShapes> zcr_cursor_shapes_;
   std::unique_ptr<WaylandZcrTouchpadHaptics> zcr_touchpad_haptics_;
   std::unique_ptr<WaylandZwpPointerConstraints> zwp_pointer_constraints_;
diff --git a/ui/ozone/platform/wayland/host/wayland_connection_test_api.h b/ui/ozone/platform/wayland/host/wayland_connection_test_api.h
index 31e61aff6010b..419801f0257a3 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection_test_api.h
+++ b/ui/ozone/platform/wayland/host/wayland_connection_test_api.h
@@ -10,6 +10,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
+#include "ui/ozone/platform/wayland/host/wayland_cursor_shape.h"
 #include "ui/ozone/platform/wayland/host/wayland_zcr_cursor_shapes.h"
 
 namespace ui {
@@ -22,6 +23,10 @@ class WaylandConnectionTestApi {
   WaylandConnectionTestApi& operator=(const WaylandConnectionTestApi&) = delete;
   ~WaylandConnectionTestApi() = default;
 
+  void SetCursorShape(std::unique_ptr<WaylandCursorShape> obj) {
+    impl_->cursor_shape_ = std::move(obj);
+  }
+
   void SetZcrCursorShapes(std::unique_ptr<WaylandZcrCursorShapes> obj) {
     impl_->zcr_cursor_shapes_ = std::move(obj);
   }
diff --git a/ui/ozone/platform/wayland/host/wayland_cursor_shape.cc b/ui/ozone/platform/wayland/host/wayland_cursor_shape.cc
new file mode 100644
index 0000000000000..e9b6f0c678c03
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/wayland_cursor_shape.cc
@@ -0,0 +1,194 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/ozone/platform/wayland/host/wayland_cursor_shape.h"
+
+#include <cursor-shape-v1-client-protocol.h>
+
+#include "base/check.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/ozone/platform/wayland/host/wayland_connection.h"
+#include "ui/ozone/platform/wayland/host/wayland_pointer.h"
+#include "ui/ozone/platform/wayland/host/wayland_seat.h"
+
+namespace ui {
+
+namespace {
+constexpr uint32_t kMinVersion = 1;
+}
+
+using mojom::CursorType;
+
+// static
+constexpr char WaylandCursorShape::kInterfaceName[];
+
+// static
+void WaylandCursorShape::Instantiate(WaylandConnection* connection,
+                                     wl_registry* registry,
+                                     uint32_t name,
+                                     const std::string& interface,
+                                     uint32_t version) {
+  CHECK_EQ(interface, kInterfaceName) << "Expected \"" << kInterfaceName
+                                      << "\" but got \"" << interface << "\"";
+
+  if (connection->cursor_shape_ ||
+      !wl::CanBind(interface, version, kMinVersion, kMinVersion)) {
+    return;
+  }
+
+  auto cursor_shape_manager =
+      wl::Bind<wp_cursor_shape_manager_v1>(registry, name, kMinVersion);
+  if (!cursor_shape_manager) {
+    LOG(ERROR) << "Failed to bind wp_cursor_shape_manager_v1";
+    return;
+  }
+  connection->cursor_shape_ = std::make_unique<WaylandCursorShape>(
+      cursor_shape_manager.release(), connection);
+}
+
+WaylandCursorShape::WaylandCursorShape(wp_cursor_shape_manager_v1* cursor_shape,
+                                       WaylandConnection* connection)
+    : wp_cursor_shape_manager_v1_(cursor_shape), connection_(connection) {
+  // |wp_cursor_shape_manager_v1_| and |connection_| may be null in tests.
+}
+
+WaylandCursorShape::~WaylandCursorShape() = default;
+
+wp_cursor_shape_device_v1* WaylandCursorShape::GetShapeDevice() {
+  DCHECK(connection_->seat()->pointer());
+
+  if (!wp_cursor_shape_device_v1_.get()) {
+    wl_pointer* pointer = connection_->seat()->pointer()->wl_object();
+    wp_cursor_shape_device_v1_.reset(wp_cursor_shape_manager_v1_get_pointer(
+        wp_cursor_shape_manager_v1_.get(), pointer));
+  }
+  DCHECK(wp_cursor_shape_device_v1_);
+  return wp_cursor_shape_device_v1_.get();
+}
+
+// static
+absl::optional<uint32_t> WaylandCursorShape::ShapeFromType(CursorType type) {
+  switch (type) {
+    case CursorType::kNull:
+      // kNull is an alias for kPointer. Fall through.
+    case CursorType::kPointer:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;
+    case CursorType::kCross:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR;
+    case CursorType::kHand:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER;
+    case CursorType::kIBeam:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT;
+    case CursorType::kWait:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT;
+    case CursorType::kHelp:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP;
+    case CursorType::kEastResize:
+    case CursorType::kEastPanning:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE;
+    case CursorType::kNorthResize:
+    case CursorType::kNorthPanning:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE;
+    case CursorType::kNorthEastResize:
+    case CursorType::kNorthEastPanning:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE;
+    case CursorType::kNorthWestResize:
+    case CursorType::kNorthWestPanning:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE;
+    case CursorType::kSouthResize:
+    case CursorType::kSouthPanning:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE;
+    case CursorType::kSouthEastResize:
+    case CursorType::kSouthEastPanning:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE;
+    case CursorType::kSouthWestResize:
+    case CursorType::kSouthWestPanning:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE;
+    case CursorType::kWestResize:
+    case CursorType::kWestPanning:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE;
+    case CursorType::kNorthSouthResize:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE;
+    case CursorType::kEastWestResize:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE;
+    case CursorType::kNorthEastSouthWestResize:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE;
+    case CursorType::kNorthWestSouthEastResize:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE;
+    case CursorType::kColumnResize:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE;
+    case CursorType::kRowResize:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE;
+    case CursorType::kMove:
+      // Returning `MOVE` is the correct thing here, but Blink does not make a
+      // distinction between move and all-scroll. Other platforms use a cursor
+      // more consistent with all-scroll, so use that.
+    case CursorType::kMiddlePanning:
+    case CursorType::kMiddlePanningVertical:
+    case CursorType::kMiddlePanningHorizontal:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL;
+    case CursorType::kVerticalText:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT;
+    case CursorType::kCell:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL;
+    case CursorType::kContextMenu:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU;
+    case CursorType::kAlias:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS;
+    case CursorType::kProgress:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS;
+    case CursorType::kNoDrop:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP;
+    case CursorType::kCopy:
+    case CursorType::kDndCopy:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY;
+    case CursorType::kNone:
+      // To be cleared through wl_pointer.set_cursor.
+      return absl::nullopt;
+    case CursorType::kNotAllowed:
+    case CursorType::kNorthSouthNoResize:
+    case CursorType::kEastWestNoResize:
+    case CursorType::kNorthEastSouthWestNoResize:
+    case CursorType::kNorthWestSouthEastNoResize:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED;
+    case CursorType::kZoomIn:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN;
+    case CursorType::kZoomOut:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT;
+    case CursorType::kGrab:
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB;
+    case CursorType::kGrabbing:
+    case CursorType::kDndNone:
+    case CursorType::kDndMove:
+    case CursorType::kDndLink:
+      // For drag-and-drop, the compositor knows the drag type and can use it to
+      // additionally decorate the cursor.
+      return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING;
+    case CursorType::kCustom:
+      // "Custom" means a bitmap cursor, which cannot use the shape API.
+      return absl::nullopt;
+  }
+}
+
+void WaylandCursorShape::SetCursorShape(uint32_t shape) {
+  DCHECK(connection_->seat());
+
+  // Nothing to do if there is no pointer (mouse) connected.
+  if (!connection_->seat()->pointer()) {
+    return;
+  }
+
+  auto pointer_enter_serial =
+      connection_->serial_tracker().GetSerial(wl::SerialType::kMouseEnter);
+  if (!pointer_enter_serial) {
+    VLOG(1) << "Failed to set cursor shape: no mouse enter serial found.";
+    return;
+  }
+  wp_cursor_shape_device_v1_set_shape(GetShapeDevice(),
+                                      pointer_enter_serial->value, shape);
+}
+
+}  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_cursor_shape.h b/ui/ozone/platform/wayland/host/wayland_cursor_shape.h
new file mode 100644
index 0000000000000..9bb2fab21a539
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/wayland_cursor_shape.h
@@ -0,0 +1,53 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_CURSOR_SHAPE_H_
+#define UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_CURSOR_SHAPE_H_
+
+#include "base/memory/raw_ptr.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/base/cursor/mojom/cursor_type.mojom-forward.h"
+#include "ui/ozone/platform/wayland/common/wayland_object.h"
+
+namespace ui {
+
+class WaylandConnection;
+
+// Wraps the cursor_shape interface for Wayland server-side cursor support.
+class WaylandCursorShape
+    : public wl::GlobalObjectRegistrar<WaylandCursorShape> {
+ public:
+  static constexpr char kInterfaceName[] = "wp_cursor_shape_manager_v1";
+
+  static void Instantiate(WaylandConnection* connection,
+                          wl_registry* registry,
+                          uint32_t name,
+                          const std::string& interface,
+                          uint32_t version);
+
+  WaylandCursorShape(wp_cursor_shape_manager_v1* cursor_shape,
+                     WaylandConnection* connection);
+  WaylandCursorShape(const WaylandCursorShape&) = delete;
+  WaylandCursorShape& operator=(const WaylandCursorShape&) = delete;
+  virtual ~WaylandCursorShape();
+
+  // Returns the cursor shape value for a cursor |type|, or nullopt if the
+  // type isn't supported by Wayland's cursor shape API.
+  static absl::optional<uint32_t> ShapeFromType(mojom::CursorType type);
+
+  // Calls wp_cursor_shape_device_v1_set_shape(). See interface description
+  // for values for |shape|. Virtual for testing.
+  virtual void SetCursorShape(uint32_t shape);
+
+ private:
+  wp_cursor_shape_device_v1* GetShapeDevice();
+
+  const wl::Object<wp_cursor_shape_manager_v1> wp_cursor_shape_manager_v1_;
+  wl::Object<wp_cursor_shape_device_v1> wp_cursor_shape_device_v1_;
+  const raw_ptr<WaylandConnection> connection_;
+};
+
+}  // namespace ui
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_HOST_WAYLAND_CURSOR_SHAPE_H_
diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc
index 68f87776ef4f0..f5c36964ad36d 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -41,6 +41,7 @@
 #include "ui/ozone/platform/wayland/common/wayland_overlay_config.h"
 #include "ui/ozone/platform/wayland/host/dump_util.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
+#include "ui/ozone/platform/wayland/host/wayland_cursor_shape.h"
 #include "ui/ozone/platform/wayland/host/wayland_data_drag_controller.h"
 #include "ui/ozone/platform/wayland/host/wayland_event_source.h"
 #include "ui/ozone/platform/wayland/host/wayland_frame_manager.h"
@@ -1110,7 +1111,9 @@ void WaylandWindow::UpdateCursorShape(scoped_refptr<BitmapCursor> cursor) {
         base::IsValueInRangeForNumericType<int>(
             cursor->cursor_image_scale_factor()));
 
-  absl::optional<int32_t> shape =
+  absl::optional<uint32_t> shape =
+      WaylandCursorShape::ShapeFromType(cursor->type());
+  absl::optional<int32_t> zcr_shape =
       WaylandZcrCursorShapes::ShapeFromType(cursor->type());
 
   // Round cursor scale factor to ceil as wl_surface.set_buffer_scale accepts
@@ -1118,20 +1121,25 @@ void WaylandWindow::UpdateCursorShape(scoped_refptr<BitmapCursor> cursor) {
   if (cursor->type() == CursorType::kNone) {  // Hide the cursor.
     connection_->SetCursorBitmap(
         {}, gfx::Point(), std::ceil(cursor->cursor_image_scale_factor()));
+  } else if (connection_->wayland_cursor_shape() && shape.has_value()) {
+    // Prefer Wayland server-side cursor support, as the compositor knows better
+    // how to draw the cursor.
+    connection_->wayland_cursor_shape()->SetCursorShape(shape.value());
   } else if (cursor->platform_data()) {  // Check for theme-provided cursor.
     connection_->SetPlatformCursor(
         reinterpret_cast<wl_cursor*>(cursor->platform_data()),
         std::ceil(cursor->cursor_image_scale_factor()));
   } else if (connection_->zcr_cursor_shapes() &&
-             shape.has_value()) {  // Check for Wayland server-side cursor
-                                   // support (e.g. exo for lacros).
+             zcr_shape.has_value()) {  // Check for Exo server-side cursor
+                                       // support.
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
     // Lacros should not load image assets for default cursors. See
     // `BitmapCursorFactory::GetDefaultCursor()`.
     DCHECK(cursor->bitmaps().empty());
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
-    connection_->zcr_cursor_shapes()->SetCursorShape(shape.value());
-  } else {  // Use client-side bitmap cursors as fallback.
+    connection_->zcr_cursor_shapes()->SetCursorShape(zcr_shape.value());
+  } else if (!cursor->bitmaps()
+                  .empty()) {  // Use client-side bitmap cursors as fallback.
     // Translate physical pixels to DIPs.
     gfx::Point hotspot_in_dips = gfx::ScaleToRoundedPoint(
         cursor->hotspot(), 1.0f / cursor->cursor_image_scale_factor());
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index 49dcdfed360fe..7a1640bc6f045 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -11,6 +11,7 @@
 #include <utility>
 #include <vector>
 
+#include <cursor-shape-v1-client-protocol.h>
 #include <cursor-shapes-unstable-v1-client-protocol.h>
 #include <linux/input.h>
 #include <wayland-server-core.h>
@@ -51,6 +52,7 @@
 #include "ui/ozone/platform/wayland/common/wayland_util.h"
 #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection_test_api.h"
+#include "ui/ozone/platform/wayland/host/wayland_cursor_shape.h"
 #include "ui/ozone/platform/wayland/host/wayland_event_source.h"
 #include "ui/ozone/platform/wayland/host/wayland_output.h"
 #include "ui/ozone/platform/wayland/host/wayland_output_manager.h"
@@ -146,6 +148,16 @@ wl::ScopedWlArray MakeStateArray(const std::vector<int32_t> states) {
   return wl::ScopedWlArray(states);
 }
 
+class MockCursorShape : public WaylandCursorShape {
+ public:
+  MockCursorShape() : WaylandCursorShape(nullptr, nullptr) {}
+  MockCursorShape(const MockCursorShape&) = delete;
+  MockCursorShape& operator=(const MockCursorShape&) = delete;
+  ~MockCursorShape() override = default;
+
+  MOCK_METHOD(void, SetCursorShape, (uint32_t), (override));
+};
+
 class MockZcrCursorShapes : public WaylandZcrCursorShapes {
  public:
   MockZcrCursorShapes() : WaylandZcrCursorShapes(nullptr, nullptr) {}
@@ -264,6 +276,14 @@ class WaylandWindowTest : public WaylandTest {
     hit_tests->push_back(static_cast<int>(HTTOPRIGHT));
   }
 
+  MockCursorShape* InstallMockCursorShape() {
+    auto zcr_cursor_shapes = std::make_unique<MockCursorShape>();
+    MockCursorShape* mock_cursor_shapes = zcr_cursor_shapes.get();
+    WaylandConnectionTestApi test_api(connection_.get());
+    test_api.SetCursorShape(std::move(zcr_cursor_shapes));
+    return mock_cursor_shapes;
+  }
+
   MockZcrCursorShapes* InstallMockZcrCursorShapes() {
     auto zcr_cursor_shapes = std::make_unique<MockZcrCursorShapes>();
     MockZcrCursorShapes* mock_cursor_shapes = zcr_cursor_shapes.get();
@@ -1692,6 +1712,62 @@ TEST_P(WaylandWindowTest, CanDispatchMouseEventFocus) {
   EXPECT_TRUE(window_->CanDispatchEvent(&test_mouse_event_));
 }
 
+TEST_P(WaylandWindowTest, SetCursorUsesCursorShapeForCommonTypes) {
+  SetPointerFocusedWindow(window_.get());
+  MockCursorShape* mock_cursor_shape = InstallMockCursorShape();
+
+  // Verify some commonly-used cursors.
+  EXPECT_CALL(*mock_cursor_shape,
+              SetCursorShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT));
+  auto pointer_cursor = AsPlatformCursor(
+      base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kPointer));
+  window_->SetCursor(pointer_cursor.get());
+
+  EXPECT_CALL(*mock_cursor_shape,
+              SetCursorShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER));
+  auto hand_cursor = AsPlatformCursor(
+      base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kHand));
+  window_->SetCursor(hand_cursor.get());
+
+  EXPECT_CALL(*mock_cursor_shape,
+              SetCursorShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT));
+  auto ibeam_cursor = AsPlatformCursor(
+      base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kIBeam));
+  window_->SetCursor(ibeam_cursor.get());
+}
+
+TEST_P(WaylandWindowTest, SetCursorCallsCursorShapeOncePerCursor) {
+  SetPointerFocusedWindow(window_.get());
+  MockCursorShape* mock_cursor_shape = InstallMockCursorShape();
+  auto hand_cursor = AsPlatformCursor(
+      base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kHand));
+  // Setting the same cursor twice on the client only calls the server once.
+  EXPECT_CALL(*mock_cursor_shape, SetCursorShape(_)).Times(1);
+  window_->SetCursor(hand_cursor.get());
+  window_->SetCursor(hand_cursor.get());
+}
+
+TEST_P(WaylandWindowTest, SetCursorDoesNotUseCursorShapeForNoneCursor) {
+  SetPointerFocusedWindow(window_.get());
+  MockCursorShape* mock_cursor_shape = InstallMockCursorShape();
+  EXPECT_CALL(*mock_cursor_shape, SetCursorShape(_)).Times(0);
+  auto none_cursor = AsPlatformCursor(
+      base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kNone));
+  window_->SetCursor(none_cursor.get());
+}
+
+TEST_P(WaylandWindowTest, SetCursorDoesNotUseCursorShapeForCustomCursors) {
+  SetPointerFocusedWindow(window_.get());
+  MockCursorShape* mock_cursor_shape = InstallMockCursorShape();
+
+  // Custom cursors require bitmaps, so they do not use server-side cursors.
+  EXPECT_CALL(*mock_cursor_shape, SetCursorShape(_)).Times(0);
+  auto custom_cursor = AsPlatformCursor(
+      base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kCustom, SkBitmap(),
+                                         gfx::Point(), kDefaultCursorScale));
+  window_->SetCursor(custom_cursor.get());
+}
+
 TEST_P(WaylandWindowTest, SetCursorUsesZcrCursorShapesForCommonTypes) {
   SetPointerFocusedWindow(window_.get());
   MockZcrCursorShapes* mock_cursor_shapes = InstallMockZcrCursorShapes();
diff --git a/ui/ozone/platform/wayland/host/wayland_zcr_cursor_shapes.cc b/ui/ozone/platform/wayland/host/wayland_zcr_cursor_shapes.cc
index 9e3243dceea20..297c48e614087 100644
--- a/ui/ozone/platform/wayland/host/wayland_zcr_cursor_shapes.cc
+++ b/ui/ozone/platform/wayland/host/wayland_zcr_cursor_shapes.cc
@@ -168,8 +168,6 @@ absl::optional<int32_t> WaylandZcrCursorShapes::ShapeFromType(CursorType type) {
       return ZCR_CURSOR_SHAPES_V1_CURSOR_SHAPE_TYPE_DND_COPY;
     case CursorType::kDndLink:
       return ZCR_CURSOR_SHAPES_V1_CURSOR_SHAPE_TYPE_DND_LINK;
-      // NOTE: If you add a new cursor shape, please also update
-      // UseDefaultCursorForType() in bitmap_cursor_factory_ozone.cc.
   }
 }
 
openSUSE Build Service is sponsored by