[wayland] Don't use hardcoded move-resize cursor

Summary:
Currently, when resizing a window the cursor doesn't match the resize
direction. The reason for that is the move-resize cursor is hardcoded.

To fix that, CursorImage::updateMoveResize has to use AbstractClient::cursor.
Also, because the move-resize cursor is updated after calling startMoveResize,
we have to connect to AbstractClient::moveResizeCursorChanged.

BUG: 370339
FIXED-IN: 5.15

Reviewers: #kwin, davidedmundson, broulik, romangg, graesslin

Reviewed By: #kwin, graesslin

Subscribers: davidedmundson, romangg, graesslin, kwin

Tags: #kwin

Maniphest Tasks: T5714

Differential Revision: https://phabricator.kde.org/D3202
icc-effect-5.17.5
Vlad Zagorodniy 2018-12-25 17:30:38 +02:00
parent 792d840455
commit 1ca2aec77f
3 changed files with 191 additions and 1 deletions

View File

@ -27,11 +27,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#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 <kwineffects.h>
#include <KWayland/Client/buffer.h>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/pointer.h>
@ -41,13 +43,59 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KWayland/Client/shm_pool.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Server/buffer_interface.h>
#include <KWayland/Server/clientconnection.h>
#include <KWayland/Server/seat_interface.h>
#include <wayland-cursor.h>
#include <linux/input.h>
namespace KWin
{
template <typename T>
PlatformCursorImage loadReferenceThemeCursor(const T &shape)
{
if (!waylandServer()->internalShmPool()) {
return PlatformCursorImage();
}
QScopedPointer<WaylandCursorTheme> 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<Qt::Edges>("edges");
QTest::addColumn<KWin::CursorShape>("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> surface(Test::createSurface());
QVERIFY(!surface.isNull());
QScopedPointer<XdgShellSurface> 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> surface(Test::createSurface());
QVERIFY(!surface.isNull());
QScopedPointer<XdgShellSurface> 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)

View File

@ -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

View File

@ -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();
}