summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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