diff --git a/autotests/integration/pointer_input.cpp b/autotests/integration/pointer_input.cpp
index dfc859ff9f..1e7a45ac43 100644
--- a/autotests/integration/pointer_input.cpp
+++ b/autotests/integration/pointer_input.cpp
@@ -27,11 +27,13 @@ along with this program. If not, see .
#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 @@ along with this program. If not, see .
#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 @@ private Q_SLOTS:
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 @@ void PointerInputTest::testConfineToScreenGeometry()
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
index dc4eefaeac..90a4c7a36a 100644
--- a/cursor.h
+++ b/cursor.h
@@ -55,6 +55,7 @@ enum Shape {
*/
class KWIN_EXPORT CursorShape {
public:
+ CursorShape() = default;
CursorShape(Qt::CursorShape qtShape) {
m_shape = qtShape;
}
@@ -300,4 +301,6 @@ inline bool Cursor::isCursorTracking() const
}
+Q_DECLARE_METATYPE(KWin::CursorShape)
+
#endif // KWIN_CURSOR_H
diff --git a/pointer_input.cpp b/pointer_input.cpp
index eb1b203f85..8f6f462d85 100644
--- a/pointer_input.cpp
+++ b/pointer_input.cpp
@@ -994,6 +994,7 @@ CursorImage::CursorImage(PointerInputRedirection *parent)
// 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 @@ void CursorImage::updateMoveResize()
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();
}