diff --git a/autotests/integration/pointer_input.cpp b/autotests/integration/pointer_input.cpp --- a/autotests/integration/pointer_input.cpp +++ b/autotests/integration/pointer_input.cpp @@ -27,11 +27,13 @@ #include "options.h" #include "screenedge.h" #include "screens.h" +#include "wayland_cursor_theme.h" #include "wayland_server.h" #include "workspace.h" #include "shell_client.h" #include +#include #include #include #include @@ -41,13 +43,59 @@ #include #include +#include +#include #include +#include + #include namespace KWin { +template +PlatformCursorImage loadReferenceThemeCursor(const T &shape) +{ + if (!waylandServer()->internalShmPool()) { + return PlatformCursorImage(); + } + + QScopedPointer cursorTheme; + cursorTheme.reset(new WaylandCursorTheme(waylandServer()->internalShmPool())); + + wl_cursor_image *cursor = cursorTheme->get(shape); + if (!cursor) { + return PlatformCursorImage(); + } + + wl_buffer *b = wl_cursor_image_get_buffer(cursor); + if (!b) { + return PlatformCursorImage(); + } + + waylandServer()->internalClientConection()->flush(); + waylandServer()->dispatch(); + + auto buffer = KWayland::Server::BufferInterface::get( + waylandServer()->internalConnection()->getResource( + KWayland::Client::Buffer::getId(b))); + if (!buffer) { + return PlatformCursorImage{}; + } + + const qreal scale = screens()->maxScale(); + QImage image = buffer->data().copy(); + image.setDevicePixelRatio(scale); + + const QPoint hotSpot( + qRound(cursor->hotspot_x / scale), + qRound(cursor->hotspot_y / scale) + ); + + return PlatformCursorImage(image, hotSpot); +} + static const QString s_socketName = QStringLiteral("wayland_test_kwin_pointer_input-0"); class PointerInputTest : public QObject @@ -80,6 +128,9 @@ void testWindowUnderCursorWhileButtonPressed(); void testConfineToScreenGeometry_data(); void testConfineToScreenGeometry(); + void testResizeCursor_data(); + void testResizeCursor(); + void testMoveCursor(); private: void render(KWayland::Client::Surface *surface, const QSize &size = QSize(100, 50)); @@ -1412,6 +1463,141 @@ QCOMPARE(Cursor::pos(), expectedPos); } +void PointerInputTest::testResizeCursor_data() +{ + QTest::addColumn("edges"); + QTest::addColumn("cursorShape"); + + QTest::newRow("top-left") << Qt::Edges(Qt::TopEdge | Qt::LeftEdge) << CursorShape(ExtendedCursor::SizeNorthWest); + QTest::newRow("top") << Qt::Edges(Qt::TopEdge) << CursorShape(ExtendedCursor::SizeNorth); + QTest::newRow("top-right") << Qt::Edges(Qt::TopEdge | Qt::RightEdge) << CursorShape(ExtendedCursor::SizeNorthEast); + QTest::newRow("right") << Qt::Edges(Qt::RightEdge) << CursorShape(ExtendedCursor::SizeEast); + QTest::newRow("bottom-right") << Qt::Edges(Qt::BottomEdge | Qt::RightEdge) << CursorShape(ExtendedCursor::SizeSouthEast); + QTest::newRow("bottom") << Qt::Edges(Qt::BottomEdge) << CursorShape(ExtendedCursor::SizeSouth); + QTest::newRow("bottom-left") << Qt::Edges(Qt::BottomEdge | Qt::LeftEdge) << CursorShape(ExtendedCursor::SizeSouthWest); + QTest::newRow("left") << Qt::Edges(Qt::LeftEdge) << CursorShape(ExtendedCursor::SizeWest); +} + +void PointerInputTest::testResizeCursor() +{ + // this test verifies that the cursor has correct shape during resize operation + + // first modify the config for this run + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", "Alt"); + group.writeEntry("CommandAll3", "Resize"); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->commandAllModifier(), Qt::AltModifier); + QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedResize); + + // create a test client + using namespace KWayland::Client; + QScopedPointer surface(Test::createSurface()); + QVERIFY(!surface.isNull()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + QVERIFY(!shellSurface.isNull()); + ShellClient *c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + + // move the cursor to the test position + QPoint cursorPos; + QFETCH(Qt::Edges, edges); + + if (edges & Qt::LeftEdge) { + cursorPos.setX(c->geometry().left()); + } else if (edges & Qt::RightEdge) { + cursorPos.setX(c->geometry().right()); + } else { + cursorPos.setX(c->geometry().center().x()); + } + + if (edges & Qt::TopEdge) { + cursorPos.setY(c->geometry().top()); + } else if (edges & Qt::BottomEdge) { + cursorPos.setY(c->geometry().bottom()); + } else { + cursorPos.setY(c->geometry().center().y()); + } + + Cursor::setPos(cursorPos); + + const PlatformCursorImage arrowCursor = loadReferenceThemeCursor(Qt::ArrowCursor); + QVERIFY(!arrowCursor.image().isNull()); + QCOMPARE(kwinApp()->platform()->cursorImage().image(), arrowCursor.image()); + QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), arrowCursor.hotSpot()); + + // start resizing the client + int timestamp = 1; + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->pointerButtonPressed(BTN_RIGHT, timestamp++); + QVERIFY(c->isResize()); + + QFETCH(KWin::CursorShape, cursorShape); + const PlatformCursorImage resizeCursor = loadReferenceThemeCursor(cursorShape); + QVERIFY(!resizeCursor.image().isNull()); + QCOMPARE(kwinApp()->platform()->cursorImage().image(), resizeCursor.image()); + QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), resizeCursor.hotSpot()); + + // finish resizing the client + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->pointerButtonReleased(BTN_RIGHT, timestamp++); + QVERIFY(!c->isResize()); + + QCOMPARE(kwinApp()->platform()->cursorImage().image(), arrowCursor.image()); + QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), arrowCursor.hotSpot()); +} + +void PointerInputTest::testMoveCursor() +{ + // this test verifies that the cursor has correct shape during move operation + + // first modify the config for this run + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", "Alt"); + group.writeEntry("CommandAll1", "Move"); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->commandAllModifier(), Qt::AltModifier); + QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); + + // create a test client + using namespace KWayland::Client; + QScopedPointer surface(Test::createSurface()); + QVERIFY(!surface.isNull()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + QVERIFY(!shellSurface.isNull()); + ShellClient *c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + + // move cursor to the test position + Cursor::setPos(c->geometry().center()); + + const PlatformCursorImage arrowCursor = loadReferenceThemeCursor(Qt::ArrowCursor); + QVERIFY(!arrowCursor.image().isNull()); + QCOMPARE(kwinApp()->platform()->cursorImage().image(), arrowCursor.image()); + QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), arrowCursor.hotSpot()); + + // start moving the client + int timestamp = 1; + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); + QVERIFY(c->isMove()); + + const PlatformCursorImage sizeAllCursor = loadReferenceThemeCursor(Qt::SizeAllCursor); + QVERIFY(!sizeAllCursor.image().isNull()); + QCOMPARE(kwinApp()->platform()->cursorImage().image(), sizeAllCursor.image()); + QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), sizeAllCursor.hotSpot()); + + // finish moving the client + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); + kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++); + QVERIFY(!c->isMove()); + + QCOMPARE(kwinApp()->platform()->cursorImage().image(), arrowCursor.image()); + QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), arrowCursor.hotSpot()); +} + } WAYLANDTEST_MAIN(KWin::PointerInputTest) diff --git a/cursor.h b/cursor.h --- a/cursor.h +++ b/cursor.h @@ -55,6 +55,7 @@ */ class KWIN_EXPORT CursorShape { public: + CursorShape() = default; CursorShape(Qt::CursorShape qtShape) { m_shape = qtShape; } @@ -300,4 +301,6 @@ } +Q_DECLARE_METATYPE(KWin::CursorShape) + #endif // KWIN_CURSOR_H diff --git a/pointer_input.cpp b/pointer_input.cpp --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -994,6 +994,7 @@ // connect the move resize of all window auto setupMoveResizeConnection = [this] (AbstractClient *c) { connect(c, &AbstractClient::moveResizedChanged, this, &CursorImage::updateMoveResize); + connect(c, &AbstractClient::moveResizeCursorChanged, this, &CursorImage::updateMoveResize); }; const auto clients = workspace()->allClientList(); std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection); @@ -1108,7 +1109,7 @@ m_moveResizeCursor.image = QImage(); m_moveResizeCursor.hotSpot = QPoint(); if (AbstractClient *c = workspace()->getMovingClient()) { - loadThemeCursor(c->isMove() ? Qt::SizeAllCursor : Qt::SizeBDiagCursor, &m_moveResizeCursor); + loadThemeCursor(c->cursor(), &m_moveResizeCursor); if (m_currentSource == CursorSource::MoveResize) { emit changed(); }