diff options
-rw-r--r-- | src/3rdparty/protocol/cursor-shape-v1.xml | 146 | ||||
-rw-r--r-- | src/client/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/client/qwaylandcursor.cpp | 72 | ||||
-rw-r--r-- | src/client/qwaylandcursor_p.h | 9 | ||||
-rw-r--r-- | src/client/qwaylanddisplay.cpp | 3 | ||||
-rw-r--r-- | src/client/qwaylanddisplay_p.h | 5 | ||||
-rw-r--r-- | src/client/qwaylandinputdevice.cpp | 9 | ||||
-rw-r--r-- | src/client/qwaylandinputdevice_p.h | 2 | ||||
-rw-r--r-- | tests/auto/client/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/client/cursor/CMakeLists.txt | 11 | ||||
-rw-r--r-- | tests/auto/client/cursor/cursorshapev1.cpp | 47 | ||||
-rw-r--r-- | tests/auto/client/cursor/cursorshapev1.h | 44 | ||||
-rw-r--r-- | tests/auto/client/cursor/tst_cursor.cpp | 89 | ||||
-rw-r--r-- | tests/auto/client/shared/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/client/shared/coreprotocol.cpp | 18 |
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 |