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 @@ -22,6 +22,7 @@ #include "abstract_client.h" #include "cursor.h" #include "deleted.h" +#include "effects.h" #include "pointer_input.h" #include "options.h" #include "screenedge.h" @@ -57,6 +58,7 @@ void cleanup(); void testWarpingUpdatesFocus(); void testWarpingGeneratesPointerMotion(); + void testWarpingDuringFilter(); void testUpdateFocusAfterScreenChange(); void testModifierClickUnrestrictedMove_data(); void testModifierClickUnrestrictedMove(); @@ -211,6 +213,50 @@ QCOMPARE(movedSpy.last().first().toPointF(), QPointF(26, 26)); } +void PointerInputTest::testWarpingDuringFilter() +{ + // this test verifies that pointer motion is handled correctly if + // the pointer gets warped during processing of input events + using namespace KWayland::Client; + + // create pointer + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy movedSpy(pointer, &Pointer::motion); + QVERIFY(movedSpy.isValid()); + + // warp cursor into expected geometry + Cursor::setPos(10, 10); + + // create a window + QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QVERIFY(clientAddedSpy.isValid()); + Surface *surface = Test::createSurface(m_compositor); + QVERIFY(surface); + ShellSurface *shellSurface = Test::createShellSurface(surface, surface); + QVERIFY(shellSurface); + render(surface); + QVERIFY(clientAddedSpy.wait()); + AbstractClient *window = workspace()->activeClient(); + QVERIFY(window); + + QCOMPARE(window->pos(), QPoint(0, 0)); + QVERIFY(window->geometry().contains(Cursor::pos())); + + // is PresentWindows effect for top left screen edge loaded + QVERIFY(static_cast(effects)->isEffectLoaded("presentwindows")); + QVERIFY(movedSpy.isEmpty()); + quint32 timestamp = 0; + kwinApp()->platform()->pointerMotion(QPoint(0, 0), timestamp++); + // screen edges push back + QCOMPARE(Cursor::pos(), QPoint(1, 1)); + QVERIFY(movedSpy.wait()); + QCOMPARE(movedSpy.count(), 2); + QCOMPARE(movedSpy.at(0).first().toPoint(), QPoint(0, 0)); + QCOMPARE(movedSpy.at(1).first().toPoint(), QPoint(1, 1)); +} + void PointerInputTest::testUpdateFocusAfterScreenChange() { // this test verifies that a pointer enter event is generated when the cursor changes to another diff --git a/pointer_input.cpp b/pointer_input.cpp --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -220,11 +220,60 @@ processMotion(pos, QSizeF(), QSizeF(), time, 0, device); } +class PositionUpdateBlocker +{ +public: + PositionUpdateBlocker(PointerInputRedirection *pointer) + : m_pointer(pointer) + { + s_counter++; + } + ~PositionUpdateBlocker() { + s_counter--; + if (s_counter == 0) { + if (!s_scheduledPositions.isEmpty()) { + const auto pos = s_scheduledPositions.takeFirst(); + m_pointer->processMotion(pos.pos, pos.delta, pos.deltaNonAccelerated, pos.time, pos.timeUsec, nullptr); + } + } + } + + static bool isPositionBlocked() { + return s_counter > 0; + } + + static void schedulePosition(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec) { + s_scheduledPositions.append({pos, delta, deltaNonAccelerated, time, timeUsec}); + } + +private: + static int s_counter; + struct ScheduledPosition { + QPointF pos; + QSizeF delta; + QSizeF deltaNonAccelerated; + quint32 time; + quint64 timeUsec; + }; + static QVector s_scheduledPositions; + + PointerInputRedirection *m_pointer; +}; + +int PositionUpdateBlocker::s_counter = 0; +QVector PositionUpdateBlocker::s_scheduledPositions; + void PointerInputRedirection::processMotion(const QPointF &pos, const QSizeF &delta, const QSizeF &deltaNonAccelerated, uint32_t time, quint64 timeUsec, LibInput::Device *device) { if (!m_inited) { return; } + if (PositionUpdateBlocker::isPositionBlocked()) { + PositionUpdateBlocker::schedulePosition(pos, delta, deltaNonAccelerated, time, timeUsec); + return; + } + + PositionUpdateBlocker blocker(this); updatePosition(pos); MouseEvent event(QEvent::MouseMove, m_pos, Qt::NoButton, m_qtButtons, m_input->keyboardModifiers(), time,