blob: e333082ecc776b485c520d5482128438cccba641 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mockcompositor.h"
#include <QtGui/QRasterWindow>
#include <QtGui/QOpenGLWindow>
using namespace MockCompositor;
class SeatV5Compositor : public DefaultCompositor {
public:
explicit SeatV5Compositor()
{
exec([this] {
m_config.autoConfigure = true;
removeAll<Seat>();
uint capabilities = MockCompositor::Seat::capability_pointer | MockCompositor::Seat::capability_touch;
int version = 5;
add<Seat>(capabilities, version);
});
}
};
class tst_seatv5 : public QObject, private SeatV5Compositor
{
Q_OBJECT
private slots:
void cleanup() { QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage())); }
void bindsToSeat();
// Pointer tests
void createsPointer();
void setsCursorOnEnter();
void usesEnterSerial();
void simpleAxis_data();
void simpleAxis();
void fingerScroll();
void fingerScrollSlow();
void wheelDiscreteScroll();
// Touch tests
void createsTouch();
void singleTap();
void singleTapFloat();
void multiTouch();
void multiTouchUpAndMotionFrame();
void tapAndMoveInSameFrame();
};
void tst_seatv5::bindsToSeat()
{
QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().size(), 1);
QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().first()->version(), 5);
}
void tst_seatv5::createsPointer()
{
QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1);
QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().first()->version(), 5);
}
void tst_seatv5::setsCursorOnEnter()
{
QRasterWindow window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *surface = xdgSurface()->m_surface;
pointer()->sendEnter(surface, {0, 0});
pointer()->sendFrame(surface->resource()->client());
});
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
}
void tst_seatv5::usesEnterSerial()
{
QSignalSpy setCursorSpy(exec([=] { return pointer(); }), &Pointer::setCursor);
QRasterWindow window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
uint enterSerial = exec([=] {
return pointer()->sendEnter(xdgSurface()->m_surface, {0, 0});
});
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
QTRY_COMPARE(setCursorSpy.count(), 1);
QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial);
}
class WheelWindow : QRasterWindow {
public:
WheelWindow()
{
resize(64, 64);
show();
}
void wheelEvent(QWheelEvent *event) override
{
QRasterWindow::wheelEvent(event);
// qDebug() << event << "angleDelta" << event->angleDelta() << "pixelDelta" << event->pixelDelta();
if (event->phase() != Qt::ScrollUpdate && event->phase() != Qt::NoScrollPhase) {
// Shouldn't have deltas in the these phases
QCOMPARE(event->angleDelta(), QPoint(0, 0));
QCOMPARE(event->pixelDelta(), QPoint(0, 0));
}
// The axis vector of the event is already in surface space, so there is now way to tell
// whether it is inverted or not.
QCOMPARE(event->inverted(), false);
// We didn't press any buttons
QCOMPARE(event->buttons(), Qt::NoButton);
m_events.append(Event{event});
}
struct Event // Because I didn't find a convenient way to copy it entirely
{
explicit Event() = default;
explicit Event(const QWheelEvent *event)
: phase(event->phase())
, pixelDelta(event->pixelDelta())
, angleDelta(event->angleDelta())
, source(event->source())
{
}
const Qt::ScrollPhase phase{};
const QPoint pixelDelta;
const QPoint angleDelta; // eights of a degree, positive is upwards, left
const Qt::MouseEventSource source{};
};
QVector<Event> m_events;
};
void tst_seatv5::simpleAxis_data()
{
QTest::addColumn<uint>("axis");
QTest::addColumn<qreal>("value");
QTest::addColumn<QPoint>("angleDelta");
// Directions in regular windows/linux terms (no "natural" scrolling)
QTest::newRow("down") << uint(Pointer::axis_vertical_scroll) << 1.0 << QPoint{0, -12};
QTest::newRow("up") << uint(Pointer::axis_vertical_scroll) << -1.0 << QPoint{0, 12};
QTest::newRow("left") << uint(Pointer::axis_horizontal_scroll) << 1.0 << QPoint{-12, 0};
QTest::newRow("right") << uint(Pointer::axis_horizontal_scroll) << -1.0 << QPoint{12, 0};
QTest::newRow("up big") << uint(Pointer::axis_vertical_scroll) << -10.0 << QPoint{0, 120};
}
void tst_seatv5::simpleAxis()
{
QFETCH(uint, axis);
QFETCH(qreal, value);
QFETCH(QPoint, angleDelta);
WheelWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *p = pointer();
p->sendEnter(xdgToplevel()->surface(), {32, 32});
p->sendFrame(client());
p->sendAxis(
client(),
Pointer::axis(axis),
value // Length of vector in surface-local space. i.e. positive is downwards
);
p->sendFrame(client());
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::NoScrollPhase);
// Pixel delta should only be set if we know it's a high-res input device (which we don't)
QCOMPARE(e.pixelDelta, QPoint(0, 0));
// There has been no information about what created the event.
// Documentation says not synthesized is appropriate in such cases
QCOMPARE(e.source, Qt::MouseEventNotSynthesized);
QCOMPARE(e.angleDelta, angleDelta);
}
// Sending axis_stop is not mandatory when axis source != finger
}
void tst_seatv5::fingerScroll()
{
WheelWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *p = pointer();
auto *c = client();
p->sendEnter(xdgToplevel()->surface(), {32, 32});
p->sendFrame(c);
p->sendAxisSource(c, Pointer::axis_source_finger);
p->sendAxis(c, Pointer::axis_vertical_scroll, 10);
p->sendFrame(c);
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::ScrollBegin);
QCOMPARE(e.angleDelta, QPoint());
QCOMPARE(e.pixelDelta, QPoint());
}
QTRY_VERIFY(!window.m_events.empty());
// For some reason we send two ScrollBegins, one for each direction, not sure if this is really
// necessary, (could be removed from QtBase, hence the conditional below.
if (window.m_events.first().phase == Qt::ScrollBegin) {
auto e = window.m_events.takeFirst();
QCOMPARE(e.angleDelta, QPoint());
QCOMPARE(e.pixelDelta, QPoint());
}
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::ScrollUpdate);
QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll
// QCOMPARE(e.angleDelta, angleDelta); // TODO: what should this be?
QCOMPARE(e.pixelDelta, QPoint(0, 10));
QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel
}
QTRY_VERIFY(window.m_events.empty());
// Scroll horizontally as well
exec([=] {
pointer()->sendAxisSource(client(), Pointer::axis_source_finger);
pointer()->sendAxis(client(), Pointer::axis_horizontal_scroll, 10);
pointer()->sendFrame(client());
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::ScrollUpdate);
QVERIFY(qAbs(e.angleDelta.x()) > qAbs(e.angleDelta.y())); // Horizontal scroll
QCOMPARE(e.pixelDelta, QPoint(10, 0));
QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel
}
// Scroll diagonally
exec([=] {
pointer()->sendAxisSource(client(), Pointer::axis_source_finger);
pointer()->sendAxis(client(), Pointer::axis_horizontal_scroll, 10);
pointer()->sendAxis(client(), Pointer::axis_vertical_scroll, 10);
pointer()->sendFrame(client());
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::ScrollUpdate);
QCOMPARE(e.pixelDelta, QPoint(10, 10));
QCOMPARE(e.source, Qt::MouseEventSynthesizedBySystem); // A finger is not a wheel
}
// For diagonal events, Qt sends an additional compatibility ScrollUpdate event
if (window.m_events.first().phase == Qt::ScrollUpdate) {
auto e = window.m_events.takeFirst();
QCOMPARE(e.angleDelta, QPoint());
QCOMPARE(e.pixelDelta, QPoint());
}
QVERIFY(window.m_events.empty());
// Sending axis_stop is mandatory when axis source == finger
exec([=] {
pointer()->sendAxisStop(client(), Pointer::axis_vertical_scroll);
pointer()->sendFrame(client());
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::ScrollEnd);
}
}
void tst_seatv5::fingerScrollSlow()
{
WheelWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *p = pointer();
auto *c = client();
p->sendEnter(xdgToplevel()->surface(), {32, 32});
p->sendFrame(c);
// Send 10 really small updates
for (int i = 0; i < 10; ++i) {
p->sendAxisSource(c, Pointer::axis_source_finger);
p->sendAxis(c, Pointer::axis_vertical_scroll, 0.1);
p->sendFrame(c);
}
p->sendAxisStop(c, Pointer::axis_vertical_scroll);
p->sendFrame(c);
});
QTRY_VERIFY(!window.m_events.empty());
QPoint accumulated;
while (window.m_events.first().phase != Qt::ScrollEnd) {
auto e = window.m_events.takeFirst();
accumulated += e.pixelDelta;
QTRY_VERIFY(!window.m_events.empty());
}
QCOMPARE(accumulated.y(), 1);
}
void tst_seatv5::wheelDiscreteScroll()
{
WheelWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *p = pointer();
auto *c = client();
p->sendEnter(xdgToplevel()->surface(), {32, 32});
p->sendFrame(c);
p->sendAxisSource(c, Pointer::axis_source_wheel);
p->sendAxisDiscrete(c, Pointer::axis_vertical_scroll, 1); // 1 click downwards
p->sendAxis(c, Pointer::axis_vertical_scroll, 1.0);
p->sendFrame(c);
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.phase, Qt::NoScrollPhase);
QVERIFY(qAbs(e.angleDelta.x()) <= qAbs(e.angleDelta.y())); // Vertical scroll
// According to the docs the angle delta is in eights of a degree and most mice have
// 1 click = 15 degrees. The angle delta should therefore be:
// 15 degrees / (1/8 eights per degrees) = 120 eights of degrees.
QCOMPARE(e.angleDelta, QPoint(0, -120));
// Click scrolls are not continuous and should not have a pixel delta
QCOMPARE(e.pixelDelta, QPoint(0, 0));
}
}
void tst_seatv5::createsTouch()
{
QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().size(), 1);
QCOMPOSITOR_TRY_COMPARE(touch()->resourceMap().first()->version(), 5);
}
class TouchWindow : public QRasterWindow {
public:
TouchWindow()
{
resize(64, 64);
show();
}
void touchEvent(QTouchEvent *event) override
{
QRasterWindow::touchEvent(event);
m_events.append(Event{event});
}
struct Event // Because I didn't find a convenient way to copy it entirely
{
explicit Event() = default;
explicit Event(const QTouchEvent *event)
: type(event->type())
, touchPointStates(event->touchPointStates())
, touchPoints(event->touchPoints())
{
}
const QEvent::Type type{};
const Qt::TouchPointStates touchPointStates{};
const QList<QTouchEvent::TouchPoint> touchPoints;
};
QVector<Event> m_events;
};
void tst_seatv5::singleTap()
{
TouchWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *t = touch();
auto *c = client();
t->sendDown(xdgToplevel()->surface(), {32, 32}, 1);
t->sendFrame(c);
t->sendUp(c, 1);
t->sendFrame(c);
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.type, QEvent::TouchBegin);
QCOMPARE(e.touchPointStates, Qt::TouchPointState::TouchPointPressed);
QCOMPARE(e.touchPoints.length(), 1);
QCOMPARE(e.touchPoints.first().pos(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top()));
}
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.type, QEvent::TouchEnd);
QCOMPARE(e.touchPointStates, Qt::TouchPointState::TouchPointReleased);
QCOMPARE(e.touchPoints.length(), 1);
QCOMPARE(e.touchPoints.first().pos(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top()));
}
}
void tst_seatv5::singleTapFloat()
{
TouchWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *t = touch();
auto *c = client();
t->sendDown(xdgToplevel()->surface(), {32.75, 32.25}, 1);
t->sendFrame(c);
t->sendUp(c, 1);
t->sendFrame(c);
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.type, QEvent::TouchBegin);
QCOMPARE(e.touchPointStates, Qt::TouchPointState::TouchPointPressed);
QCOMPARE(e.touchPoints.length(), 1);
QCOMPARE(e.touchPoints.first().pos(), QPointF(32.75-window.frameMargins().left(), 32.25-window.frameMargins().top()));
}
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.type, QEvent::TouchEnd);
QCOMPARE(e.touchPointStates, Qt::TouchPointState::TouchPointReleased);
QCOMPARE(e.touchPoints.length(), 1);
QCOMPARE(e.touchPoints.first().pos(), QPointF(32.75-window.frameMargins().left(), 32.25-window.frameMargins().top()));
}
}
void tst_seatv5::multiTouch()
{
TouchWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *t = touch();
auto *c = client();
t->sendDown(xdgToplevel()->surface(), {32, 32}, 0);
t->sendDown(xdgToplevel()->surface(), {48, 48}, 1);
t->sendFrame(c);
// Compositor event order should not change the order of the QTouchEvent::touchPoints()
// See QTBUG-77014
t->sendMotion(c, {49, 48}, 1);
t->sendMotion(c, {33, 32}, 0);
t->sendFrame(c);
t->sendUp(c, 0);
t->sendFrame(c);
t->sendUp(c, 1);
t->sendFrame(c);
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.type, QEvent::TouchBegin);
QCOMPARE(e.touchPointStates, Qt::TouchPointState::TouchPointPressed);
QCOMPARE(e.touchPoints.length(), 2);
QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointPressed);
QCOMPARE(e.touchPoints[0].pos(), QPointF(32-window.frameMargins().left(), 32-window.frameMargins().top()));
QCOMPARE(e.touchPoints[1].state(), Qt::TouchPointState::TouchPointPressed);
QCOMPARE(e.touchPoints[1].pos(), QPointF(48-window.frameMargins().left(), 48-window.frameMargins().top()));
}
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.type, QEvent::TouchUpdate);
QCOMPARE(e.touchPoints.length(), 2);
QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointMoved);
QCOMPARE(e.touchPoints[0].pos(), QPointF(33-window.frameMargins().left(), 32-window.frameMargins().top()));
QCOMPARE(e.touchPoints[1].state(), Qt::TouchPointState::TouchPointMoved);
QCOMPARE(e.touchPoints[1].pos(), QPointF(49-window.frameMargins().left(), 48-window.frameMargins().top()));
}
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.type, QEvent::TouchUpdate);
QCOMPARE(e.touchPointStates, Qt::TouchPointState::TouchPointReleased | Qt::TouchPointState::TouchPointStationary);
QCOMPARE(e.touchPoints.length(), 2);
QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointReleased);
QCOMPARE(e.touchPoints[0].pos(), QPointF(33-window.frameMargins().left(), 32-window.frameMargins().top()));
QCOMPARE(e.touchPoints[1].state(), Qt::TouchPointState::TouchPointStationary);
QCOMPARE(e.touchPoints[1].pos(), QPointF(49-window.frameMargins().left(), 48-window.frameMargins().top()));
}
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.type, QEvent::TouchEnd);
QCOMPARE(e.touchPointStates, Qt::TouchPointState::TouchPointReleased);
QCOMPARE(e.touchPoints.length(), 1);
QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointReleased);
QCOMPARE(e.touchPoints[0].pos(), QPointF(49-window.frameMargins().left(), 48-window.frameMargins().top()));
}
}
void tst_seatv5::multiTouchUpAndMotionFrame()
{
TouchWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *t = touch();
auto *c = client();
t->sendDown(xdgToplevel()->surface(), {32, 32}, 0);
t->sendDown(xdgToplevel()->surface(), {48, 48}, 1);
t->sendFrame(c);
// Sending an up event after a frame event, before any motion or down events used to
// unnecessarily trigger a workaround for a bug in an old version of Weston. The workaround
// would prematurely insert a fake frame event splitting the touch event up into two events.
// However, this should only be needed on the up event for the very last touch point. So in
// this test we verify that it doesn't unncecessarily break up the events.
t->sendUp(c, 0);
t->sendMotion(c, {49, 48}, 1);
t->sendFrame(c);
t->sendUp(c, 1);
t->sendFrame(c);
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.type, QEvent::TouchBegin);
QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointPressed);
QCOMPARE(e.touchPoints[1].state(), Qt::TouchPointState::TouchPointPressed);
}
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.type, QEvent::TouchUpdate);
QCOMPARE(e.touchPoints.length(), 2);
QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointReleased);
QCOMPARE(e.touchPoints[1].state(), Qt::TouchPointState::TouchPointMoved);
}
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.type, QEvent::TouchEnd);
QCOMPARE(e.touchPoints.length(), 1);
QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointReleased);
}
QVERIFY(window.m_events.empty());
}
void tst_seatv5::tapAndMoveInSameFrame()
{
TouchWindow window;
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] {
auto *t = touch();
auto *c = client();
t->sendDown(xdgToplevel()->surface(), {32, 32}, 0);
t->sendMotion(c, {33, 33}, 0);
t->sendFrame(c);
// Don't leave touch in a weird state
t->sendUp(c, 0);
t->sendFrame(c);
});
QTRY_VERIFY(!window.m_events.empty());
{
auto e = window.m_events.takeFirst();
QCOMPARE(e.type, QEvent::TouchBegin);
QCOMPARE(e.touchPoints.size(), 1);
QCOMPARE(e.touchPoints[0].state(), Qt::TouchPointState::TouchPointPressed);
// Position isn't that important, we just want to make sure we actually get the pressed event
}
// Make sure we eventually release
QTRY_VERIFY(!window.m_events.empty());
QTRY_COMPARE(window.m_events.last().touchPoints.first().state(), Qt::TouchPointState::TouchPointReleased);
}
QCOMPOSITOR_TEST_MAIN(tst_seatv5)
#include "tst_seatv5.moc"