summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Edmundson <davidedmundson@kde.org>2023-05-11 12:26:15 +0300
committerDavid Edmundson <davidedmundson@kde.org>2023-06-01 14:57:20 +0000
commitb1cae06cb65658dcddf01a596ea95cfd7c0ebdc2 (patch)
tree6b669599ad016d93a5f8b46613ed512f5672f474
parent9b3a347d6cc3bbc40071f245344b7bff9e6c2d96 (diff)
client: Implement cursor_shape_v1
A new wayland protocol allows clients to specify the cursor being used rather than load themes and attach buffers. Short term this has better behavior for scaling. Long term when this has universal compositor support we can drop the theme loading code. Change-Id: I119e1ca44d351e7b13b8ec56f2218d94b7da0705 Reviewed-by: Vlad Zahorodnii <vlad.zahorodnii@kde.org> Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
-rw-r--r--src/3rdparty/protocol/cursor-shape-v1.xml146
-rw-r--r--src/client/CMakeLists.txt1
-rw-r--r--src/client/qwaylandcursor.cpp72
-rw-r--r--src/client/qwaylandcursor_p.h9
-rw-r--r--src/client/qwaylanddisplay.cpp3
-rw-r--r--src/client/qwaylanddisplay_p.h5
-rw-r--r--src/client/qwaylandinputdevice.cpp9
-rw-r--r--src/client/qwaylandinputdevice_p.h2
-rw-r--r--tests/auto/client/CMakeLists.txt1
-rw-r--r--tests/auto/client/cursor/CMakeLists.txt11
-rw-r--r--tests/auto/client/cursor/cursorshapev1.cpp47
-rw-r--r--tests/auto/client/cursor/cursorshapev1.h44
-rw-r--r--tests/auto/client/cursor/tst_cursor.cpp89
-rw-r--r--tests/auto/client/shared/CMakeLists.txt1
-rw-r--r--tests/auto/client/shared/coreprotocol.cpp18
15 files changed, 450 insertions, 8 deletions
diff --git a/src/3rdparty/protocol/cursor-shape-v1.xml b/src/3rdparty/protocol/cursor-shape-v1.xml
new file mode 100644
index 000000000..b6fbe08b7
--- /dev/null
+++ b/src/3rdparty/protocol/cursor-shape-v1.xml
@@ -0,0 +1,146 @@
+<?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="1">
+ <description summary="cursor shape manager">
+ This global allows clients to set cursor images by name instead of
+ creating and attaching buffers.
+
+ 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.
+ </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.
+ </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="1">
+ <description summary="cursor shape for a device">
+ This interface advertises the list of supported cursor shapes for a
+ device, and 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
+ </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"/>
+ </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/src/client/CMakeLists.txt b/src/client/CMakeLists.txt
index 47312010f..d42259fc0 100644
--- a/src/client/CMakeLists.txt
+++ b/src/client/CMakeLists.txt
@@ -82,6 +82,7 @@ qt_internal_add_module(WaylandClient
qt6_generate_wayland_protocol_client_sources(WaylandClient
FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/cursor-shape-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/pointer-gestures-unstable-v1.xml
${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/tablet-unstable-v2.xml
${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/text-input-unstable-v1.xml
diff --git a/src/client/qwaylandcursor.cpp b/src/client/qwaylandcursor.cpp
index ec17ed218..83b240ce5 100644
--- a/src/client/qwaylandcursor.cpp
+++ b/src/client/qwaylandcursor.cpp
@@ -1,4 +1,5 @@
// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qwaylandcursor_p.h"
@@ -206,6 +207,77 @@ wl_cursor *QWaylandCursorTheme::requestCursor(WaylandCursor shape)
return waylandCursor;
}
+QWaylandCursorShape::QWaylandCursorShape(::wp_cursor_shape_device_v1 *object)
+ : QtWayland::wp_cursor_shape_device_v1(object)
+{}
+
+QWaylandCursorShape::~QWaylandCursorShape()
+{
+ destroy();
+}
+
+static QtWayland::wp_cursor_shape_device_v1::shape qtCursorShapeToWaylandShape(Qt::CursorShape cursorShape)
+{
+ using QtWayland::wp_cursor_shape_device_v1;
+
+ switch (cursorShape) {
+ case Qt::BlankCursor:
+ case Qt::CustomCursor:
+ case Qt::BitmapCursor:
+ // these should have been handled separately before using the shape protocol
+ Q_ASSERT(false);
+ break;
+ case Qt::ArrowCursor:
+ return wp_cursor_shape_device_v1::shape_default;
+ case Qt::SizeVerCursor:
+ return wp_cursor_shape_device_v1::shape_ns_resize;
+ case Qt::UpArrowCursor:
+ return wp_cursor_shape_device_v1::shape_n_resize;
+ case Qt::SizeHorCursor:
+ return wp_cursor_shape_device_v1::shape_ew_resize;
+ case Qt::CrossCursor:
+ return wp_cursor_shape_device_v1::shape_crosshair;
+ case Qt::SizeBDiagCursor:
+ return wp_cursor_shape_device_v1::shape_nesw_resize;
+ case Qt::IBeamCursor:
+ return wp_cursor_shape_device_v1::shape_text;
+ case Qt::SizeFDiagCursor:
+ return wp_cursor_shape_device_v1::shape_nwse_resize;
+ case Qt::WaitCursor:
+ return wp_cursor_shape_device_v1::shape_progress;
+ case Qt::SizeAllCursor:
+ return wp_cursor_shape_device_v1::shape_all_scroll;
+ case Qt::BusyCursor:
+ return wp_cursor_shape_device_v1::shape_wait;
+ case Qt::SplitVCursor:
+ return wp_cursor_shape_device_v1::shape_row_resize;
+ case Qt::ForbiddenCursor:
+ return wp_cursor_shape_device_v1::shape_not_allowed;
+ case Qt::SplitHCursor:
+ return wp_cursor_shape_device_v1::shape_col_resize;
+ case Qt::PointingHandCursor:
+ return wp_cursor_shape_device_v1::shape_pointer;
+ case Qt::OpenHandCursor:
+ return wp_cursor_shape_device_v1::shape_grab;
+ case Qt::WhatsThisCursor:
+ return wp_cursor_shape_device_v1::shape_help;
+ case Qt::ClosedHandCursor:
+ return wp_cursor_shape_device_v1::shape_grabbing;
+ case Qt::DragMoveCursor:
+ case Qt::DragCopyCursor:
+ case Qt::DragLinkCursor:
+ // drags on wayland are different, the compositor knows
+ // the drag type and can do something custom
+ return wp_cursor_shape_device_v1::shape_grab;
+ }
+ return wp_cursor_shape_device_v1::shape_default;
+}
+
+void QWaylandCursorShape::setShape(uint32_t serial, Qt::CursorShape shape)
+{
+ set_shape(serial, qtCursorShapeToWaylandShape(shape));
+}
+
QWaylandCursor::QWaylandCursor(QWaylandDisplay *display)
: mDisplay(display)
{
diff --git a/src/client/qwaylandcursor_p.h b/src/client/qwaylandcursor_p.h
index 2334c88d9..8f4a5b7e5 100644
--- a/src/client/qwaylandcursor_p.h
+++ b/src/client/qwaylandcursor_p.h
@@ -18,6 +18,7 @@
#include <qpa/qplatformcursor.h>
#include <QtCore/QMap>
#include <QtWaylandClient/qtwaylandclientglobal.h>
+#include <QtWaylandClient/private/qwayland-cursor-shape-v1.h>
#include <QtCore/private/qglobal_p.h>
#if QT_CONFIG(cursor)
@@ -87,6 +88,14 @@ protected:
wl_cursor *m_cursors[NumWaylandCursors] = {};
};
+class Q_WAYLANDCLIENT_EXPORT QWaylandCursorShape : public QtWayland::wp_cursor_shape_device_v1
+{
+public:
+ QWaylandCursorShape(struct ::wp_cursor_shape_device_v1 *object);
+ ~QWaylandCursorShape();
+ void setShape(uint32_t serial, Qt::CursorShape shape);
+};
+
class Q_WAYLANDCLIENT_EXPORT QWaylandCursor : public QPlatformCursor
{
public:
diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp
index 911004f8b..c49cb428a 100644
--- a/src/client/qwaylanddisplay.cpp
+++ b/src/client/qwaylanddisplay.cpp
@@ -52,6 +52,7 @@
#include <QtWaylandClient/private/qwayland-qt-text-input-method-unstable-v1.h>
#include <QtWaylandClient/private/qwayland-fractional-scale-v1.h>
#include <QtWaylandClient/private/qwayland-viewporter.h>
+#include <QtWaylandClient/private/qwayland-cursor-shape-v1.h>
#include <QtCore/private/qcore_unix_p.h>
@@ -755,6 +756,8 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin
mFractionalScaleManager.reset(new QtWayland::wp_fractional_scale_manager_v1(registry, id, 1));
} else if (interface == QLatin1String("wp_viewporter")) {
mViewporter.reset(new QtWayland::wp_viewporter(registry, id, qMin(1u, version)));
+ } else if (interface == QLatin1String(QtWayland::wp_cursor_shape_manager_v1::interface()->name)) {
+ mCursorShapeManager.reset(new QtWayland::wp_cursor_shape_manager_v1(registry, id, std::min(1u, version)));
}
mGlobals.append(RegistryGlobal(id, interface, version, registry));
diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h
index 93d4b1d90..877ff9692 100644
--- a/src/client/qwaylanddisplay_p.h
+++ b/src/client/qwaylanddisplay_p.h
@@ -51,8 +51,9 @@ namespace QtWayland {
class zwp_text_input_manager_v2;
class zwp_text_input_manager_v4;
class qt_text_input_method_manager_v1;
- class wp_viewporter;
+ class wp_cursor_shape_manager_v1;
class wp_fractional_scale_manager_v1;
+ class wp_viewporter;
}
namespace QtWaylandClient {
@@ -152,6 +153,7 @@ public:
QWaylandXdgOutputManagerV1 *xdgOutputManager() const { return mXdgOutputManager.data(); }
QtWayland::wp_fractional_scale_manager_v1 *fractionalScaleManager() const { return mFractionalScaleManager.data(); }
QtWayland::wp_viewporter *viewporter() const { return mViewporter.data(); }
+ QtWayland::wp_cursor_shape_manager_v1 *cursorShapeManager() const { return mCursorShapeManager.data();}
struct RegistryGlobal {
uint32_t id;
@@ -277,6 +279,7 @@ private:
QScopedPointer<QWaylandXdgOutputManagerV1> mXdgOutputManager;
QScopedPointer<QtWayland::wp_viewporter> mViewporter;
QScopedPointer<QtWayland::wp_fractional_scale_manager_v1> mFractionalScaleManager;
+ QScopedPointer<QtWayland::wp_cursor_shape_manager_v1> mCursorShapeManager;
int mFd = -1;
int mWritableNotificationFd = -1;
QList<RegistryGlobal> mGlobals;
diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp
index 24c86a0cf..b39e3a975 100644
--- a/src/client/qwaylandinputdevice.cpp
+++ b/src/client/qwaylandinputdevice.cpp
@@ -127,6 +127,10 @@ QWaylandInputDevice::Pointer::Pointer(QWaylandInputDevice *seat)
{
init(seat->get_pointer());
#if QT_CONFIG(cursor)
+ if (auto cursorShapeManager = seat->mQDisplay->cursorShapeManager()) {
+ mCursor.shape.reset(new QWaylandCursorShape(cursorShapeManager->get_pointer(object())));
+ }
+
mCursor.frameTimer.setSingleShot(true);
mCursor.frameTimer.callOnTimeout([&]() {
cursorTimerCallback();
@@ -303,6 +307,11 @@ void QWaylandInputDevice::Pointer::updateCursor()
return;
}
+ if (mCursor.shape) {
+ mCursor.shape->setShape(mEnterSerial, shape);
+ return;
+ }
+
if (!mCursor.theme || idealCursorScale() != mCursor.themeBufferScale)
updateCursorTheme();
diff --git a/src/client/qwaylandinputdevice_p.h b/src/client/qwaylandinputdevice_p.h
index 115b39a88..d41d885f3 100644
--- a/src/client/qwaylandinputdevice_p.h
+++ b/src/client/qwaylandinputdevice_p.h
@@ -64,6 +64,7 @@ class QWaylandTextInputInterface;
class QWaylandTextInputMethod;
#if QT_CONFIG(cursor)
class QWaylandCursorTheme;
+class QWaylandCursorShape;
class CursorSurface;
#endif
@@ -323,6 +324,7 @@ public:
uint32_t mEnterSerial = 0;
#if QT_CONFIG(cursor)
struct {
+ QScopedPointer<QWaylandCursorShape> shape;
QWaylandCursorTheme *theme = nullptr;
int themeBufferScale = 0;
QScopedPointer<CursorSurface> surface;
diff --git a/tests/auto/client/CMakeLists.txt b/tests/auto/client/CMakeLists.txt
index 5ae005eaa..79bcd442e 100644
--- a/tests/auto/client/CMakeLists.txt
+++ b/tests/auto/client/CMakeLists.txt
@@ -10,6 +10,7 @@ add_subdirectory(shared)
if (NOT WEBOS)
add_subdirectory(client)
add_subdirectory(clientextension)
+ add_subdirectory(cursor)
add_subdirectory(datadevicev1)
add_subdirectory(fullscreenshellv1)
add_subdirectory(iviapplication)
diff --git a/tests/auto/client/cursor/CMakeLists.txt b/tests/auto/client/cursor/CMakeLists.txt
new file mode 100644
index 000000000..93783994e
--- /dev/null
+++ b/tests/auto/client/cursor/CMakeLists.txt
@@ -0,0 +1,11 @@
+#####################################################################
+## tst_cursor Test:
+#####################################################################
+
+qt_internal_add_test(tst_cursor
+ SOURCES
+ tst_cursor.cpp
+ cursorshapev1.cpp
+ LIBRARIES
+ SharedClientTest
+)
diff --git a/tests/auto/client/cursor/cursorshapev1.cpp b/tests/auto/client/cursor/cursorshapev1.cpp
new file mode 100644
index 000000000..7fd93ed1d
--- /dev/null
+++ b/tests/auto/client/cursor/cursorshapev1.cpp
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "cursorshapev1.h"
+
+namespace MockCompositor {
+
+CursorShapeManager::CursorShapeManager(CoreCompositor *compositor, int version)
+ : QtWaylandServer::wp_cursor_shape_manager_v1(compositor->m_display, version)
+{
+}
+
+void CursorShapeManager::wp_cursor_shape_manager_v1_get_pointer(Resource *resource, uint32_t id, wl_resource *pointer)
+{
+ auto *p = fromResource<Pointer>(pointer);
+ auto *cursorShape = new CursorShapeDevice(p, resource->client(), id, resource->version());
+ connect(cursorShape, &QObject::destroyed, this, [this, cursorShape]() {
+ m_cursorDevices.removeOne(cursorShape);
+ });
+ m_cursorDevices << cursorShape;
+}
+
+CursorShapeDevice::CursorShapeDevice(Pointer *pointer, wl_client *client, int id, int version)
+ : QtWaylandServer::wp_cursor_shape_device_v1(client, id, version)
+ , m_pointer(pointer)
+{
+}
+
+void CursorShapeDevice::wp_cursor_shape_device_v1_destroy_resource(Resource *resource)
+{
+ Q_UNUSED(resource)
+ delete this;
+}
+
+void CursorShapeDevice::wp_cursor_shape_device_v1_destroy(Resource *resource)
+{
+ wl_resource_destroy(resource->handle);
+}
+
+void CursorShapeDevice::wp_cursor_shape_device_v1_set_shape(Resource *resource, uint32_t serial, uint32_t shape)
+{
+ Q_UNUSED(resource);
+ m_currentShape = static_cast<CursorShapeDevice::shape>(shape);
+ emit setCursor(serial);
+}
+
+}
diff --git a/tests/auto/client/cursor/cursorshapev1.h b/tests/auto/client/cursor/cursorshapev1.h
new file mode 100644
index 000000000..a8c2376ae
--- /dev/null
+++ b/tests/auto/client/cursor/cursorshapev1.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef MOCKCOMPOSITOR_CURSORSHAPE_H
+#define MOCKCOMPOSITOR_CURSORSHAPE_H
+
+#include "coreprotocol.h"
+#include <qwayland-server-cursor-shape-v1.h>
+
+namespace MockCompositor {
+
+class CursorShapeDevice;
+
+class CursorShapeManager : public Global, public QtWaylandServer::wp_cursor_shape_manager_v1
+{
+ Q_OBJECT
+public:
+ explicit CursorShapeManager(CoreCompositor *compositor, int version = 1);
+ QList<CursorShapeDevice *> m_cursorDevices;
+
+protected:
+ void wp_cursor_shape_manager_v1_get_pointer(Resource *resource, uint32_t id, wl_resource *pointer) override;
+};
+
+class CursorShapeDevice : public QObject, public QtWaylandServer::wp_cursor_shape_device_v1
+{
+ Q_OBJECT
+public:
+ explicit CursorShapeDevice(Pointer *pointer, wl_client *client, int id, int version);
+ Pointer *m_pointer;
+ shape m_currentShape = shape_default;
+
+Q_SIGNALS:
+ void setCursor(uint serial);
+
+protected:
+ void wp_cursor_shape_device_v1_destroy_resource(Resource *resource) override;
+ void wp_cursor_shape_device_v1_destroy(Resource *resource) override;
+ void wp_cursor_shape_device_v1_set_shape(Resource *resource, uint32_t serial, uint32_t shape) override;
+};
+
+}
+
+#endif
diff --git a/tests/auto/client/cursor/tst_cursor.cpp b/tests/auto/client/cursor/tst_cursor.cpp
new file mode 100644
index 000000000..65bafb23e
--- /dev/null
+++ b/tests/auto/client/cursor/tst_cursor.cpp
@@ -0,0 +1,89 @@
+// Copyright (C) 2023 David Edmundson <davidedmundson@kde.org>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "mockcompositor.h"
+#include <QtGui/QRasterWindow>
+#include <QtGui/qpa/qplatformnativeinterface.h>
+#include <QtWaylandClient/private/wayland-wayland-client-protocol.h>
+#include <QtWaylandClient/private/qwaylandwindow_p.h>
+
+#include "cursorshapev1.h"
+
+using namespace MockCompositor;
+
+class tst_cursor : public QObject, private DefaultCompositor
+{
+ Q_OBJECT
+public:
+ tst_cursor();
+ CursorShapeDevice* cursorShape();
+private slots:
+ void init();
+ void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); }
+ void setCursor();
+};
+
+tst_cursor::tst_cursor()
+{
+ exec([this] {
+ m_config.autoConfigure = true;
+ add<CursorShapeManager>(1);
+ });
+}
+
+CursorShapeDevice* tst_cursor::cursorShape()
+{
+ auto manager = get<CursorShapeManager>();
+ if (!manager->m_cursorDevices.count())
+ return nullptr;
+ return manager->m_cursorDevices[0];
+}
+
+void tst_cursor::init()
+{
+ setenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1", 1);
+}
+
+void tst_cursor::setCursor()
+{
+ QCOMPOSITOR_TRY_VERIFY(cursorShape());
+ QSignalSpy setCursorSpy(exec([&] { return pointer(); }), &Pointer::setCursor);
+ QSignalSpy setCursorShapeSpy(exec([&] { return cursorShape(); }), &CursorShapeDevice::setCursor);
+
+ QRasterWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ uint enterSerial = exec([&] {
+ return pointer()->sendEnter(xdgSurface()->m_surface, {32, 32});
+ });
+ setCursorShapeSpy.wait();
+ // verify we got given a cursor on enter
+ QCOMPOSITOR_COMPARE(cursorShape()->m_currentShape, CursorShapeDevice::shape_default);
+ QVERIFY(setCursorSpy.isEmpty());
+ QCOMPARE(setCursorShapeSpy.takeFirst().at(0).toUInt(), enterSerial);
+
+ // client sets a different shape
+ window.setCursor(QCursor(Qt::BusyCursor));
+ QCOMPOSITOR_TRY_COMPARE(cursorShape()->m_currentShape, CursorShapeDevice::shape_wait);
+
+ setCursorShapeSpy.clear();
+
+ // client hides the cursor
+ // CursorShape will not be used, instead, it uses the old path
+ window.setCursor(QCursor(Qt::BlankCursor));
+ QVERIFY(setCursorSpy.wait());
+ QVERIFY(setCursorShapeSpy.isEmpty());
+ QCOMPOSITOR_VERIFY(!pointer()->cursorSurface());
+
+ // same for bitmaps
+ QPixmap myCustomPixmap(10, 10);
+ myCustomPixmap.fill(Qt::red);
+ window.setCursor(QCursor(myCustomPixmap));
+ QVERIFY(setCursorSpy.wait());
+ QVERIFY(setCursorShapeSpy.isEmpty());
+}
+
+QCOMPOSITOR_TEST_MAIN(tst_cursor)
+#include "tst_cursor.moc"
diff --git a/tests/auto/client/shared/CMakeLists.txt b/tests/auto/client/shared/CMakeLists.txt
index a1f150c22..ee81b4d68 100644
--- a/tests/auto/client/shared/CMakeLists.txt
+++ b/tests/auto/client/shared/CMakeLists.txt
@@ -39,6 +39,7 @@ add_library(SharedClientTest
qt6_generate_wayland_protocol_server_sources(SharedClientTest
FILES
+ ${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/cursor-shape-v1.xml
${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/fullscreen-shell-unstable-v1.xml
${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/ivi-application.xml
${PROJECT_SOURCE_DIR}/src/3rdparty/protocol/wp-primary-selection-unstable-v1.xml
diff --git a/tests/auto/client/shared/coreprotocol.cpp b/tests/auto/client/shared/coreprotocol.cpp
index 15be62ccb..64586d413 100644
--- a/tests/auto/client/shared/coreprotocol.cpp
+++ b/tests/auto/client/shared/coreprotocol.cpp
@@ -435,15 +435,19 @@ void Pointer::sendAxisValue120(wl_client *client, QtWaylandServer::wl_pointer::a
void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y)
{
Q_UNUSED(resource);
- auto *s = fromResource<Surface>(surface);
- QVERIFY(s);
- if (s->m_role) {
- m_cursorRole = CursorRole::fromSurface(s);
- QVERIFY(m_cursorRole);
+ if (!surface) {
+ m_cursorRole = nullptr;
} else {
- m_cursorRole = new CursorRole(s); //TODO: make sure we don't leak CursorRole
- s->m_role = m_cursorRole;
+ auto *s = fromResource<Surface>(surface);
+ QVERIFY(s);
+ if (s->m_role) {
+ m_cursorRole = CursorRole::fromSurface(s);
+ QVERIFY(m_cursorRole);
+ } else {
+ m_cursorRole = new CursorRole(s); //TODO: make sure we don't leak CursorRole
+ s->m_role = m_cursorRole;
+ }
}
// Directly checking the last serial would be racy, we may just have sent leaves/enters which