diff --git a/CMakeLists.txt b/CMakeLists.txt index 278f38b2a6..7ce9dc6a87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,7 +203,7 @@ set_package_properties(Wayland PROPERTIES PURPOSE "Required for building KWin with Wayland support" ) -find_package(WaylandProtocols 1.31) +find_package(WaylandProtocols 1.32) set_package_properties(WaylandProtocols PROPERTIES TYPE REQUIRED PURPOSE "Collection of Wayland protocols that add functionality not available in the Wayland core protocol" diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt index b972010f64..afc77d306e 100644 --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -15,6 +15,7 @@ qt6_generate_wayland_protocol_client_sources(KWinIntegrationTestFramework ${WaylandProtocols_DATADIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml ${WaylandProtocols_DATADIR}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml ${WaylandProtocols_DATADIR}/staging/fractional-scale/fractional-scale-v1.xml + ${WaylandProtocols_DATADIR}/staging/cursor-shape/cursor-shape-v1.xml ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-output-device-v2.xml ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-output-management-v2.xml ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-screen-edge-v1.xml diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h index 5137f8ab0f..9022d65caa 100644 --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -20,6 +20,7 @@ #include #include +#include "qwayland-cursor-shape-v1.h" #include "qwayland-fractional-scale-v1.h" #include "qwayland-idle-inhibit-unstable-v1.h" #include "qwayland-input-method-unstable-v1.h" @@ -42,6 +43,7 @@ class Compositor; class Output; class PlasmaShell; class PlasmaWindowManagement; +class Pointer; class PointerConstraints; class Seat; class ServerSideDecorationManager; @@ -504,6 +506,21 @@ public: ~AutoHideScreenEdgeV1() override; }; +class CursorShapeManagerV1 : public QObject, public QtWayland::wp_cursor_shape_manager_v1 +{ + Q_OBJECT +public: + ~CursorShapeManagerV1() override; +}; + +class CursorShapeDeviceV1 : public QObject, public QtWayland::wp_cursor_shape_device_v1 +{ + Q_OBJECT +public: + CursorShapeDeviceV1(CursorShapeManagerV1 *manager, KWayland::Client::Pointer *pointer); + ~CursorShapeDeviceV1() override; +}; + enum class AdditionalWaylandInterface { Seat = 1 << 0, Decoration = 1 << 1, @@ -523,6 +540,7 @@ enum class AdditionalWaylandInterface { FractionalScaleManagerV1 = 1 << 15, ScreencastingV1 = 1 << 16, ScreenEdgeV1 = 1 << 17, + CursorShapeV1 = 1 << 18, }; Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface) @@ -669,6 +687,7 @@ XdgPopup *createXdgPopupSurface(KWayland::Client::Surface *surface, XdgSurface * XdgToplevelDecorationV1 *createXdgToplevelDecorationV1(XdgToplevel *toplevel, QObject *parent = nullptr); IdleInhibitorV1 *createIdleInhibitorV1(KWayland::Client::Surface *surface); AutoHideScreenEdgeV1 *createAutoHideScreenEdgeV1(KWayland::Client::Surface *surface, uint32_t border); +CursorShapeDeviceV1 *createCursorShapeDeviceV1(KWayland::Client::Pointer *pointer); /** * Creates a shared memory buffer of @p size in @p color and attaches it to the @p surface. diff --git a/autotests/integration/pointer_input.cpp b/autotests/integration/pointer_input.cpp index 53511e8bdb..3a01728b50 100644 --- a/autotests/integration/pointer_input.cpp +++ b/autotests/integration/pointer_input.cpp @@ -10,6 +10,7 @@ #include "core/output.h" #include "cursor.h" +#include "cursorsource.h" #include "libkwineffects/kwineffects.h" #include "options.h" #include "pointer_input.h" @@ -107,6 +108,7 @@ private Q_SLOTS: void testMouseActionActiveWindow_data(); void testMouseActionActiveWindow(); void testCursorImage(); + void testCursorShapeV1(); void testEffectOverrideCursorImage(); void testPopup(); void testDecoCancelsPopup(); @@ -159,7 +161,7 @@ void PointerInputTest::initTestCase() void PointerInputTest::init() { - QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::XdgDecorationV1)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::XdgDecorationV1 | Test::AdditionalWaylandInterface::CursorShapeV1)); QVERIFY(Test::waitForWaylandPointer()); m_compositor = Test::waylandCompositor(); m_seat = Test::waylandSeat(); @@ -1151,6 +1153,51 @@ void PointerInputTest::testCursorImage() QCOMPARE(kwinApp()->cursorImage().image(), fallbackCursor); } +static QByteArray currentCursorShape() +{ + if (auto source = qobject_cast(Cursors::self()->currentCursor()->source())) { + return source->shape(); + } + return QByteArray(); +} + +void PointerInputTest::testCursorShapeV1() +{ + // this test verifies the integration of the cursor-shape-v1 protocol + + // get the pointer + std::unique_ptr pointer(m_seat->createPointer()); + std::unique_ptr cursorShapeDevice(Test::createCursorShapeDeviceV1(pointer.get())); + + // move cursor somewhere the new window won't open + input()->pointer()->warp(QPointF(800, 800)); + QCOMPARE(currentCursorShape(), QByteArray("left_ptr")); + + // create a window + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::cyan); + QVERIFY(window); + + // move the pointer to the center of the window + QSignalSpy enteredSpy(pointer.get(), &KWayland::Client::Pointer::entered); + input()->pointer()->warp(window->frameGeometry().center()); + QVERIFY(enteredSpy.wait()); + + // set a custom cursor shape + QSignalSpy cursorChanged(Cursors::self(), &Cursors::currentCursorChanged); + cursorShapeDevice->set_shape(enteredSpy.last().at(0).value(), Test::CursorShapeDeviceV1::shape_text); + QVERIFY(cursorChanged.wait()); + QCOMPARE(currentCursorShape(), QByteArray("text")); + + // cursor shape won't be changed if the window has no pointer focus + input()->pointer()->warp(QPointF(800, 800)); + QCOMPARE(currentCursorShape(), QByteArray("left_ptr")); + cursorShapeDevice->set_shape(enteredSpy.last().at(0).value(), Test::CursorShapeDeviceV1::shape_grab); + QVERIFY(Test::waylandSync()); + QCOMPARE(currentCursorShape(), QByteArray("left_ptr")); +} + class HelperEffect : public Effect { Q_OBJECT @@ -1168,16 +1215,14 @@ void PointerInputTest::testEffectOverrideCursorImage() // this test verifies the effect cursor override handling // we need a pointer to get the enter event and set a cursor - auto pointer = m_seat->createPointer(m_seat); - QVERIFY(pointer); - QVERIFY(pointer->isValid()); - QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered); - QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left); + std::unique_ptr pointer(m_seat->createPointer()); + std::unique_ptr cursorShapeDevice(Test::createCursorShapeDeviceV1(pointer.get())); + QSignalSpy enteredSpy(pointer.get(), &KWayland::Client::Pointer::entered); + QSignalSpy leftSpy(pointer.get(), &KWayland::Client::Pointer::left); + QSignalSpy cursorChanged(Cursors::self(), &Cursors::currentCursorChanged); + // move cursor somewhere the new window won't open input()->pointer()->warp(QPointF(800, 800)); - // here we should have the fallback cursor - const QImage fallback = kwinApp()->cursorImage().image(); - QVERIFY(!fallback.isNull()); // now let's create a window QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); @@ -1194,34 +1239,26 @@ void PointerInputTest::testEffectOverrideCursorImage() QVERIFY(!exclusiveContains(window->frameGeometry(), QPoint(800, 800))); input()->pointer()->warp(window->frameGeometry().center()); QVERIFY(enteredSpy.wait()); - // cursor image should still be fallback - QCOMPARE(kwinApp()->cursorImage().image(), fallback); + cursorShapeDevice->set_shape(enteredSpy.last().at(0).value(), Test::CursorShapeDeviceV1::shape_wait); + QVERIFY(cursorChanged.wait()); + QCOMPARE(currentCursorShape(), QByteArray("wait")); // now create an effect and set an override cursor std::unique_ptr effect(new HelperEffect); effects->startMouseInterception(effect.get(), Qt::SizeAllCursor); - const QImage sizeAll = kwinApp()->cursorImage().image(); - QVERIFY(!sizeAll.isNull()); - QVERIFY(sizeAll != fallback); - QVERIFY(leftSpy.wait()); + QCOMPARE(currentCursorShape(), QByteArray("size_all")); // let's change to arrow cursor, this should be our fallback effects->defineCursor(Qt::ArrowCursor); - QCOMPARE(kwinApp()->cursorImage().image(), fallback); + QCOMPARE(currentCursorShape(), QByteArray("left_ptr")); // back to size all effects->defineCursor(Qt::SizeAllCursor); - QCOMPARE(kwinApp()->cursorImage().image(), sizeAll); + QCOMPARE(currentCursorShape(), QByteArray("size_all")); // move cursor outside the window area input()->pointer()->warp(QPointF(800, 800)); - // and end the override, which should switch to fallback - effects->stopMouseInterception(effect.get()); - QCOMPARE(kwinApp()->cursorImage().image(), fallback); - - // start mouse interception again - effects->startMouseInterception(effect.get(), Qt::SizeAllCursor); - QCOMPARE(kwinApp()->cursorImage().image(), sizeAll); + QCOMPARE(currentCursorShape(), QByteArray("size_all")); // move cursor to area of window input()->pointer()->warp(window->frameGeometry().center()); @@ -1232,7 +1269,9 @@ void PointerInputTest::testEffectOverrideCursorImage() // after ending the interception we should get an enter event effects->stopMouseInterception(effect.get()); QVERIFY(enteredSpy.wait()); - QVERIFY(kwinApp()->cursorImage().image().isNull()); + cursorShapeDevice->set_shape(enteredSpy.last().at(0).value(), Test::CursorShapeDeviceV1::shape_crosshair); + QVERIFY(cursorChanged.wait()); + QCOMPARE(currentCursorShape(), QByteArray("cross")); } void PointerInputTest::testPopup() diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp index 445c5cbce8..b4e25efe1f 100644 --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -244,6 +245,21 @@ AutoHideScreenEdgeV1::~AutoHideScreenEdgeV1() destroy(); } +CursorShapeManagerV1::~CursorShapeManagerV1() +{ + destroy(); +} + +CursorShapeDeviceV1::CursorShapeDeviceV1(CursorShapeManagerV1 *manager, KWayland::Client::Pointer *pointer) + : QtWayland::wp_cursor_shape_device_v1(manager->get_pointer(*pointer)) +{ +} + +CursorShapeDeviceV1::~CursorShapeDeviceV1() +{ + destroy(); +} + static struct { KWayland::Client::ConnectionThread *connection = nullptr; @@ -275,6 +291,7 @@ static struct FractionalScaleManagerV1 *fractionalScaleManagerV1 = nullptr; ScreencastingV1 *screencastingV1 = nullptr; ScreenEdgeManagerV1 *screenEdgeManagerV1 = nullptr; + CursorShapeManagerV1 *cursorShapeManagerV1 = nullptr; } s_waylandConnection; MockInputMethod *inputMethod() @@ -460,6 +477,12 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags) return; } } + if (flags &AdditionalWaylandInterface::CursorShapeV1) { + if (interface == wp_cursor_shape_manager_v1_interface.name) { + s_waylandConnection.cursorShapeManagerV1 = new CursorShapeManagerV1(); + s_waylandConnection.cursorShapeManagerV1->init(*registry, name, version); + } + } }); QSignalSpy allAnnounced(registry, &KWayland::Client::Registry::interfacesAnnounced); @@ -590,6 +613,8 @@ void destroyWaylandConnection() s_waylandConnection.screencastingV1 = nullptr; delete s_waylandConnection.screenEdgeManagerV1; s_waylandConnection.screenEdgeManagerV1 = nullptr; + delete s_waylandConnection.cursorShapeManagerV1; + s_waylandConnection.cursorShapeManagerV1 = nullptr; delete s_waylandConnection.queue; // Must be destroyed last s_waylandConnection.queue = nullptr; @@ -1009,6 +1034,17 @@ AutoHideScreenEdgeV1 *createAutoHideScreenEdgeV1(KWayland::Client::Surface *surf return new AutoHideScreenEdgeV1(manager, surface, border); } +CursorShapeDeviceV1 *createCursorShapeDeviceV1(KWayland::Client::Pointer *pointer) +{ + CursorShapeManagerV1 *manager = s_waylandConnection.cursorShapeManagerV1; + if (!manager) { + qWarning() << "Could not create a wp_cursor_shape_device_v1 because wp_cursor_shape_manager_v1 global is not bound"; + return nullptr; + } + + return new CursorShapeDeviceV1(manager, pointer); +} + bool waitForWindowClosed(Window *window) { QSignalSpy closedSpy(window, &Window::closed); diff --git a/src/input.cpp b/src/input.cpp index ee7cc3223c..47d27e23a2 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -1956,27 +1956,35 @@ public: explicit SurfaceCursor(KWaylandServer::TabletToolV2Interface *tool) : Cursor(tool) { - connect(tool, &KWaylandServer::TabletToolV2Interface::cursorChanged, this, [this](KWaylandServer::TabletCursorV2 *tcursor) { - if (!tcursor || tcursor->enteredSerial() == 0) { - if (!m_defaultSource) { - m_defaultSource = std::make_unique(); + connect(tool, &KWaylandServer::TabletToolV2Interface::cursorChanged, this, [this](const KWaylandServer::TabletCursorSourceV2 &cursor) { + if (auto surfaceCursor = std::get_if(&cursor)) { + // If the cursor is unset, fallback to the cross cursor. + if ((*surfaceCursor) && (*surfaceCursor)->enteredSerial()) { + if (!m_surfaceSource) { + m_surfaceSource = std::make_unique(); + } + m_surfaceSource->update((*surfaceCursor)->surface(), (*surfaceCursor)->hotspot()); + setSource(m_surfaceSource.get()); + return; } - static WaylandCursorImage defaultCursor; - m_defaultSource->setTheme(defaultCursor.theme()); - m_defaultSource->setShape(Qt::CrossCursor); - setSource(m_defaultSource.get()); + } + + QByteArray shape; + if (auto shapeCursor = std::get_if(&cursor)) { + shape = *shapeCursor; } else { - if (!m_surfaceSource) { - m_surfaceSource = std::make_unique(); - } - m_surfaceSource->update(tcursor->surface(), tcursor->hotspot()); - setSource(m_surfaceSource.get()); + shape = QByteArrayLiteral("cross"); } + + static WaylandCursorImage defaultCursor; + m_shapeSource->setTheme(defaultCursor.theme()); + m_shapeSource->setShape(shape); + setSource(m_shapeSource.get()); }); } private: - std::unique_ptr m_defaultSource; + std::unique_ptr m_shapeSource; std::unique_ptr m_surfaceSource; }; diff --git a/src/pointer_input.cpp b/src/pointer_input.cpp index b6e4232ab6..24a75ce69e 100644 --- a/src/pointer_input.cpp +++ b/src/pointer_input.cpp @@ -862,7 +862,8 @@ CursorImage::CursorImage(PointerInputRedirection *parent) m_moveResizeCursor = std::make_unique(); m_windowSelectionCursor = std::make_unique(); m_decoration.cursor = std::make_unique(); - m_serverCursor.cursor = std::make_unique(); + m_serverCursor.surface = std::make_unique(); + m_serverCursor.shape = std::make_unique(); #if KWIN_BUILD_SCREENLOCKER if (waylandServer()->hasScreenLockerIntegration()) { @@ -886,6 +887,7 @@ CursorImage::CursorImage(PointerInputRedirection *parent) m_moveResizeCursor->setTheme(m_waylandImage.theme()); m_windowSelectionCursor->setTheme(m_waylandImage.theme()); m_decoration.cursor->setTheme(m_waylandImage.theme()); + m_serverCursor.shape->setTheme(m_waylandImage.theme()); connect(&m_waylandImage, &WaylandCursorImage::themeChanged, this, [this] { m_effectsCursor->setTheme(m_waylandImage.theme()); @@ -893,6 +895,7 @@ CursorImage::CursorImage(PointerInputRedirection *parent) m_moveResizeCursor->setTheme(m_waylandImage.theme()); m_windowSelectionCursor->setTheme(m_waylandImage.theme()); m_decoration.cursor->setTheme(m_waylandImage.theme()); + m_serverCursor.shape->setTheme(m_waylandImage.theme()); }); KWaylandServer::PointerInterface *pointer = waylandServer()->seat()->pointer(); @@ -907,8 +910,8 @@ CursorImage::~CursorImage() = default; void CursorImage::updateCursorOutputs(const QPointF &pos) { - if (m_currentSource == m_serverCursor.cursor.get()) { - auto cursorSurface = m_serverCursor.cursor->surface(); + if (m_currentSource == m_serverCursor.surface.get()) { + auto cursorSurface = m_serverCursor.surface->surface(); if (cursorSurface) { const QRectF cursorGeometry(pos - m_currentSource->hotspot(), m_currentSource->size()); cursorSurface->setOutputs(waylandServer()->display()->outputsIntersecting(cursorGeometry.toAlignedRect())); @@ -918,8 +921,8 @@ void CursorImage::updateCursorOutputs(const QPointF &pos) void CursorImage::markAsRendered(std::chrono::milliseconds timestamp) { - if (m_currentSource == m_serverCursor.cursor.get()) { - auto cursorSurface = m_serverCursor.cursor->surface(); + if (m_currentSource == m_serverCursor.surface.get()) { + auto cursorSurface = m_serverCursor.surface->surface(); if (cursorSurface) { cursorSurface->frameRendered(timestamp.count()); } @@ -970,9 +973,15 @@ void CursorImage::updateMoveResize() reevaluteSource(); } -void CursorImage::updateServerCursor(KWaylandServer::Cursor *cursor) +void CursorImage::updateServerCursor(const KWaylandServer::PointerCursor &cursor) { - m_serverCursor.cursor->update(cursor->surface(), cursor->hotspot()); + if (auto surfaceCursor = std::get_if(&cursor)) { + m_serverCursor.surface->update((*surfaceCursor)->surface(), (*surfaceCursor)->hotspot()); + m_serverCursor.cursor = m_serverCursor.surface.get(); + } else if (auto shapeCursor = std::get_if(&cursor)) { + m_serverCursor.shape->setShape(*shapeCursor); + m_serverCursor.cursor = m_serverCursor.shape.get(); + } reevaluteSource(); } @@ -1040,7 +1049,7 @@ void WaylandCursorImage::updateCursorTheme() void CursorImage::reevaluteSource() { if (waylandServer()->isScreenLocked()) { - setSource(m_serverCursor.cursor.get()); + setSource(m_serverCursor.cursor); return; } if (input()->isSelectingWindow()) { @@ -1061,7 +1070,7 @@ void CursorImage::reevaluteSource() } const KWaylandServer::PointerInterface *pointer = waylandServer()->seat()->pointer(); if (pointer && pointer->focusedSurface()) { - setSource(m_serverCursor.cursor.get()); + setSource(m_serverCursor.cursor); return; } setSource(m_fallbackCursor.get()); diff --git a/src/pointer_input.h b/src/pointer_input.h index 34c006f56c..ad5e9a2692 100644 --- a/src/pointer_input.h +++ b/src/pointer_input.h @@ -222,7 +222,7 @@ Q_SIGNALS: private: void reevaluteSource(); - void updateServerCursor(KWaylandServer::Cursor *cursor); + void updateServerCursor(const std::variant &cursor); void updateDecoration(); void updateDecorationCursor(); void updateMoveResize(); @@ -246,7 +246,9 @@ private: struct { QMetaObject::Connection connection; - std::unique_ptr cursor; + std::unique_ptr surface; + std::unique_ptr shape; + CursorSource *cursor = nullptr; } m_serverCursor; }; diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 2b97389fbd..a41b62bee8 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -212,6 +212,10 @@ ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-screen-edge-v1.xml BASENAME kde-screen-edge-v1 ) +ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml + PROTOCOL ${WaylandProtocols_DATADIR}/staging/cursor-shape/cursor-shape-v1.xml + BASENAME cursor-shape-v1 +) target_sources(kwin PRIVATE abstract_data_source.cpp @@ -222,6 +226,7 @@ target_sources(kwin PRIVATE compositor_interface.cpp contenttype_v1_interface.cpp contrast_interface.cpp + cursorshape_v1_interface.cpp datacontroldevice_v1_interface.cpp datacontroldevicemanager_v1_interface.cpp datacontroloffer_v1_interface.cpp @@ -305,6 +310,7 @@ install(FILES compositor_interface.h contenttype_v1_interface.h contrast_interface.h + cursorshape_v1_interface.h datacontroldevice_v1_interface.h datacontroldevicemanager_v1_interface.h datacontroloffer_v1_interface.h diff --git a/src/wayland/autotests/client/test_wayland_seat.cpp b/src/wayland/autotests/client/test_wayland_seat.cpp index a1e1f98c20..a2aaa845ba 100644 --- a/src/wayland/autotests/client/test_wayland_seat.cpp +++ b/src/wayland/autotests/client/test_wayland_seat.cpp @@ -1328,7 +1328,7 @@ void TestWaylandSeat::testCursor() p->setCursor(nullptr); QVERIFY(cursorChangedSpy.wait()); QCOMPARE(cursorChangedSpy.count(), 1); - auto cursor = cursorChangedSpy.last().first().value(); + auto cursor = std::get(cursorChangedSpy.last().first().value()); QVERIFY(cursor); QVERIFY(!cursor->surface()); QCOMPARE(cursor->hotspot(), QPoint()); diff --git a/src/wayland/cursorshape_v1_interface.cpp b/src/wayland/cursorshape_v1_interface.cpp new file mode 100644 index 0000000000..bc374f65a6 --- /dev/null +++ b/src/wayland/cursorshape_v1_interface.cpp @@ -0,0 +1,209 @@ +/* + SPDX-FileCopyrightText: 2023 Vlad Zahorodnii + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "wayland/cursorshape_v1_interface.h" +#include "wayland/display.h" +#include "wayland/pointer_interface.h" +#include "wayland/surface_interface.h" +#include "wayland/tablet_v2_interface.h" + +#include + +#include "qwayland-server-cursor-shape-v1.h" + +namespace KWaylandServer +{ + +static constexpr int s_version = 1; + +class CursorShapeManagerV1InterfacePrivate : public QtWaylandServer::wp_cursor_shape_manager_v1 +{ +public: + CursorShapeManagerV1InterfacePrivate(Display *display); + +protected: + void wp_cursor_shape_manager_v1_destroy(Resource *resource) override; + void wp_cursor_shape_manager_v1_get_pointer(Resource *resource, uint32_t cursor_shape_device, struct ::wl_resource *pointer) override; + void wp_cursor_shape_manager_v1_get_tablet_tool_v2(Resource *resource, uint32_t cursor_shape_device, struct ::wl_resource *tablet_tool) override; +}; + +class CursorShapeDeviceV1Interface : public QtWaylandServer::wp_cursor_shape_device_v1 +{ +public: + CursorShapeDeviceV1Interface(PointerInterface *pointer, wl_resource *resource); + CursorShapeDeviceV1Interface(TabletToolV2Interface *tabletTool, wl_resource *resource); + + QPointer pointer; + QPointer tabletTool; + +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; +}; + +CursorShapeManagerV1InterfacePrivate::CursorShapeManagerV1InterfacePrivate(Display *display) + : QtWaylandServer::wp_cursor_shape_manager_v1(*display, s_version) +{ +} + +void CursorShapeManagerV1InterfacePrivate::wp_cursor_shape_manager_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void CursorShapeManagerV1InterfacePrivate::wp_cursor_shape_manager_v1_get_pointer(Resource *resource, uint32_t cursor_shape_device, struct ::wl_resource *pointer) +{ + wl_resource *device = wl_resource_create(resource->client(), &wp_cursor_shape_device_v1_interface, resource->version(), cursor_shape_device); + if (!device) { + wl_resource_post_no_memory(resource->handle); + return; + } + new CursorShapeDeviceV1Interface(PointerInterface::get(pointer), device); +} + +void CursorShapeManagerV1InterfacePrivate::wp_cursor_shape_manager_v1_get_tablet_tool_v2(Resource *resource, uint32_t cursor_shape_device, struct ::wl_resource *tablet_tool) +{ + wl_resource *device = wl_resource_create(resource->client(), &wp_cursor_shape_device_v1_interface, resource->version(), cursor_shape_device); + if (!device) { + wl_resource_post_no_memory(resource->handle); + return; + } + new CursorShapeDeviceV1Interface(TabletToolV2Interface::get(tablet_tool), device); +} + +CursorShapeManagerV1Interface::CursorShapeManagerV1Interface(Display *display, QObject *parent) + : QObject(parent) + , d(std::make_unique(display)) +{ +} + +CursorShapeManagerV1Interface::~CursorShapeManagerV1Interface() +{ +} + +CursorShapeDeviceV1Interface::CursorShapeDeviceV1Interface(PointerInterface *pointer, wl_resource *resource) + : QtWaylandServer::wp_cursor_shape_device_v1(resource) + , pointer(pointer) +{ +} + +CursorShapeDeviceV1Interface::CursorShapeDeviceV1Interface(TabletToolV2Interface *tabletTool, wl_resource *resource) + : QtWaylandServer::wp_cursor_shape_device_v1(resource) + , tabletTool(tabletTool) +{ +} + +void CursorShapeDeviceV1Interface::wp_cursor_shape_device_v1_destroy_resource(Resource *resource) +{ + delete this; +} + +void CursorShapeDeviceV1Interface::wp_cursor_shape_device_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +static QByteArray shapeName(uint32_t shape) +{ + switch (shape) { + case QtWaylandServer::wp_cursor_shape_device_v1::shape_default: + return QByteArrayLiteral("default"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_context_menu: + return QByteArrayLiteral("context-menu"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_help: + return QByteArrayLiteral("whats_this"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_pointer: + return QByteArrayLiteral("pointing_hand"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_progress: + return QByteArrayLiteral("left_ptr_watch"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_wait: + return QByteArrayLiteral("wait"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_cell: + return QByteArrayLiteral("cell"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_crosshair: + return QByteArrayLiteral("cross"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_text: + return QByteArrayLiteral("text"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_vertical_text: + return QByteArrayLiteral("vertical-text"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_alias: + return QByteArrayLiteral("dnd-link"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_copy: + return QByteArrayLiteral("dnd-copy"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_move: + return QByteArrayLiteral("dnd-move"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_no_drop: + return QByteArrayLiteral("no-drop"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_not_allowed: + return QByteArrayLiteral("not-allowed"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_grab: + return QByteArrayLiteral("openhand"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_grabbing: + return QByteArrayLiteral("closedhand"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_e_resize: + return QByteArrayLiteral("e-resize"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_n_resize: + return QByteArrayLiteral("n-resize"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_ne_resize: + return QByteArrayLiteral("ne-resize"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_nw_resize: + return QByteArrayLiteral("nw-resize"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_s_resize: + return QByteArrayLiteral("s-resize"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_se_resize: + return QByteArrayLiteral("se-resize"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_sw_resize: + return QByteArrayLiteral("sw-resize"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_w_resize: + return QByteArrayLiteral("w-resize"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_ew_resize: + return QByteArrayLiteral("size_hor"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_ns_resize: + return QByteArrayLiteral("size_ver"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_nesw_resize: + return QByteArrayLiteral("size_bdiag"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_nwse_resize: + return QByteArrayLiteral("size_fdiag"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_col_resize: + return QByteArrayLiteral("col-resize"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_row_resize: + return QByteArrayLiteral("row-resize"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_all_scroll: + return QByteArrayLiteral("all-scroll"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_zoom_in: + return QByteArrayLiteral("zoom-in"); + case QtWaylandServer::wp_cursor_shape_device_v1::shape_zoom_out: + return QByteArrayLiteral("zoom-out"); + default: + return QByteArrayLiteral("left_ptr"); + } +} + +void CursorShapeDeviceV1Interface::wp_cursor_shape_device_v1_set_shape(Resource *resource, uint32_t serial, uint32_t shape) +{ + if (shape < shape_default || shape > shape_zoom_out) { + wl_resource_post_error(resource->handle, error_invalid_shape, "unknown cursor shape"); + return; + } + if (pointer) { + if (!pointer->focusedSurface() || pointer->focusedSurface()->client()->client() != resource->client()) { + return; + } + if (pointer->focusedSerial() == serial) { + Q_EMIT pointer->cursorChanged(shapeName(shape)); + } + } else if (tabletTool) { + if (!tabletTool->currentSurface() || tabletTool->currentSurface()->client()->client() != resource->client()) { + return; + } + if (tabletTool->proximitySerial() == serial) { + Q_EMIT tabletTool->cursorChanged(shapeName(shape)); + } + } +} + +} // namespace KWaylandServer diff --git a/src/wayland/cursorshape_v1_interface.h b/src/wayland/cursorshape_v1_interface.h new file mode 100644 index 0000000000..007d522a33 --- /dev/null +++ b/src/wayland/cursorshape_v1_interface.h @@ -0,0 +1,31 @@ +/* + SPDX-FileCopyrightText: 2023 Vlad Zahorodnii + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include "kwin_export.h" + +#include + +namespace KWaylandServer +{ + +class CursorShapeManagerV1InterfacePrivate; +class Display; + +class KWIN_EXPORT CursorShapeManagerV1Interface : public QObject +{ + Q_OBJECT + +public: + explicit CursorShapeManagerV1Interface(Display *display, QObject *parent = nullptr); + ~CursorShapeManagerV1Interface() override; + +private: + std::unique_ptr d; +}; + +} // namespace KWaylandServer diff --git a/src/wayland/pointer_interface.cpp b/src/wayland/pointer_interface.cpp index 0b1a37cb02..c4bee8e88a 100644 --- a/src/wayland/pointer_interface.cpp +++ b/src/wayland/pointer_interface.cpp @@ -147,6 +147,11 @@ SurfaceInterface *PointerInterface::focusedSurface() const return d->focusedSurface; } +quint32 PointerInterface::focusedSerial() const +{ + return d->focusedSerial; +} + void PointerInterface::sendEnter(SurfaceInterface *surface, const QPointF &position, quint32 serial) { if (d->focusedSurface == surface) { diff --git a/src/wayland/pointer_interface.h b/src/wayland/pointer_interface.h index 8e1321ac33..ad127a94d3 100644 --- a/src/wayland/pointer_interface.h +++ b/src/wayland/pointer_interface.h @@ -25,6 +25,8 @@ class SurfaceInterface; enum class PointerAxisSource; enum class PointerButtonState : quint32; +using PointerCursor = std::variant; + /** * The PointerInterface class represents one or more input devices such as mice, which control * the pointer location. It corresponds to the Wayland interface @c wl_pointer. @@ -42,6 +44,7 @@ public: * the effective focused surface. */ SurfaceInterface *focusedSurface() const; + quint32 focusedSerial() const; /** * Returns the seat to which this pointer belongs to. @@ -65,7 +68,7 @@ Q_SIGNALS: * This signal is emitted whenever the cursor surface changes. As long as there is no * any focused surface, the cursor cannot be changed. */ - void cursorChanged(KWaylandServer::Cursor *cursor); + void cursorChanged(const PointerCursor &cursor); /** * This signal is emitted whenever the focused pointer surface changes. */ diff --git a/src/wayland/tablet_v2_interface.cpp b/src/wayland/tablet_v2_interface.cpp index 719187e6d8..a93a92d118 100644 --- a/src/wayland/tablet_v2_interface.cpp +++ b/src/wayland/tablet_v2_interface.cpp @@ -8,6 +8,7 @@ #include "display.h" #include "seat_interface.h" #include "surface_interface.h" +#include "utils.h" #include "qwayland-server-tablet-unstable-v2.h" #include @@ -68,10 +69,10 @@ TabletPadV2Interface *TabletV2Interface::pad() const return d->m_pad; } -class TabletCursorV2Private +class TabletSurfaceCursorV2Private { public: - TabletCursorV2Private(TabletCursorV2 *q) + TabletSurfaceCursorV2Private(TabletSurfaceCursorV2 *q) : q(q) { } @@ -88,32 +89,32 @@ public: } } - TabletCursorV2 *const q; + TabletSurfaceCursorV2 *const q; quint32 m_serial = 0; QPointer m_surface; QPoint m_hotspot; }; -TabletCursorV2::TabletCursorV2() +TabletSurfaceCursorV2::TabletSurfaceCursorV2() : QObject() - , d(new TabletCursorV2Private(this)) + , d(new TabletSurfaceCursorV2Private(this)) { } -TabletCursorV2::~TabletCursorV2() = default; +TabletSurfaceCursorV2::~TabletSurfaceCursorV2() = default; -QPoint TabletCursorV2::hotspot() const +QPoint TabletSurfaceCursorV2::hotspot() const { return d->m_hotspot; } -quint32 TabletCursorV2::enteredSerial() const +quint32 TabletSurfaceCursorV2::enteredSerial() const { return d->m_serial; } -SurfaceInterface *TabletCursorV2::surface() const +SurfaceInterface *TabletSurfaceCursorV2::surface() const { return d->m_surface; } @@ -162,17 +163,18 @@ public: void zwp_tablet_tool_v2_bind_resource(QtWaylandServer::zwp_tablet_tool_v2::Resource *resource) override { - TabletCursorV2 *&c = m_cursors[resource->handle]; + TabletSurfaceCursorV2 *&c = m_cursors[resource->handle]; if (!c) - c = new TabletCursorV2; + c = new TabletSurfaceCursorV2; } void zwp_tablet_tool_v2_set_cursor(Resource *resource, uint32_t serial, struct ::wl_resource *_surface, int32_t hotspot_x, int32_t hotspot_y) override { - TabletCursorV2 *c = m_cursors[resource->handle]; + TabletSurfaceCursorV2 *c = m_cursors[resource->handle]; c->d->update(serial, SurfaceInterface::get(_surface), {hotspot_x, hotspot_y}); - if (resource->handle == targetResource()) - q->cursorChanged(c); + if (resource->handle == targetResource()) { + Q_EMIT q->cursorChanged(c); + } } void zwp_tablet_tool_v2_destroy_resource(Resource *resource) override @@ -189,6 +191,7 @@ public: } Display *const m_display; + quint32 m_proximitySerial = 0; bool m_cleanup = false; bool m_removed = false; QPointer m_surface; @@ -197,7 +200,7 @@ public: const uint32_t m_hardwareSerialHigh, m_hardwareSerialLow; const uint32_t m_hardwareIdHigh, m_hardwareIdLow; const QVector m_capabilities; - QHash m_cursors; + QHash m_cursors; TabletToolV2Interface *const q; }; @@ -220,11 +223,24 @@ TabletToolV2Interface::~TabletToolV2Interface() } } +TabletToolV2Interface *TabletToolV2Interface::get(wl_resource *resource) +{ + if (TabletToolV2InterfacePrivate *tabletToolPrivate = resource_cast(resource)) { + return tabletToolPrivate->q; + } + return nullptr; +} + bool TabletToolV2Interface::hasCapability(Capability capability) const { return d->m_capabilities.contains(capability); } +SurfaceInterface *TabletToolV2Interface::currentSurface() const +{ + return d->m_surface; +} + void TabletToolV2Interface::setCurrentSurface(SurfaceInterface *surface) { if (d->m_surface == surface) @@ -247,6 +263,11 @@ void TabletToolV2Interface::setCurrentSurface(SurfaceInterface *surface) Q_EMIT cursorChanged(d->m_cursors.value(d->targetResource())); } +quint32 TabletToolV2Interface::proximitySerial() const +{ + return d->m_proximitySerial; +} + bool TabletToolV2Interface::isClientSupported() const { return d->m_surface && d->targetResource(); @@ -310,7 +331,10 @@ void TabletToolV2Interface::sendWheel(int32_t degrees, int32_t clicks) void TabletToolV2Interface::sendProximityIn(TabletV2Interface *tablet) { wl_resource *tabletResource = tablet->d->resourceForSurface(d->m_surface); - d->send_proximity_in(d->targetResource(), d->m_display->nextSerial(), tabletResource, d->m_surface->resource()); + quint32 serial = d->m_display->nextSerial(); + + d->send_proximity_in(d->targetResource(), serial, tabletResource, d->m_surface->resource()); + d->m_proximitySerial = serial; d->m_lastTablet = tablet; } diff --git a/src/wayland/tablet_v2_interface.h b/src/wayland/tablet_v2_interface.h index 1bf3bdb3b0..c81386559d 100644 --- a/src/wayland/tablet_v2_interface.h +++ b/src/wayland/tablet_v2_interface.h @@ -11,14 +11,16 @@ #include #include +struct wl_resource; + namespace KWaylandServer { class ClientConnection; class Display; class SeatInterface; class SurfaceInterface; -class TabletCursorV2; -class TabletCursorV2Private; +class TabletSurfaceCursorV2; +class TabletSurfaceCursorV2Private; class TabletManagerV2InterfacePrivate; class TabletSeatV2Interface; class TabletSeatV2InterfacePrivate; @@ -54,6 +56,26 @@ private: std::unique_ptr d; }; +class KWIN_EXPORT TabletSurfaceCursorV2 : public QObject +{ + Q_OBJECT +public: + ~TabletSurfaceCursorV2() override; + QPoint hotspot() const; + quint32 enteredSerial() const; + SurfaceInterface *surface() const; + +Q_SIGNALS: + void changed(); + +private: + TabletSurfaceCursorV2(); + const std::unique_ptr d; + friend class TabletToolV2InterfacePrivate; +}; + +using TabletCursorSourceV2 = std::variant; + class KWIN_EXPORT TabletToolV2Interface : public QObject { Q_OBJECT @@ -93,8 +115,12 @@ public: * @see TabletV2Interface::isSurfaceSupported */ void setCurrentSurface(SurfaceInterface *surface); + SurfaceInterface *currentSurface() const; + bool isClientSupported() const; + quint32 proximitySerial() const; + void sendProximityIn(TabletV2Interface *tablet); void sendProximityOut(); void sendUp(); @@ -109,8 +135,10 @@ public: void sendFrame(quint32 time); void sendMotion(const QPointF &pos); + static TabletToolV2Interface *get(wl_resource *resource); + Q_SIGNALS: - void cursorChanged(TabletCursorV2 *cursor) const; + void cursorChanged(const TabletCursorSourceV2 &cursor); private: friend class TabletSeatV2InterfacePrivate; @@ -125,24 +153,6 @@ private: std::unique_ptr d; }; -class KWIN_EXPORT TabletCursorV2 : public QObject -{ - Q_OBJECT -public: - ~TabletCursorV2() override; - QPoint hotspot() const; - quint32 enteredSerial() const; - SurfaceInterface *surface() const; - -Q_SIGNALS: - void changed(); - -private: - TabletCursorV2(); - const std::unique_ptr d; - friend class TabletToolV2InterfacePrivate; -}; - class KWIN_EXPORT TabletPadV2Interface : public QObject { Q_OBJECT diff --git a/src/wayland_server.cpp b/src/wayland_server.cpp index 491fb23bf6..d60e83d561 100644 --- a/src/wayland_server.cpp +++ b/src/wayland_server.cpp @@ -23,6 +23,7 @@ #include "wayland/appmenu_interface.h" #include "wayland/compositor_interface.h" #include "wayland/contenttype_v1_interface.h" +#include "wayland/cursorshape_v1_interface.h" #include "wayland/datacontroldevicemanager_v1_interface.h" #include "wayland/datadevicemanager_interface.h" #include "wayland/display.h" @@ -413,6 +414,7 @@ bool WaylandServer::init(InitializationFlags flags) new RelativePointerManagerV1Interface(m_display, m_display); m_dataDeviceManager = new DataDeviceManagerInterface(m_display, m_display); new DataControlDeviceManagerV1Interface(m_display, m_display); + new CursorShapeManagerV1Interface(m_display, m_display); const auto kwinConfig = kwinApp()->config(); if (kwinConfig->group("Wayland").readEntry("EnablePrimarySelection", true)) {