kwin/autotests/wayland/client/test_wayland_seat.cpp
Aleix Pol Gonzalez 6c4ded2034 input: Allow different surfaces to be interacted in parallel by touch
While on pointers and keyboards the focus patterns follows rather
naturally, on touch screens it doesn't so much.
This change adapts our touch infrastructure to allow for multiple
surfaces to be issued touch events without forcing all interactions into
the same one.

Signed-off-by: Victoria Fischer <victoria.fischer@mercedes-benz.com>
2024-08-28 09:36:51 +00:00

1953 lines
81 KiB
C++

/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
// Qt
#include <QMimeType>
#include <QSignalSpy>
#include <QTest>
// KWin
#include "wayland/compositor.h"
#include "wayland/datadevicemanager.h"
#include "wayland/datasource.h"
#include "wayland/display.h"
#include "wayland/keyboard.h"
#include "wayland/pointer.h"
#include "wayland/pointergestures_v1.h"
#include "wayland/relativepointer_v1.h"
#include "wayland/seat.h"
#include "wayland/subcompositor.h"
#include "wayland/surface.h"
#include "KWayland/Client/compositor.h"
#include "KWayland/Client/connection_thread.h"
#include "KWayland/Client/datadevice.h"
#include "KWayland/Client/datadevicemanager.h"
#include "KWayland/Client/datasource.h"
#include "KWayland/Client/event_queue.h"
#include "KWayland/Client/keyboard.h"
#include "KWayland/Client/pointer.h"
#include "KWayland/Client/pointergestures.h"
#include "KWayland/Client/registry.h"
#include "KWayland/Client/relativepointer.h"
#include "KWayland/Client/seat.h"
#include "KWayland/Client/shm_pool.h"
#include "KWayland/Client/subcompositor.h"
#include "KWayland/Client/subsurface.h"
#include "KWayland/Client/surface.h"
#include "KWayland/Client/touch.h"
// Wayland
#include "qwayland-pointer-gestures-unstable-v1.h"
#include <wayland-client-protocol.h>
#include <linux/input.h>
// System
#include <fcntl.h>
#include <unistd.h>
using namespace std::literals;
class WaylandSyncPoint : public QObject
{
Q_OBJECT
public:
explicit WaylandSyncPoint(KWayland::Client::ConnectionThread *connection, KWayland::Client::EventQueue *eventQueue)
{
static const wl_callback_listener listener = {
.done = [](void *data, wl_callback *callback, uint32_t callback_data) {
auto syncPoint = static_cast<WaylandSyncPoint *>(data);
Q_EMIT syncPoint->done();
},
};
m_callback = wl_display_sync(connection->display());
eventQueue->addProxy(m_callback);
wl_callback_add_listener(m_callback, &listener, this);
}
~WaylandSyncPoint() override
{
wl_callback_destroy(m_callback);
}
Q_SIGNALS:
void done();
private:
wl_callback *m_callback;
};
class TestWaylandSeat : public QObject
{
Q_OBJECT
public:
explicit TestWaylandSeat(QObject *parent = nullptr);
private Q_SLOTS:
void init();
void cleanup();
void testName();
void testCapabilities_data();
void testCapabilities();
void testPointer();
void testPointerTransformation_data();
void testPointerTransformation();
void testPointerButton_data();
void testPointerButton();
void testPointerSubSurfaceTree();
void testPointerSwipeGesture_data();
void testPointerSwipeGesture();
void testPointerPinchGesture_data();
void testPointerPinchGesture();
void testPointerHoldGesture_data();
void testPointerHoldGesture();
void testPointerAxis();
void testCursor();
void testKeyboard();
void testSelection();
void testDataDeviceForKeyboardSurface();
void testTouch();
void testKeymap();
private:
bool sync();
KWin::Display *m_display;
KWin::CompositorInterface *m_compositorInterface;
KWin::SeatInterface *m_seatInterface;
KWin::SubCompositorInterface *m_subCompositorInterface;
KWin::RelativePointerManagerV1Interface *m_relativePointerManagerV1Interface;
KWin::PointerGesturesV1Interface *m_pointerGesturesV1Interface;
KWayland::Client::ConnectionThread *m_connection;
KWayland::Client::Compositor *m_compositor;
KWayland::Client::Seat *m_seat;
KWayland::Client::ShmPool *m_shm;
KWayland::Client::SubCompositor *m_subCompositor;
KWayland::Client::RelativePointerManager *m_relativePointerManager;
KWayland::Client::PointerGestures *m_pointerGestures;
KWayland::Client::EventQueue *m_queue;
QThread *m_thread;
};
static const QString s_socketName = QStringLiteral("kwin-test-wayland-seat-0");
TestWaylandSeat::TestWaylandSeat(QObject *parent)
: QObject(parent)
, m_display(nullptr)
, m_compositorInterface(nullptr)
, m_seatInterface(nullptr)
, m_subCompositorInterface(nullptr)
, m_relativePointerManagerV1Interface(nullptr)
, m_pointerGesturesV1Interface(nullptr)
, m_connection(nullptr)
, m_compositor(nullptr)
, m_seat(nullptr)
, m_shm(nullptr)
, m_subCompositor(nullptr)
, m_relativePointerManager(nullptr)
, m_pointerGestures(nullptr)
, m_queue(nullptr)
, m_thread(nullptr)
{
}
void TestWaylandSeat::init()
{
using namespace KWin;
delete m_display;
m_display = new KWin::Display(this);
m_display->addSocketName(s_socketName);
m_display->start();
QVERIFY(m_display->isRunning());
m_display->createShm();
m_compositorInterface = new CompositorInterface(m_display, m_display);
m_subCompositorInterface = new SubCompositorInterface(m_display, m_display);
QVERIFY(m_subCompositorInterface);
m_relativePointerManagerV1Interface = new RelativePointerManagerV1Interface(m_display, m_display);
m_pointerGesturesV1Interface = new PointerGesturesV1Interface(m_display, m_display);
// setup connection
m_connection = new KWayland::Client::ConnectionThread;
QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected);
m_connection->setSocketName(s_socketName);
m_thread = new QThread(this);
m_connection->moveToThread(m_thread);
m_thread->start();
m_connection->initConnection();
QVERIFY(connectedSpy.wait());
m_queue = new KWayland::Client::EventQueue(this);
m_queue->setup(m_connection);
KWayland::Client::Registry registry;
QSignalSpy compositorSpy(&registry, &KWayland::Client::Registry::compositorAnnounced);
QSignalSpy seatSpy(&registry, &KWayland::Client::Registry::seatAnnounced);
QSignalSpy shmSpy(&registry, &KWayland::Client::Registry::shmAnnounced);
registry.setEventQueue(m_queue);
registry.create(m_connection->display());
QVERIFY(registry.isValid());
registry.setup();
QVERIFY(compositorSpy.wait());
m_seatInterface = new SeatInterface(m_display, m_display);
QVERIFY(m_seatInterface);
m_seatInterface->setName(QStringLiteral("seat0"));
QVERIFY(seatSpy.wait());
m_compositor = new KWayland::Client::Compositor(this);
m_compositor->setup(registry.bindCompositor(compositorSpy.first().first().value<quint32>(), compositorSpy.first().last().value<quint32>()));
QVERIFY(m_compositor->isValid());
m_seat = registry.createSeat(seatSpy.first().first().value<quint32>(), seatSpy.first().last().value<quint32>(), this);
QSignalSpy nameSpy(m_seat, &KWayland::Client::Seat::nameChanged);
QVERIFY(nameSpy.wait());
m_shm = new KWayland::Client::ShmPool(this);
m_shm->setup(registry.bindShm(shmSpy.first().first().value<quint32>(), shmSpy.first().last().value<quint32>()));
QVERIFY(m_shm->isValid());
m_subCompositor = registry.createSubCompositor(registry.interface(KWayland::Client::Registry::Interface::SubCompositor).name,
registry.interface(KWayland::Client::Registry::Interface::SubCompositor).version,
this);
QVERIFY(m_subCompositor->isValid());
m_relativePointerManager =
registry.createRelativePointerManager(registry.interface(KWayland::Client::Registry::Interface::RelativePointerManagerUnstableV1).name,
registry.interface(KWayland::Client::Registry::Interface::RelativePointerManagerUnstableV1).version,
this);
QVERIFY(m_relativePointerManager->isValid());
m_pointerGestures = registry.createPointerGestures(registry.interface(KWayland::Client::Registry::Interface::PointerGesturesUnstableV1).name,
registry.interface(KWayland::Client::Registry::Interface::PointerGesturesUnstableV1).version,
this);
QVERIFY(m_pointerGestures->isValid());
}
void TestWaylandSeat::cleanup()
{
if (m_pointerGestures) {
delete m_pointerGestures;
m_pointerGestures = nullptr;
}
if (m_relativePointerManager) {
delete m_relativePointerManager;
m_relativePointerManager = nullptr;
}
if (m_subCompositor) {
delete m_subCompositor;
m_subCompositor = nullptr;
}
if (m_shm) {
delete m_shm;
m_shm = nullptr;
}
if (m_seat) {
delete m_seat;
m_seat = nullptr;
}
if (m_compositor) {
delete m_compositor;
m_compositor = nullptr;
}
if (m_queue) {
delete m_queue;
m_queue = nullptr;
}
if (m_connection) {
m_connection->deleteLater();
m_connection = nullptr;
}
if (m_thread) {
m_thread->quit();
m_thread->wait();
delete m_thread;
m_thread = nullptr;
}
delete m_display;
m_display = nullptr;
// these are the children of the display
m_compositorInterface = nullptr;
m_seatInterface = nullptr;
m_subCompositorInterface = nullptr;
m_relativePointerManagerV1Interface = nullptr;
m_pointerGesturesV1Interface = nullptr;
}
bool TestWaylandSeat::sync()
{
WaylandSyncPoint syncPoint(m_connection, m_queue);
QSignalSpy doneSpy(&syncPoint, &WaylandSyncPoint::done);
return doneSpy.wait();
}
void TestWaylandSeat::testName()
{
// no name set yet
QCOMPARE(m_seat->name(), QStringLiteral("seat0"));
QSignalSpy spy(m_seat, &KWayland::Client::Seat::nameChanged);
const QString name = QStringLiteral("foobar");
m_seatInterface->setName(name);
QVERIFY(spy.wait());
QCOMPARE(m_seat->name(), name);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.first().first().toString(), name);
}
void TestWaylandSeat::testCapabilities_data()
{
QTest::addColumn<bool>("pointer");
QTest::addColumn<bool>("keyboard");
QTest::addColumn<bool>("touch");
QTest::newRow("none") << false << false << false;
QTest::newRow("pointer") << true << false << false;
QTest::newRow("keyboard") << false << true << false;
QTest::newRow("touch") << false << false << true;
QTest::newRow("pointer/keyboard") << true << true << false;
QTest::newRow("pointer/touch") << true << false << true;
QTest::newRow("keyboard/touch") << false << true << true;
QTest::newRow("all") << true << true << true;
}
void TestWaylandSeat::testCapabilities()
{
QVERIFY(!m_seat->hasPointer());
QVERIFY(!m_seat->hasKeyboard());
QVERIFY(!m_seat->hasTouch());
QFETCH(bool, pointer);
QFETCH(bool, keyboard);
QFETCH(bool, touch);
QSignalSpy pointerSpy(m_seat, &KWayland::Client::Seat::hasPointerChanged);
QSignalSpy keyboardSpy(m_seat, &KWayland::Client::Seat::hasKeyboardChanged);
QSignalSpy touchSpy(m_seat, &KWayland::Client::Seat::hasTouchChanged);
m_seatInterface->setHasPointer(pointer);
m_seatInterface->setHasKeyboard(keyboard);
m_seatInterface->setHasTouch(touch);
QVERIFY(sync());
// do processing
QCOMPARE(pointerSpy.isEmpty(), !pointer);
if (!pointerSpy.isEmpty()) {
QCOMPARE(pointerSpy.first().first().toBool(), pointer);
}
QCOMPARE(keyboardSpy.isEmpty(), !keyboard);
if (!keyboardSpy.isEmpty()) {
QCOMPARE(keyboardSpy.first().first().toBool(), keyboard);
}
QCOMPARE(touchSpy.isEmpty(), !touch);
if (!touchSpy.isEmpty()) {
QCOMPARE(touchSpy.first().first().toBool(), touch);
}
QCOMPARE(m_seat->hasPointer(), pointer);
QCOMPARE(m_seat->hasKeyboard(), keyboard);
QCOMPARE(m_seat->hasTouch(), touch);
}
void TestWaylandSeat::testPointer()
{
using namespace KWin;
QSignalSpy pointerSpy(m_seat, &KWayland::Client::Seat::hasPointerChanged);
m_seatInterface->setHasPointer(true);
QVERIFY(pointerSpy.wait());
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &KWin::CompositorInterface::surfaceCreated);
KWayland::Client::Surface *s = m_compositor->createSurface(m_compositor);
QVERIFY(surfaceCreatedSpy.wait());
SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value<KWin::SurfaceInterface *>();
QVERIFY(serverSurface);
QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::black);
s->attachBuffer(m_shm->createBuffer(image));
s->damage(image.rect());
s->commit(KWayland::Client::Surface::CommitFlag::None);
QSignalSpy committedSpy(serverSurface, &KWin::SurfaceInterface::committed);
QVERIFY(committedSpy.wait());
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(20, 18), QPointF(10, 15));
KWayland::Client::Pointer *p = m_seat->createPointer(m_seat);
QSignalSpy frameSpy(p, &KWayland::Client::Pointer::frame);
const KWayland::Client::Pointer &cp = *p;
QVERIFY(p->isValid());
std::unique_ptr<KWayland::Client::RelativePointer> relativePointer(m_relativePointerManager->createRelativePointer(p));
QVERIFY(relativePointer->isValid());
QVERIFY(frameSpy.wait());
QCOMPARE(frameSpy.count(), 1);
m_seatInterface->notifyPointerLeave();
serverSurface->client()->flush();
QVERIFY(frameSpy.wait());
QCOMPARE(frameSpy.count(), 2);
QSignalSpy enteredSpy(p, &KWayland::Client::Pointer::entered);
QSignalSpy leftSpy(p, &KWayland::Client::Pointer::left);
QSignalSpy motionSpy(p, &KWayland::Client::Pointer::motion);
QSignalSpy axisSpy(p, &KWayland::Client::Pointer::axisChanged);
QSignalSpy buttonSpy(p, &KWayland::Client::Pointer::buttonStateChanged);
QSignalSpy relativeMotionSpy(relativePointer.get(), &KWayland::Client::RelativePointer::relativeMotion);
QVERIFY(!p->enteredSurface());
QVERIFY(!cp.enteredSurface());
uint32_t serial = m_display->serial();
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(20, 18), QPointF(10, 15));
QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface);
QVERIFY(enteredSpy.wait());
QCOMPARE_GT(enteredSpy.first().first().value<quint32>(), serial);
QCOMPARE(enteredSpy.first().last().toPoint(), QPoint(10, 3));
QCOMPARE(frameSpy.count(), 3);
QCOMPARE(p->enteredSurface(), s);
QCOMPARE(cp.enteredSurface(), s);
auto timestamp = 1ms;
// test motion
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerMotion(QPoint(10, 16));
m_seatInterface->notifyPointerFrame();
QVERIFY(motionSpy.wait());
QCOMPARE(frameSpy.count(), 4);
QCOMPARE(motionSpy.first().first().toPoint(), QPoint(0, 1));
QCOMPARE(motionSpy.first().last().value<quint32>(), quint32(1));
// test relative motion
m_seatInterface->relativePointerMotion(QPointF(1, 2), QPointF(3, 4), 1234us);
m_seatInterface->notifyPointerFrame();
QVERIFY(relativeMotionSpy.wait());
QCOMPARE(relativeMotionSpy.count(), 1);
QCOMPARE(frameSpy.count(), 5);
QCOMPARE(relativeMotionSpy.first().at(0).toSizeF(), QSizeF(1, 2));
QCOMPARE(relativeMotionSpy.first().at(1).toSizeF(), QSizeF(3, 4));
QCOMPARE(relativeMotionSpy.first().at(2).value<quint64>(), 1234);
// test axis
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerAxis(Qt::Horizontal, 10, 120, PointerAxisSource::Wheel);
m_seatInterface->notifyPointerFrame();
QVERIFY(axisSpy.wait());
QCOMPARE(frameSpy.count(), 6);
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerAxis(Qt::Vertical, 20, 240, PointerAxisSource::Wheel);
m_seatInterface->notifyPointerFrame();
QVERIFY(axisSpy.wait());
QCOMPARE(frameSpy.count(), 7);
QCOMPARE(axisSpy.first().at(0).value<quint32>(), quint32(2));
QCOMPARE(axisSpy.first().at(1).value<KWayland::Client::Pointer::Axis>(), KWayland::Client::Pointer::Axis::Horizontal);
QCOMPARE(axisSpy.first().at(2).value<qreal>(), qreal(10));
QCOMPARE(axisSpy.last().at(0).value<quint32>(), quint32(3));
QCOMPARE(axisSpy.last().at(1).value<KWayland::Client::Pointer::Axis>(), KWayland::Client::Pointer::Axis::Vertical);
QCOMPARE(axisSpy.last().at(2).value<qreal>(), qreal(20));
// test button
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerButton(1, PointerButtonState::Pressed);
m_seatInterface->notifyPointerFrame();
QVERIFY(buttonSpy.wait());
QCOMPARE(frameSpy.count(), 8);
QCOMPARE(buttonSpy.at(0).at(0).value<quint32>(), m_display->serial());
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerButton(2, PointerButtonState::Pressed);
m_seatInterface->notifyPointerFrame();
QVERIFY(buttonSpy.wait());
QCOMPARE(frameSpy.count(), 9);
QCOMPARE(buttonSpy.at(1).at(0).value<quint32>(), m_display->serial());
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerButton(2, PointerButtonState::Released);
m_seatInterface->notifyPointerFrame();
QVERIFY(buttonSpy.wait());
QCOMPARE(frameSpy.count(), 10);
QCOMPARE(buttonSpy.at(2).at(0).value<quint32>(), m_display->serial());
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerButton(1, PointerButtonState::Released);
m_seatInterface->notifyPointerFrame();
QVERIFY(buttonSpy.wait());
QCOMPARE(frameSpy.count(), 11);
QCOMPARE(buttonSpy.count(), 4);
// timestamp
QCOMPARE(buttonSpy.at(0).at(1).value<quint32>(), quint32(4));
// button
QCOMPARE(buttonSpy.at(0).at(2).value<quint32>(), quint32(1));
QCOMPARE(buttonSpy.at(0).at(3).value<KWayland::Client::Pointer::ButtonState>(), KWayland::Client::Pointer::ButtonState::Pressed);
// timestamp
QCOMPARE(buttonSpy.at(1).at(1).value<quint32>(), quint32(5));
// button
QCOMPARE(buttonSpy.at(1).at(2).value<quint32>(), quint32(2));
QCOMPARE(buttonSpy.at(1).at(3).value<KWayland::Client::Pointer::ButtonState>(), KWayland::Client::Pointer::ButtonState::Pressed);
QCOMPARE(buttonSpy.at(2).at(0).value<quint32>(), m_seatInterface->pointerButtonSerial(2));
// timestamp
QCOMPARE(buttonSpy.at(2).at(1).value<quint32>(), quint32(6));
// button
QCOMPARE(buttonSpy.at(2).at(2).value<quint32>(), quint32(2));
QCOMPARE(buttonSpy.at(2).at(3).value<KWayland::Client::Pointer::ButtonState>(), KWayland::Client::Pointer::ButtonState::Released);
QCOMPARE(buttonSpy.at(3).at(0).value<quint32>(), m_seatInterface->pointerButtonSerial(1));
// timestamp
QCOMPARE(buttonSpy.at(3).at(1).value<quint32>(), quint32(7));
// button
QCOMPARE(buttonSpy.at(3).at(2).value<quint32>(), quint32(1));
QCOMPARE(buttonSpy.at(3).at(3).value<KWayland::Client::Pointer::ButtonState>(), KWayland::Client::Pointer::ButtonState::Released);
// leave the surface
serial = m_display->serial();
m_seatInterface->notifyPointerLeave();
QVERIFY(leftSpy.wait());
QCOMPARE(frameSpy.count(), 12);
QCOMPARE_GT(leftSpy.first().first().value<quint32>(), serial);
QVERIFY(!p->enteredSurface());
QVERIFY(!cp.enteredSurface());
// now a relative motion should not be sent to the relative pointer
m_seatInterface->relativePointerMotion(QPointF(1, 2), QPointF(3, 4), std::chrono::milliseconds::zero());
QVERIFY(sync());
QCOMPARE(relativeMotionSpy.count(), 1);
// enter it again
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(10, 16), QPointF(0, 0));
QVERIFY(enteredSpy.wait());
QCOMPARE(frameSpy.count(), 13);
QCOMPARE(p->enteredSurface(), s);
QCOMPARE(cp.enteredSurface(), s);
// send another relative motion event
m_seatInterface->relativePointerMotion(QPointF(4, 5), QPointF(6, 7), 1234us);
QVERIFY(relativeMotionSpy.wait());
QCOMPARE(relativeMotionSpy.count(), 2);
QCOMPARE(relativeMotionSpy.last().at(0).toSizeF(), QSizeF(4, 5));
QCOMPARE(relativeMotionSpy.last().at(1).toSizeF(), QSizeF(6, 7));
QCOMPARE(relativeMotionSpy.last().at(2).value<quint64>(), 1234);
}
void TestWaylandSeat::testPointerTransformation_data()
{
QTest::addColumn<QMatrix4x4>("enterTransformation");
// global position at 20/18
QTest::addColumn<QPointF>("expectedEnterPoint");
// global position at 10/16
QTest::addColumn<QPointF>("expectedMovePoint");
QMatrix4x4 tm;
tm.translate(-10, -15);
QTest::newRow("translation") << tm << QPointF(10, 3) << QPointF(0, 1);
QMatrix4x4 sm;
sm.scale(2, 2);
QTest::newRow("scale") << sm << QPointF(40, 36) << QPointF(20, 32);
QMatrix4x4 rotate;
rotate.rotate(90, 0, 0, 1);
QTest::newRow("rotate") << rotate << QPointF(-18, 20) << QPointF(-16, 10);
}
void TestWaylandSeat::testPointerTransformation()
{
using namespace KWin;
QSignalSpy pointerSpy(m_seat, &KWayland::Client::Seat::hasPointerChanged);
m_seatInterface->setHasPointer(true);
QVERIFY(pointerSpy.wait());
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
KWayland::Client::Surface *s = m_compositor->createSurface(m_compositor);
QVERIFY(surfaceCreatedSpy.wait());
SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value<KWin::SurfaceInterface *>();
QVERIFY(serverSurface);
QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::black);
s->attachBuffer(m_shm->createBuffer(image));
s->damage(image.rect());
s->commit(KWayland::Client::Surface::CommitFlag::None);
QSignalSpy committedSpy(serverSurface, &KWin::SurfaceInterface::committed);
QVERIFY(committedSpy.wait());
KWayland::Client::Pointer *p = m_seat->createPointer(m_seat);
QVERIFY(p->isValid());
const KWayland::Client::Pointer &cp = *p;
QSignalSpy enteredSpy(p, &KWayland::Client::Pointer::entered);
QSignalSpy leftSpy(p, &KWayland::Client::Pointer::left);
QSignalSpy motionSpy(p, &KWayland::Client::Pointer::motion);
QVERIFY(!p->enteredSurface());
QVERIFY(!cp.enteredSurface());
uint32_t serial = m_display->serial();
QFETCH(QMatrix4x4, enterTransformation);
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(20, 18), enterTransformation);
QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface);
QVERIFY(enteredSpy.wait());
QCOMPARE_GT(enteredSpy.first().first().value<quint32>(), serial);
QTEST(enteredSpy.first().last().toPointF(), "expectedEnterPoint");
QCOMPARE(p->enteredSurface(), s);
QCOMPARE(cp.enteredSurface(), s);
// test motion
m_seatInterface->setTimestamp(std::chrono::milliseconds(1));
m_seatInterface->notifyPointerMotion(QPoint(10, 16));
m_seatInterface->notifyPointerFrame();
QVERIFY(motionSpy.wait());
QTEST(motionSpy.first().first().toPointF(), "expectedMovePoint");
QCOMPARE(motionSpy.first().last().value<quint32>(), quint32(1));
// leave the surface
serial = m_display->serial();
m_seatInterface->notifyPointerLeave();
QVERIFY(leftSpy.wait());
QCOMPARE_GT(leftSpy.first().first().value<quint32>(), serial);
QVERIFY(!p->enteredSurface());
QVERIFY(!cp.enteredSurface());
// enter it again
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(10, 16));
QVERIFY(enteredSpy.wait());
QCOMPARE(p->enteredSurface(), s);
QCOMPARE(cp.enteredSurface(), s);
QSignalSpy serverSurfaceDestroyedSpy(serverSurface, &QObject::destroyed);
delete s;
QVERIFY(serverSurfaceDestroyedSpy.wait());
QVERIFY(!m_seatInterface->focusedPointerSurface());
}
Q_DECLARE_METATYPE(Qt::MouseButton)
void TestWaylandSeat::testPointerButton_data()
{
QTest::addColumn<Qt::MouseButton>("qtButton");
QTest::addColumn<quint32>("waylandButton");
QTest::newRow("left") << Qt::LeftButton << quint32(BTN_LEFT);
QTest::newRow("right") << Qt::RightButton << quint32(BTN_RIGHT);
QTest::newRow("middle") << Qt::MiddleButton << quint32(BTN_MIDDLE);
QTest::newRow("back") << Qt::BackButton << quint32(BTN_BACK);
QTest::newRow("x1") << Qt::XButton1 << quint32(BTN_BACK);
QTest::newRow("extra1") << Qt::ExtraButton1 << quint32(BTN_BACK);
QTest::newRow("forward") << Qt::ForwardButton << quint32(BTN_FORWARD);
QTest::newRow("x2") << Qt::XButton2 << quint32(BTN_FORWARD);
QTest::newRow("extra2") << Qt::ExtraButton2 << quint32(BTN_FORWARD);
QTest::newRow("task") << Qt::TaskButton << quint32(BTN_TASK);
QTest::newRow("extra3") << Qt::ExtraButton3 << quint32(BTN_TASK);
QTest::newRow("extra4") << Qt::ExtraButton4 << quint32(BTN_EXTRA);
QTest::newRow("extra5") << Qt::ExtraButton5 << quint32(BTN_SIDE);
QTest::newRow("extra6") << Qt::ExtraButton6 << quint32(0x118);
QTest::newRow("extra7") << Qt::ExtraButton7 << quint32(0x119);
QTest::newRow("extra8") << Qt::ExtraButton8 << quint32(0x11a);
QTest::newRow("extra9") << Qt::ExtraButton9 << quint32(0x11b);
QTest::newRow("extra10") << Qt::ExtraButton10 << quint32(0x11c);
QTest::newRow("extra11") << Qt::ExtraButton11 << quint32(0x11d);
QTest::newRow("extra12") << Qt::ExtraButton12 << quint32(0x11e);
QTest::newRow("extra13") << Qt::ExtraButton13 << quint32(0x11f);
}
void TestWaylandSeat::testPointerButton()
{
using namespace KWin;
QSignalSpy pointerSpy(m_seat, &KWayland::Client::Seat::hasPointerChanged);
m_seatInterface->setHasPointer(true);
QVERIFY(pointerSpy.wait());
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &KWin::CompositorInterface::surfaceCreated);
KWayland::Client::Surface *s = m_compositor->createSurface(m_compositor);
QVERIFY(surfaceCreatedSpy.wait());
SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value<KWin::SurfaceInterface *>();
QVERIFY(serverSurface);
QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::black);
s->attachBuffer(m_shm->createBuffer(image));
s->damage(image.rect());
s->commit(KWayland::Client::Surface::CommitFlag::None);
QSignalSpy committedSpy(serverSurface, &KWin::SurfaceInterface::committed);
QVERIFY(committedSpy.wait());
std::unique_ptr<KWayland::Client::Pointer> p(m_seat->createPointer());
QVERIFY(p->isValid());
QSignalSpy buttonChangedSpy(p.get(), &KWayland::Client::Pointer::buttonStateChanged);
wl_display_flush(m_connection->display());
QCoreApplication::processEvents();
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(20, 18), QPointF(10, 15));
QVERIFY(m_seatInterface->focusedPointerSurface());
QCoreApplication::processEvents();
QFETCH(Qt::MouseButton, qtButton);
QFETCH(quint32, waylandButton);
std::chrono::milliseconds timestamp(1);
QCOMPARE(m_seatInterface->isPointerButtonPressed(waylandButton), false);
QCOMPARE(m_seatInterface->isPointerButtonPressed(qtButton), false);
m_seatInterface->setTimestamp(timestamp);
m_seatInterface->notifyPointerButton(qtButton, PointerButtonState::Pressed);
m_seatInterface->notifyPointerFrame();
QCOMPARE(m_seatInterface->isPointerButtonPressed(waylandButton), true);
QCOMPARE(m_seatInterface->isPointerButtonPressed(qtButton), true);
QVERIFY(buttonChangedSpy.wait());
QCOMPARE(buttonChangedSpy.count(), 1);
QCOMPARE(buttonChangedSpy.last().at(0).value<quint32>(), m_seatInterface->pointerButtonSerial(waylandButton));
QCOMPARE(buttonChangedSpy.last().at(0).value<quint32>(), m_seatInterface->pointerButtonSerial(qtButton));
QCOMPARE(buttonChangedSpy.last().at(1).value<quint32>(), timestamp.count());
QCOMPARE(buttonChangedSpy.last().at(2).value<quint32>(), waylandButton);
QCOMPARE(buttonChangedSpy.last().at(3).value<KWayland::Client::Pointer::ButtonState>(), KWayland::Client::Pointer::ButtonState::Pressed);
timestamp++;
m_seatInterface->setTimestamp(timestamp);
m_seatInterface->notifyPointerButton(qtButton, PointerButtonState::Released);
m_seatInterface->notifyPointerFrame();
QCOMPARE(m_seatInterface->isPointerButtonPressed(waylandButton), false);
QCOMPARE(m_seatInterface->isPointerButtonPressed(qtButton), false);
QVERIFY(buttonChangedSpy.wait());
QCOMPARE(buttonChangedSpy.count(), 2);
QCOMPARE(buttonChangedSpy.last().at(0).value<quint32>(), m_seatInterface->pointerButtonSerial(waylandButton));
QCOMPARE(buttonChangedSpy.last().at(0).value<quint32>(), m_seatInterface->pointerButtonSerial(qtButton));
QCOMPARE(buttonChangedSpy.last().at(1).value<quint32>(), timestamp.count());
QCOMPARE(buttonChangedSpy.last().at(2).value<quint32>(), waylandButton);
QCOMPARE(buttonChangedSpy.last().at(3).value<KWayland::Client::Pointer::ButtonState>(), KWayland::Client::Pointer::ButtonState::Released);
}
void TestWaylandSeat::testPointerSubSurfaceTree()
{
// this test verifies that pointer motion on a surface with sub-surfaces sends motion enter/leave to the sub-surface
using namespace KWin;
// first create the pointer
QSignalSpy hasPointerChangedSpy(m_seat, &KWayland::Client::Seat::hasPointerChanged);
m_seatInterface->setHasPointer(true);
QVERIFY(hasPointerChangedSpy.wait());
std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer());
// create a sub surface tree
// parent surface (100, 100) with one sub surface taking the half of it's size (50, 100)
// which has two further children (50, 50) which are overlapping
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
std::unique_ptr<KWayland::Client::Surface> parentSurface(m_compositor->createSurface());
std::unique_ptr<KWayland::Client::Surface> childSurface(m_compositor->createSurface());
std::unique_ptr<KWayland::Client::Surface> grandChild1Surface(m_compositor->createSurface());
std::unique_ptr<KWayland::Client::Surface> grandChild2Surface(m_compositor->createSurface());
std::unique_ptr<KWayland::Client::SubSurface> childSubSurface(m_subCompositor->createSubSurface(childSurface.get(), parentSurface.get()));
std::unique_ptr<KWayland::Client::SubSurface> grandChild1SubSurface(m_subCompositor->createSubSurface(grandChild1Surface.get(), childSurface.get()));
std::unique_ptr<KWayland::Client::SubSurface> grandChild2SubSurface(m_subCompositor->createSubSurface(grandChild2Surface.get(), childSurface.get()));
grandChild2SubSurface->setPosition(QPoint(0, 25));
// let's map the surfaces
auto render = [this](KWayland::Client::Surface *s, const QSize &size) {
QImage image(size, QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::black);
s->attachBuffer(m_shm->createBuffer(image));
s->damage(QRect(QPoint(0, 0), size));
s->commit(KWayland::Client::Surface::CommitFlag::None);
};
render(grandChild2Surface.get(), QSize(50, 50));
render(grandChild1Surface.get(), QSize(50, 50));
render(childSurface.get(), QSize(50, 100));
render(parentSurface.get(), QSize(100, 100));
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
QVERIFY(serverSurface->isMapped());
// send in pointer events
QSignalSpy enteredSpy(pointer.get(), &KWayland::Client::Pointer::entered);
QSignalSpy leftSpy(pointer.get(), &KWayland::Client::Pointer::left);
QSignalSpy motionSpy(pointer.get(), &KWayland::Client::Pointer::motion);
// first to the grandChild2 in the overlapped area
std::chrono::milliseconds timestamp(1);
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(25, 50));
QVERIFY(enteredSpy.wait());
QCOMPARE(enteredSpy.count(), 1);
QCOMPARE(leftSpy.count(), 0);
QCOMPARE(motionSpy.count(), 0);
QCOMPARE(enteredSpy.last().last().toPointF(), QPointF(25, 25));
QCOMPARE(pointer->enteredSurface(), grandChild2Surface.get());
// a motion on grandchild2
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerMotion(QPointF(25, 60));
m_seatInterface->notifyPointerFrame();
QVERIFY(motionSpy.wait());
QCOMPARE(enteredSpy.count(), 1);
QCOMPARE(leftSpy.count(), 0);
QCOMPARE(motionSpy.count(), 1);
QCOMPARE(motionSpy.last().first().toPointF(), QPointF(25, 35));
// motion which changes to childSurface
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerMotion(QPointF(25, 80));
m_seatInterface->notifyPointerFrame();
QVERIFY(enteredSpy.wait());
QCOMPARE(enteredSpy.count(), 2);
QCOMPARE(leftSpy.count(), 1);
QCOMPARE(motionSpy.count(), 2);
QCOMPARE(enteredSpy.last().last().toPointF(), QPointF(25, 80));
QCOMPARE(pointer->enteredSurface(), childSurface.get());
// a leave for the whole surface
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerLeave();
QVERIFY(leftSpy.wait());
QCOMPARE(enteredSpy.count(), 2);
QCOMPARE(leftSpy.count(), 2);
QCOMPARE(motionSpy.count(), 2);
// a new enter on the main surface
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(75, 50));
QVERIFY(enteredSpy.wait());
QCOMPARE(enteredSpy.count(), 3);
QCOMPARE(leftSpy.count(), 2);
QCOMPARE(motionSpy.count(), 2);
QCOMPARE(enteredSpy.last().last().toPointF(), QPointF(75, 50));
QCOMPARE(pointer->enteredSurface(), parentSurface.get());
}
void TestWaylandSeat::testPointerSwipeGesture_data()
{
QTest::addColumn<bool>("cancel");
QTest::addColumn<int>("expectedEndCount");
QTest::addColumn<int>("expectedCancelCount");
QTest::newRow("end") << false << 1 << 0;
QTest::newRow("cancel") << true << 0 << 1;
}
void TestWaylandSeat::testPointerSwipeGesture()
{
using namespace KWin;
// first create the pointer and pointer swipe gesture
QSignalSpy hasPointerChangedSpy(m_seat, &KWayland::Client::Seat::hasPointerChanged);
m_seatInterface->setHasPointer(true);
QVERIFY(hasPointerChangedSpy.wait());
std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer());
std::unique_ptr<KWayland::Client::PointerSwipeGesture> gesture(m_pointerGestures->createSwipeGesture(pointer.get()));
QVERIFY(gesture);
QVERIFY(gesture->isValid());
QVERIFY(gesture->surface().isNull());
QCOMPARE(gesture->fingerCount(), 0u);
QSignalSpy startSpy(gesture.get(), &KWayland::Client::PointerSwipeGesture::started);
QSignalSpy updateSpy(gesture.get(), &KWayland::Client::PointerSwipeGesture::updated);
QSignalSpy endSpy(gesture.get(), &KWayland::Client::PointerSwipeGesture::ended);
QSignalSpy cancelledSpy(gesture.get(), &KWayland::Client::PointerSwipeGesture::cancelled);
// now create a surface
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
QVERIFY(serverSurface);
QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::black);
surface->attachBuffer(m_shm->createBuffer(image));
surface->damage(image.rect());
surface->commit(KWayland::Client::Surface::CommitFlag::None);
QSignalSpy committedSpy(serverSurface, &KWin::SurfaceInterface::committed);
QVERIFY(committedSpy.wait());
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(0, 0));
QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface);
QVERIFY(m_seatInterface->pointer());
// send in the start
std::chrono::milliseconds timestamp(1);
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->startPointerSwipeGesture(2);
QVERIFY(startSpy.wait());
QCOMPARE(startSpy.count(), 1);
QCOMPARE(startSpy.first().at(0).value<quint32>(), m_display->serial());
QCOMPARE(startSpy.first().at(1).value<quint32>(), 1u);
QCOMPARE(gesture->fingerCount(), 2u);
QCOMPARE(gesture->surface().data(), surface.get());
// another start should not be possible
m_seatInterface->startPointerSwipeGesture(2);
QVERIFY(sync());
QCOMPARE(startSpy.count(), 1);
// send in some updates
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->updatePointerSwipeGesture(QPointF(2, 3));
QVERIFY(updateSpy.wait());
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->updatePointerSwipeGesture(QPointF(4, 5));
QVERIFY(updateSpy.wait());
QCOMPARE(updateSpy.count(), 2);
QCOMPARE(updateSpy.at(0).at(0).toSizeF(), QSizeF(2, 3));
QCOMPARE(updateSpy.at(0).at(1).value<quint32>(), 2u);
QCOMPARE(updateSpy.at(1).at(0).toSizeF(), QSizeF(4, 5));
QCOMPARE(updateSpy.at(1).at(1).value<quint32>(), 3u);
// now end or cancel
QFETCH(bool, cancel);
QSignalSpy *spy;
m_seatInterface->setTimestamp(timestamp++);
if (cancel) {
m_seatInterface->cancelPointerSwipeGesture();
spy = &cancelledSpy;
} else {
m_seatInterface->endPointerSwipeGesture();
spy = &endSpy;
}
QVERIFY(spy->wait());
QFETCH(int, expectedEndCount);
QCOMPARE(endSpy.count(), expectedEndCount);
QFETCH(int, expectedCancelCount);
QCOMPARE(cancelledSpy.count(), expectedCancelCount);
QCOMPARE(spy->count(), 1);
QCOMPARE(spy->first().at(0).value<quint32>(), m_display->serial());
QCOMPARE(spy->first().at(1).value<quint32>(), 4u);
QCOMPARE(gesture->fingerCount(), 0u);
QVERIFY(gesture->surface().isNull());
// now a start should be possible again
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->startPointerSwipeGesture(2);
QVERIFY(startSpy.wait());
// unsetting the focused pointer surface should not change anything
m_seatInterface->notifyPointerLeave();
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->updatePointerSwipeGesture(QPointF(6, 7));
QVERIFY(updateSpy.wait());
// and end
m_seatInterface->setTimestamp(timestamp++);
if (cancel) {
m_seatInterface->cancelPointerSwipeGesture();
} else {
m_seatInterface->endPointerSwipeGesture();
}
QVERIFY(spy->wait());
}
void TestWaylandSeat::testPointerPinchGesture_data()
{
QTest::addColumn<bool>("cancel");
QTest::addColumn<int>("expectedEndCount");
QTest::addColumn<int>("expectedCancelCount");
QTest::newRow("end") << false << 1 << 0;
QTest::newRow("cancel") << true << 0 << 1;
}
void TestWaylandSeat::testPointerPinchGesture()
{
using namespace KWin;
// first create the pointer and pointer swipe gesture
QSignalSpy hasPointerChangedSpy(m_seat, &KWayland::Client::Seat::hasPointerChanged);
m_seatInterface->setHasPointer(true);
QVERIFY(hasPointerChangedSpy.wait());
std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer());
std::unique_ptr<KWayland::Client::PointerPinchGesture> gesture(m_pointerGestures->createPinchGesture(pointer.get()));
QVERIFY(gesture);
QVERIFY(gesture->isValid());
QVERIFY(gesture->surface().isNull());
QCOMPARE(gesture->fingerCount(), 0u);
QSignalSpy startSpy(gesture.get(), &KWayland::Client::PointerPinchGesture::started);
QSignalSpy updateSpy(gesture.get(), &KWayland::Client::PointerPinchGesture::updated);
QSignalSpy endSpy(gesture.get(), &KWayland::Client::PointerPinchGesture::ended);
QSignalSpy cancelledSpy(gesture.get(), &KWayland::Client::PointerPinchGesture::cancelled);
// now create a surface
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
QVERIFY(serverSurface);
QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::black);
surface->attachBuffer(m_shm->createBuffer(image));
surface->damage(image.rect());
surface->commit(KWayland::Client::Surface::CommitFlag::None);
QSignalSpy committedSpy(serverSurface, &KWin::SurfaceInterface::committed);
QVERIFY(committedSpy.wait());
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(0, 0));
QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface);
QVERIFY(m_seatInterface->pointer());
// send in the start
std::chrono::milliseconds timestamp(1);
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->startPointerPinchGesture(3);
QVERIFY(startSpy.wait());
QCOMPARE(startSpy.count(), 1);
QCOMPARE(startSpy.first().at(0).value<quint32>(), m_display->serial());
QCOMPARE(startSpy.first().at(1).value<quint32>(), 1u);
QCOMPARE(gesture->fingerCount(), 3u);
QCOMPARE(gesture->surface().data(), surface.get());
// another start should not be possible
m_seatInterface->startPointerPinchGesture(3);
QVERIFY(sync());
QCOMPARE(startSpy.count(), 1);
// send in some updates
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->updatePointerPinchGesture(QPointF(2, 3), 2, 45);
QVERIFY(updateSpy.wait());
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->updatePointerPinchGesture(QPointF(4, 5), 1, 90);
QVERIFY(updateSpy.wait());
QCOMPARE(updateSpy.count(), 2);
QCOMPARE(updateSpy.at(0).at(0).toSizeF(), QSizeF(2, 3));
QCOMPARE(updateSpy.at(0).at(1).value<quint32>(), 2u);
QCOMPARE(updateSpy.at(0).at(2).value<quint32>(), 45u);
QCOMPARE(updateSpy.at(0).at(3).value<quint32>(), 2u);
QCOMPARE(updateSpy.at(1).at(0).toSizeF(), QSizeF(4, 5));
QCOMPARE(updateSpy.at(1).at(1).value<quint32>(), 1u);
QCOMPARE(updateSpy.at(1).at(2).value<quint32>(), 90u);
QCOMPARE(updateSpy.at(1).at(3).value<quint32>(), 3u);
// now end or cancel
QFETCH(bool, cancel);
QSignalSpy *spy;
m_seatInterface->setTimestamp(timestamp++);
if (cancel) {
m_seatInterface->cancelPointerPinchGesture();
spy = &cancelledSpy;
} else {
m_seatInterface->endPointerPinchGesture();
spy = &endSpy;
}
QVERIFY(spy->wait());
QFETCH(int, expectedEndCount);
QCOMPARE(endSpy.count(), expectedEndCount);
QFETCH(int, expectedCancelCount);
QCOMPARE(cancelledSpy.count(), expectedCancelCount);
QCOMPARE(spy->count(), 1);
QCOMPARE(spy->first().at(0).value<quint32>(), m_display->serial());
QCOMPARE(spy->first().at(1).value<quint32>(), 4u);
QCOMPARE(gesture->fingerCount(), 0u);
QVERIFY(gesture->surface().isNull());
// now a start should be possible again
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->startPointerPinchGesture(3);
QVERIFY(startSpy.wait());
// unsetting the focused pointer surface should not change anything
m_seatInterface->notifyPointerLeave();
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->updatePointerPinchGesture(QPointF(6, 7), 2, -45);
QVERIFY(updateSpy.wait());
// and end
m_seatInterface->setTimestamp(timestamp++);
if (cancel) {
m_seatInterface->cancelPointerPinchGesture();
} else {
m_seatInterface->endPointerPinchGesture();
}
QVERIFY(spy->wait());
}
void TestWaylandSeat::testPointerHoldGesture_data()
{
QTest::addColumn<bool>("cancel");
QTest::addColumn<int>("expectedEndCount");
QTest::addColumn<int>("expectedCancelCount");
QTest::newRow("end") << false << 1 << 0;
QTest::newRow("cancel") << true << 0 << 1;
}
class PointerHoldGesture : public QObject, public QtWayland::zwp_pointer_gesture_hold_v1
{
using zwp_pointer_gesture_hold_v1::zwp_pointer_gesture_hold_v1;
Q_OBJECT
void zwp_pointer_gesture_hold_v1_begin(uint32_t serial, uint32_t time, wl_surface *surface, uint32_t fingers) override
{
Q_EMIT started(serial, time, surface, fingers);
}
void zwp_pointer_gesture_hold_v1_end(uint32_t serial, uint32_t time, int32_t cancelled) override
{
cancelled ? Q_EMIT this->cancelled(serial, time) : Q_EMIT ended(serial, time);
}
Q_SIGNALS:
void started(quint32 serial, quint32 time, void *surface, quint32 fingers);
void ended(quint32 serial, quint32 time);
void cancelled(quint32 serial, quint32 time);
};
void TestWaylandSeat::testPointerHoldGesture()
{
using namespace KWin;
// first create the pointer and pointer swipe gesture
QSignalSpy hasPointerChangedSpy(m_seat, &KWayland::Client::Seat::hasPointerChanged);
m_seatInterface->setHasPointer(true);
QVERIFY(hasPointerChangedSpy.wait());
std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer());
KWayland::Client::Registry registry;
QSignalSpy gesturesAnnoucedSpy(&registry, &KWayland::Client::Registry::pointerGesturesUnstableV1Announced);
registry.create(m_connection);
registry.setup();
QVERIFY(gesturesAnnoucedSpy.wait());
QtWayland::zwp_pointer_gestures_v1 gestures(registry, gesturesAnnoucedSpy.first().at(0).value<int>(), gesturesAnnoucedSpy.first().at(1).value<int>());
PointerHoldGesture gesture(gestures.get_hold_gesture(*pointer));
QVERIFY(gesture.isInitialized());
QSignalSpy startSpy(&gesture, &PointerHoldGesture::started);
QSignalSpy endSpy(&gesture, &PointerHoldGesture::ended);
QSignalSpy cancelledSpy(&gesture, &PointerHoldGesture::cancelled);
// now create a surface
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
QVERIFY(serverSurface);
QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::black);
surface->attachBuffer(m_shm->createBuffer(image));
surface->damage(image.rect());
surface->commit(KWayland::Client::Surface::CommitFlag::None);
QSignalSpy committedSpy(serverSurface, &KWin::SurfaceInterface::committed);
QVERIFY(committedSpy.wait());
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(0, 0));
QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface);
QVERIFY(m_seatInterface->pointer());
// send in the start
std::chrono::milliseconds timestamp(1);
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->startPointerHoldGesture(3);
QVERIFY(startSpy.wait());
QCOMPARE(startSpy.count(), 1);
QCOMPARE(startSpy.first().at(0).value<quint32>(), m_display->serial());
QCOMPARE(startSpy.first().at(1).value<quint32>(), 1u);
QCOMPARE(startSpy.first().at(2).value<void *>(), *surface.get());
QCOMPARE(startSpy.first().at(3).value<quint32>(), 3);
// another start should not be possible
m_seatInterface->startPointerPinchGesture(3);
QVERIFY(sync());
QCOMPARE(startSpy.count(), 1);
// now end or cancel
QFETCH(bool, cancel);
QSignalSpy *spy;
m_seatInterface->setTimestamp(timestamp++);
if (cancel) {
m_seatInterface->cancelPointerHoldGesture();
spy = &cancelledSpy;
} else {
m_seatInterface->endPointerHoldGesture();
spy = &endSpy;
}
QVERIFY(spy->wait());
QFETCH(int, expectedEndCount);
QCOMPARE(endSpy.count(), expectedEndCount);
QFETCH(int, expectedCancelCount);
QCOMPARE(cancelledSpy.count(), expectedCancelCount);
QCOMPARE(spy->count(), 1);
QCOMPARE(spy->first().at(0).value<quint32>(), m_display->serial());
QCOMPARE(spy->first().at(1).value<quint32>(), 2);
// now a start should be possible again
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->startPointerHoldGesture(3);
QVERIFY(startSpy.wait());
// and end
m_seatInterface->setTimestamp(timestamp++);
if (cancel) {
m_seatInterface->cancelPointerHoldGesture();
} else {
m_seatInterface->endPointerHoldGesture();
}
QVERIFY(spy->wait());
}
void TestWaylandSeat::testPointerAxis()
{
using namespace KWin;
// first create the pointer
QSignalSpy hasPointerChangedSpy(m_seat, &KWayland::Client::Seat::hasPointerChanged);
m_seatInterface->setHasPointer(true);
QVERIFY(hasPointerChangedSpy.wait());
std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer());
QVERIFY(pointer);
// now create a surface
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
QVERIFY(serverSurface);
QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::black);
surface->attachBuffer(m_shm->createBuffer(image));
surface->damage(image.rect());
surface->commit(KWayland::Client::Surface::CommitFlag::None);
QSignalSpy committedSpy(serverSurface, &KWin::SurfaceInterface::committed);
QVERIFY(committedSpy.wait());
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(0, 0));
QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface);
QSignalSpy frameSpy(pointer.get(), &KWayland::Client::Pointer::frame);
QVERIFY(frameSpy.wait());
QCOMPARE(frameSpy.count(), 1);
// let's scroll vertically
QSignalSpy axisSourceSpy(pointer.get(), &KWayland::Client::Pointer::axisSourceChanged);
QSignalSpy axisSpy(pointer.get(), &KWayland::Client::Pointer::axisChanged);
QSignalSpy axisDiscreteSpy(pointer.get(), &KWayland::Client::Pointer::axisDiscreteChanged);
QSignalSpy axisStoppedSpy(pointer.get(), &KWayland::Client::Pointer::axisStopped);
std::chrono::milliseconds timestamp(1);
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerAxis(Qt::Vertical, 10, 120, PointerAxisSource::Wheel);
m_seatInterface->notifyPointerFrame();
QVERIFY(frameSpy.wait());
QCOMPARE(frameSpy.count(), 2);
QCOMPARE(axisSourceSpy.count(), 1);
QCOMPARE(axisSourceSpy.last().at(0).value<KWayland::Client::Pointer::AxisSource>(), KWayland::Client::Pointer::AxisSource::Wheel);
QCOMPARE(axisDiscreteSpy.count(), 1);
QCOMPARE(axisDiscreteSpy.last().at(0).value<KWayland::Client::Pointer::Axis>(), KWayland::Client::Pointer::Axis::Vertical);
QCOMPARE(axisDiscreteSpy.last().at(1).value<qint32>(), 1);
QCOMPARE(axisSpy.count(), 1);
QCOMPARE(axisSpy.last().at(0).value<quint32>(), quint32(1));
QCOMPARE(axisSpy.last().at(1).value<KWayland::Client::Pointer::Axis>(), KWayland::Client::Pointer::Axis::Vertical);
QCOMPARE(axisSpy.last().at(2).value<qreal>(), 10.0);
QCOMPARE(axisStoppedSpy.count(), 0);
// let's scroll using fingers
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerAxis(Qt::Horizontal, 42, 0, PointerAxisSource::Finger);
m_seatInterface->notifyPointerFrame();
QVERIFY(frameSpy.wait());
QCOMPARE(frameSpy.count(), 3);
QCOMPARE(axisSourceSpy.count(), 2);
QCOMPARE(axisSourceSpy.last().at(0).value<KWayland::Client::Pointer::AxisSource>(), KWayland::Client::Pointer::AxisSource::Finger);
QCOMPARE(axisDiscreteSpy.count(), 1);
QCOMPARE(axisSpy.count(), 2);
QCOMPARE(axisSpy.last().at(0).value<quint32>(), quint32(2));
QCOMPARE(axisSpy.last().at(1).value<KWayland::Client::Pointer::Axis>(), KWayland::Client::Pointer::Axis::Horizontal);
QCOMPARE(axisSpy.last().at(2).value<qreal>(), 42.0);
QCOMPARE(axisStoppedSpy.count(), 0);
// lift the fingers off the device
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerAxis(Qt::Horizontal, 0, 0, PointerAxisSource::Finger);
m_seatInterface->notifyPointerFrame();
QVERIFY(frameSpy.wait());
QCOMPARE(frameSpy.count(), 4);
QCOMPARE(axisSourceSpy.count(), 3);
QCOMPARE(axisSourceSpy.last().at(0).value<KWayland::Client::Pointer::AxisSource>(), KWayland::Client::Pointer::AxisSource::Finger);
QCOMPARE(axisDiscreteSpy.count(), 1);
QCOMPARE(axisSpy.count(), 2);
QCOMPARE(axisStoppedSpy.count(), 1);
QCOMPARE(axisStoppedSpy.last().at(0).value<quint32>(), 3);
QCOMPARE(axisStoppedSpy.last().at(1).value<KWayland::Client::Pointer::Axis>(), KWayland::Client::Pointer::Axis::Horizontal);
// if the device is unknown, no axis_source event should be sent
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyPointerAxis(Qt::Horizontal, 42, 120, PointerAxisSource::Unknown);
m_seatInterface->notifyPointerFrame();
QVERIFY(frameSpy.wait());
QCOMPARE(frameSpy.count(), 5);
QCOMPARE(axisSourceSpy.count(), 3);
QCOMPARE(axisDiscreteSpy.count(), 2);
QCOMPARE(axisDiscreteSpy.last().at(0).value<KWayland::Client::Pointer::Axis>(), KWayland::Client::Pointer::Axis::Horizontal);
QCOMPARE(axisDiscreteSpy.last().at(1).value<qint32>(), 1);
QCOMPARE(axisSpy.count(), 3);
QCOMPARE(axisSpy.last().at(0).value<quint32>(), quint32(4));
QCOMPARE(axisSpy.last().at(1).value<KWayland::Client::Pointer::Axis>(), KWayland::Client::Pointer::Axis::Horizontal);
QCOMPARE(axisSpy.last().at(2).value<qreal>(), 42.0);
QCOMPARE(axisStoppedSpy.count(), 1);
}
void TestWaylandSeat::testCursor()
{
using namespace KWin;
QSignalSpy pointerSpy(m_seat, &KWayland::Client::Seat::hasPointerChanged);
m_seatInterface->setHasPointer(true);
QVERIFY(pointerSpy.wait());
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &KWin::CompositorInterface::surfaceCreated);
KWayland::Client::Surface *surface = m_compositor->createSurface(m_compositor);
QVERIFY(surfaceCreatedSpy.wait());
SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value<KWin::SurfaceInterface *>();
QVERIFY(serverSurface);
QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::black);
surface->attachBuffer(m_shm->createBuffer(image));
surface->damage(image.rect());
surface->commit(KWayland::Client::Surface::CommitFlag::None);
QSignalSpy committedSpy(serverSurface, &KWin::SurfaceInterface::committed);
QVERIFY(committedSpy.wait());
std::unique_ptr<KWayland::Client::Pointer> p(m_seat->createPointer());
QVERIFY(p->isValid());
wl_display_flush(m_connection->display());
QCoreApplication::processEvents();
QSignalSpy enteredSpy(p.get(), &KWayland::Client::Pointer::entered);
uint32_t serial = m_seatInterface->display()->serial();
m_seatInterface->notifyPointerEnter(serverSurface, QPointF(20, 18), QPointF(10, 15));
QVERIFY(enteredSpy.wait());
QCOMPARE_GT(enteredSpy.first().first().value<quint32>(), serial);
QVERIFY(m_seatInterface->focusedPointerSurface());
QSignalSpy cursorChangedSpy(m_seatInterface->pointer(), &KWin::PointerInterface::cursorChanged);
// just remove the pointer
p->setCursor(nullptr);
QVERIFY(cursorChangedSpy.wait());
QCOMPARE(cursorChangedSpy.count(), 1);
auto cursor = std::get<KWin::PointerSurfaceCursor *>(cursorChangedSpy.last().first().value<KWin::PointerCursor>());
QVERIFY(cursor);
QVERIFY(!cursor->surface());
QCOMPARE(cursor->hotspot(), QPoint());
// test changing hotspot
p->setCursor(nullptr, QPoint(1, 2));
QVERIFY(cursorChangedSpy.wait());
QCOMPARE(cursorChangedSpy.count(), 2);
QCOMPARE(cursor->surface(), nullptr);
QCOMPARE(cursor->hotspot(), QPoint(1, 2));
// set surface
QImage img(QSize(10, 20), QImage::Format_RGB32);
img.fill(Qt::red);
auto cursorSurface = m_compositor->createSurface(m_compositor);
cursorSurface->attachBuffer(m_shm->createBuffer(img));
cursorSurface->damage(QRect(0, 0, 10, 20));
cursorSurface->commit(KWayland::Client::Surface::CommitFlag::None);
p->setCursor(cursorSurface, QPoint(1, 2));
QVERIFY(cursorChangedSpy.wait());
QCOMPARE(cursorChangedSpy.count(), 3);
QCOMPARE(cursor->hotspot(), QPoint(1, 2));
QVERIFY(cursor->surface());
p->hideCursor();
QVERIFY(cursorChangedSpy.wait());
QCOMPARE(cursorChangedSpy.count(), 4);
QVERIFY(!cursor->surface());
}
void TestWaylandSeat::testKeyboard()
{
using namespace KWin;
QSignalSpy keyboardSpy(m_seat, &KWayland::Client::Seat::hasKeyboardChanged);
m_seatInterface->setHasKeyboard(true);
QVERIFY(keyboardSpy.wait());
// update modifiers before any surface focused
m_seatInterface->notifyKeyboardModifiers(4, 3, 2, 1);
// create the surface
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &KWin::CompositorInterface::surfaceCreated);
KWayland::Client::Surface *s = m_compositor->createSurface(m_compositor);
QVERIFY(surfaceCreatedSpy.wait());
SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value<KWin::SurfaceInterface *>();
QVERIFY(serverSurface);
KWayland::Client::Keyboard *keyboard = m_seat->createKeyboard(m_seat);
QSignalSpy repeatInfoSpy(keyboard, &KWayland::Client::Keyboard::keyRepeatChanged);
const KWayland::Client::Keyboard &ckeyboard = *keyboard;
QVERIFY(keyboard->isValid());
QCOMPARE(keyboard->isKeyRepeatEnabled(), false);
QCOMPARE(keyboard->keyRepeatDelay(), 0);
QCOMPARE(keyboard->keyRepeatRate(), 0);
QVERIFY(repeatInfoSpy.wait());
auto serverKeyboard = m_seatInterface->keyboard();
QVERIFY(serverKeyboard);
// we should get the repeat info announced
QCOMPARE(repeatInfoSpy.count(), 1);
QCOMPARE(keyboard->isKeyRepeatEnabled(), false);
QCOMPARE(keyboard->keyRepeatDelay(), 0);
QCOMPARE(keyboard->keyRepeatRate(), 0);
// let's change repeat in server
m_seatInterface->keyboard()->setRepeatInfo(25, 660);
QVERIFY(repeatInfoSpy.wait());
QCOMPARE(repeatInfoSpy.count(), 2);
QCOMPARE(keyboard->isKeyRepeatEnabled(), true);
QCOMPARE(keyboard->keyRepeatRate(), 25);
QCOMPARE(keyboard->keyRepeatDelay(), 660);
std::chrono::milliseconds time(1);
m_seatInterface->setTimestamp(time++);
m_seatInterface->notifyKeyboardKey(KEY_K, KeyboardKeyState::Pressed);
m_seatInterface->setTimestamp(time++);
m_seatInterface->notifyKeyboardKey(KEY_D, KeyboardKeyState::Pressed);
m_seatInterface->setTimestamp(time++);
m_seatInterface->notifyKeyboardKey(KEY_E, KeyboardKeyState::Pressed);
QSignalSpy modifierSpy(keyboard, &KWayland::Client::Keyboard::modifiersChanged);
QSignalSpy enteredSpy(keyboard, &KWayland::Client::Keyboard::entered);
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface);
QCOMPARE(m_seatInterface->keyboard()->focusedSurface(), serverSurface);
// we get the modifiers sent after the enter
QVERIFY(modifierSpy.wait());
QCOMPARE(modifierSpy.count(), 1);
QCOMPARE(modifierSpy.first().at(0).value<quint32>(), quint32(4));
QCOMPARE(modifierSpy.first().at(1).value<quint32>(), quint32(3));
QCOMPARE(modifierSpy.first().at(2).value<quint32>(), quint32(2));
QCOMPARE(modifierSpy.first().at(3).value<quint32>(), quint32(1));
QCOMPARE(enteredSpy.count(), 1);
// TODO: get through API
QCOMPARE(enteredSpy.first().first().value<quint32>(), m_display->serial() - 1);
QSignalSpy keyChangedSpy(keyboard, &KWayland::Client::Keyboard::keyChanged);
m_seatInterface->setTimestamp(time++);
m_seatInterface->notifyKeyboardKey(KEY_E, KeyboardKeyState::Released);
QVERIFY(keyChangedSpy.wait());
m_seatInterface->setTimestamp(time++);
m_seatInterface->notifyKeyboardKey(KEY_D, KeyboardKeyState::Released);
QVERIFY(keyChangedSpy.wait());
m_seatInterface->setTimestamp(time++);
m_seatInterface->notifyKeyboardKey(KEY_K, KeyboardKeyState::Released);
QVERIFY(keyChangedSpy.wait());
m_seatInterface->setTimestamp(time++);
m_seatInterface->notifyKeyboardKey(KEY_F1, KeyboardKeyState::Pressed);
QVERIFY(keyChangedSpy.wait());
m_seatInterface->setTimestamp(time++);
m_seatInterface->notifyKeyboardKey(KEY_F1, KeyboardKeyState::Released);
QVERIFY(keyChangedSpy.wait());
QCOMPARE(keyChangedSpy.count(), 5);
QCOMPARE(keyChangedSpy.at(0).at(0).value<quint32>(), quint32(KEY_E));
QCOMPARE(keyChangedSpy.at(0).at(1).value<KWayland::Client::Keyboard::KeyState>(), KWayland::Client::Keyboard::KeyState::Released);
QCOMPARE(keyChangedSpy.at(0).at(2).value<quint32>(), quint32(4));
QCOMPARE(keyChangedSpy.at(1).at(0).value<quint32>(), quint32(KEY_D));
QCOMPARE(keyChangedSpy.at(1).at(1).value<KWayland::Client::Keyboard::KeyState>(), KWayland::Client::Keyboard::KeyState::Released);
QCOMPARE(keyChangedSpy.at(1).at(2).value<quint32>(), quint32(5));
QCOMPARE(keyChangedSpy.at(2).at(0).value<quint32>(), quint32(KEY_K));
QCOMPARE(keyChangedSpy.at(2).at(1).value<KWayland::Client::Keyboard::KeyState>(), KWayland::Client::Keyboard::KeyState::Released);
QCOMPARE(keyChangedSpy.at(2).at(2).value<quint32>(), quint32(6));
QCOMPARE(keyChangedSpy.at(3).at(0).value<quint32>(), quint32(KEY_F1));
QCOMPARE(keyChangedSpy.at(3).at(1).value<KWayland::Client::Keyboard::KeyState>(), KWayland::Client::Keyboard::KeyState::Pressed);
QCOMPARE(keyChangedSpy.at(3).at(2).value<quint32>(), quint32(7));
QCOMPARE(keyChangedSpy.at(4).at(0).value<quint32>(), quint32(KEY_F1));
QCOMPARE(keyChangedSpy.at(4).at(1).value<KWayland::Client::Keyboard::KeyState>(), KWayland::Client::Keyboard::KeyState::Released);
QCOMPARE(keyChangedSpy.at(4).at(2).value<quint32>(), quint32(8));
// releasing a key which is already released should not set a key changed
m_seatInterface->notifyKeyboardKey(KEY_F1, KeyboardKeyState::Released);
QVERIFY(sync());
QCOMPARE(keyChangedSpy.count(), 5);
// let's press it again
m_seatInterface->notifyKeyboardKey(KEY_F1, KeyboardKeyState::Pressed);
QVERIFY(keyChangedSpy.wait());
QCOMPARE(keyChangedSpy.count(), 6);
// press again should be ignored
m_seatInterface->notifyKeyboardKey(KEY_F1, KeyboardKeyState::Pressed);
QVERIFY(sync());
QCOMPARE(keyChangedSpy.count(), 6);
// and release
m_seatInterface->notifyKeyboardKey(KEY_F1, KeyboardKeyState::Released);
QVERIFY(keyChangedSpy.wait());
QCOMPARE(keyChangedSpy.count(), 7);
m_seatInterface->notifyKeyboardModifiers(1, 2, 3, 4);
QVERIFY(modifierSpy.wait());
QCOMPARE(modifierSpy.count(), 2);
QCOMPARE(modifierSpy.last().at(0).value<quint32>(), quint32(1));
QCOMPARE(modifierSpy.last().at(1).value<quint32>(), quint32(2));
QCOMPARE(modifierSpy.last().at(2).value<quint32>(), quint32(3));
QCOMPARE(modifierSpy.last().at(3).value<quint32>(), quint32(4));
QSignalSpy leftSpy(keyboard, &KWayland::Client::Keyboard::left);
m_seatInterface->setFocusedKeyboardSurface(nullptr);
QVERIFY(!m_seatInterface->focusedKeyboardSurface());
QVERIFY(leftSpy.wait());
QCOMPARE(leftSpy.count(), 1);
// TODO: get through API
QCOMPARE(leftSpy.first().first().value<quint32>(), m_display->serial() - 1);
QVERIFY(!keyboard->enteredSurface());
QVERIFY(!ckeyboard.enteredSurface());
// enter it again
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
QVERIFY(modifierSpy.wait());
QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface);
QCOMPARE(m_seatInterface->keyboard()->focusedSurface(), serverSurface);
QCOMPARE(enteredSpy.count(), 2);
QCOMPARE(keyboard->enteredSurface(), s);
QCOMPARE(ckeyboard.enteredSurface(), s);
QSignalSpy serverSurfaceDestroyedSpy(serverSurface, &QObject::destroyed);
QCOMPARE(keyboard->enteredSurface(), s);
delete s;
QVERIFY(!keyboard->enteredSurface());
QVERIFY(leftSpy.wait());
QCOMPARE(serverSurfaceDestroyedSpy.count(), 1);
QVERIFY(!m_seatInterface->focusedKeyboardSurface());
QVERIFY(!serverKeyboard->focusedSurface());
// let's create a Surface again
std::unique_ptr<KWayland::Client::Surface> s2(m_compositor->createSurface());
QVERIFY(surfaceCreatedSpy.wait());
QCOMPARE(surfaceCreatedSpy.count(), 2);
serverSurface = surfaceCreatedSpy.last().first().value<SurfaceInterface *>();
QVERIFY(serverSurface);
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface);
QCOMPARE(m_seatInterface->keyboard(), serverKeyboard);
}
void TestWaylandSeat::testSelection()
{
using namespace KWin;
std::unique_ptr<DataDeviceManagerInterface> ddmi(new DataDeviceManagerInterface(m_display));
KWayland::Client::Registry registry;
QSignalSpy dataDeviceManagerSpy(&registry, &KWayland::Client::Registry::dataDeviceManagerAnnounced);
m_seatInterface->setHasKeyboard(true);
registry.setEventQueue(m_queue);
registry.create(m_connection->display());
QVERIFY(registry.isValid());
registry.setup();
QVERIFY(dataDeviceManagerSpy.wait());
std::unique_ptr<KWayland::Client::DataDeviceManager> ddm(
registry.createDataDeviceManager(dataDeviceManagerSpy.first().first().value<quint32>(), dataDeviceManagerSpy.first().last().value<quint32>()));
QVERIFY(ddm->isValid());
std::unique_ptr<KWayland::Client::DataDevice> dd1(ddm->getDataDevice(m_seat));
QVERIFY(dd1->isValid());
QSignalSpy selectionSpy(dd1.get(), &KWayland::Client::DataDevice::selectionOffered);
QSignalSpy selectionClearedSpy(dd1.get(), &KWayland::Client::DataDevice::selectionCleared);
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &KWin::CompositorInterface::surfaceCreated);
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
QVERIFY(surface->isValid());
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
QVERIFY(!m_seatInterface->selection());
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface);
QVERIFY(selectionClearedSpy.wait());
QVERIFY(selectionSpy.isEmpty());
QVERIFY(!selectionClearedSpy.isEmpty());
selectionClearedSpy.clear();
QVERIFY(!m_seatInterface->selection());
// now let's try to set a selection - we have keyboard focus, so it should be sent to us
std::unique_ptr<KWayland::Client::DataSource> ds(ddm->createDataSource());
QVERIFY(ds->isValid());
ds->offer(QStringLiteral("text/plain"));
dd1->setSelection(m_display->nextSerial(), ds.get());
QVERIFY(selectionSpy.wait());
QCOMPARE(selectionSpy.count(), 1);
auto ddi = m_seatInterface->selection();
QVERIFY(ddi);
auto df = selectionSpy.first().first().value<KWayland::Client::DataOffer *>();
QCOMPARE(df->offeredMimeTypes().count(), 1);
QCOMPARE(df->offeredMimeTypes().first().name(), QStringLiteral("text/plain"));
// try to clear
dd1->setSelection(m_display->nextSerial());
QVERIFY(selectionClearedSpy.wait());
QCOMPARE(selectionClearedSpy.count(), 1);
QCOMPARE(selectionSpy.count(), 1);
// unset the keyboard focus
m_seatInterface->setFocusedKeyboardSurface(nullptr);
QVERIFY(!m_seatInterface->focusedKeyboardSurface());
serverSurface->client()->flush();
QCoreApplication::processEvents();
QCoreApplication::processEvents();
// try to set Selection
dd1->setSelection(m_display->nextSerial(), ds.get());
wl_display_flush(m_connection->display());
QCoreApplication::processEvents();
QCoreApplication::processEvents();
QCOMPARE(selectionSpy.count(), 1);
// let's unset the selection on the seat
m_seatInterface->setSelection(nullptr, m_display->nextSerial());
// and pass focus back on our surface
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
// we don't have a selection, so it should not send a selection
QVERIFY(sync());
QCOMPARE(selectionSpy.count(), 1);
// now let's set it manually
m_seatInterface->setSelection(ddi, m_display->nextSerial());
QCOMPARE(m_seatInterface->selection(), ddi);
QVERIFY(selectionSpy.wait());
QCOMPARE(selectionSpy.count(), 2);
// setting the same again should not change
m_seatInterface->setSelection(ddi, m_display->nextSerial());
QVERIFY(sync());
QCOMPARE(selectionSpy.count(), 2);
// now clear it manually
m_seatInterface->setSelection(nullptr, m_display->nextSerial());
QVERIFY(selectionClearedSpy.wait());
QCOMPARE(selectionSpy.count(), 2);
// create a second ddi and a data source
std::unique_ptr<KWayland::Client::DataDevice> dd2(ddm->getDataDevice(m_seat));
QVERIFY(dd2->isValid());
std::unique_ptr<KWayland::Client::DataSource> ds2(ddm->createDataSource());
QVERIFY(ds2->isValid());
ds2->offer(QStringLiteral("text/plain"));
dd2->setSelection(m_display->nextSerial(), ds2.get());
QVERIFY(selectionSpy.wait());
QSignalSpy cancelledSpy(ds2.get(), &KWayland::Client::DataSource::cancelled);
m_seatInterface->setSelection(ddi, m_display->nextSerial());
QVERIFY(cancelledSpy.wait());
}
void TestWaylandSeat::testDataDeviceForKeyboardSurface()
{
// this test verifies that the server does not crash when creating a datadevice for the focused keyboard surface
// and the currentSelection does not have a DataSource.
// to properly test the functionality this test requires a second client
using namespace KWin;
// create the DataDeviceManager
std::unique_ptr<DataDeviceManagerInterface> ddmi(new DataDeviceManagerInterface(m_display));
QSignalSpy ddiCreatedSpy(ddmi.get(), &DataDeviceManagerInterface::dataDeviceCreated);
m_seatInterface->setHasKeyboard(true);
// create a second Wayland client connection to use it for setSelection
auto c = new KWayland::Client::ConnectionThread;
QSignalSpy connectedSpy(c, &KWayland::Client::ConnectionThread::connected);
c->setSocketName(s_socketName);
auto thread = new QThread(this);
c->moveToThread(thread);
thread->start();
c->initConnection();
QVERIFY(connectedSpy.wait());
std::unique_ptr<KWayland::Client::EventQueue> queue(new KWayland::Client::EventQueue);
queue->setup(c);
std::unique_ptr<KWayland::Client::Registry> registry(new KWayland::Client::Registry);
QSignalSpy interfacesAnnouncedSpy(registry.get(), &KWayland::Client::Registry::interfacesAnnounced);
registry->setEventQueue(queue.get());
registry->create(c);
QVERIFY(registry->isValid());
registry->setup();
QVERIFY(interfacesAnnouncedSpy.wait());
std::unique_ptr<KWayland::Client::Seat> seat(
registry->createSeat(registry->interface(KWayland::Client::Registry::Interface::Seat).name, registry->interface(KWayland::Client::Registry::Interface::Seat).version));
QVERIFY(seat->isValid());
std::unique_ptr<KWayland::Client::DataDeviceManager> ddm1(registry->createDataDeviceManager(registry->interface(KWayland::Client::Registry::Interface::DataDeviceManager).name,
registry->interface(KWayland::Client::Registry::Interface::DataDeviceManager).version));
QVERIFY(ddm1->isValid());
// now create our first datadevice
std::unique_ptr<KWayland::Client::DataDevice> dd1(ddm1->getDataDevice(seat.get()));
QVERIFY(ddiCreatedSpy.wait());
auto ddi = ddiCreatedSpy.first().first().value<DataDeviceInterface *>();
QVERIFY(ddi);
m_seatInterface->setSelection(ddi->selection(), m_display->nextSerial());
// switch to other client
// create a surface and pass it keyboard focus
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
QVERIFY(surface->isValid());
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface);
// now create a DataDevice
KWayland::Client::Registry registry2;
QSignalSpy dataDeviceManagerSpy(&registry2, &KWayland::Client::Registry::dataDeviceManagerAnnounced);
registry2.setEventQueue(m_queue);
registry2.create(m_connection->display());
QVERIFY(registry2.isValid());
registry2.setup();
QVERIFY(dataDeviceManagerSpy.wait());
std::unique_ptr<KWayland::Client::DataDeviceManager> ddm(
registry2.createDataDeviceManager(dataDeviceManagerSpy.first().first().value<quint32>(), dataDeviceManagerSpy.first().last().value<quint32>()));
QVERIFY(ddm->isValid());
std::unique_ptr<KWayland::Client::DataDevice> dd(ddm->getDataDevice(m_seat));
QVERIFY(dd->isValid());
QVERIFY(ddiCreatedSpy.wait());
// unset surface and set again
m_seatInterface->setFocusedKeyboardSurface(nullptr);
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
// and delete the connection thread again
dd1.reset();
ddm1.reset();
seat.reset();
registry.reset();
queue.reset();
c->deleteLater();
thread->quit();
thread->wait();
delete thread;
}
void TestWaylandSeat::testTouch()
{
using namespace KWin;
QSignalSpy touchSpy(m_seat, &KWayland::Client::Seat::hasTouchChanged);
m_seatInterface->setHasTouch(true);
QVERIFY(touchSpy.wait());
// create the surface
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &KWin::CompositorInterface::surfaceCreated);
KWayland::Client::Surface *s = m_compositor->createSurface(m_compositor);
QVERIFY(surfaceCreatedSpy.wait());
SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value<KWin::SurfaceInterface *>();
QVERIFY(serverSurface);
// no keyboard yet
QCOMPARE(m_seatInterface->isSurfaceTouched(serverSurface), false);
KWayland::Client::Touch *touch = m_seat->createTouch(m_seat);
QVERIFY(touch->isValid());
// Process wl_touch bind request.
wl_display_flush(m_connection->display());
QCoreApplication::processEvents();
QSignalSpy sequenceStartedSpy(touch, &KWayland::Client::Touch::sequenceStarted);
QSignalSpy sequenceEndedSpy(touch, &KWayland::Client::Touch::sequenceEnded);
QSignalSpy sequenceCanceledSpy(touch, &KWayland::Client::Touch::sequenceCanceled);
QSignalSpy frameEndedSpy(touch, &KWayland::Client::Touch::frameEnded);
QSignalSpy pointAddedSpy(touch, &KWayland::Client::Touch::pointAdded);
QSignalSpy pointMovedSpy(touch, &KWayland::Client::Touch::pointMoved);
QSignalSpy pointRemovedSpy(touch, &KWayland::Client::Touch::pointRemoved);
std::chrono::milliseconds timestamp(1);
// try a few things
const QPointF surfacePosition(10, 20);
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyTouchDown(serverSurface, QPointF(10, 20), 0, QPointF(15, 26));
QVERIFY(sequenceStartedSpy.wait());
QCOMPARE(sequenceStartedSpy.count(), 1);
QCOMPARE(sequenceEndedSpy.count(), 0);
QCOMPARE(sequenceCanceledSpy.count(), 0);
QCOMPARE(frameEndedSpy.count(), 0);
QCOMPARE(pointAddedSpy.count(), 0);
QCOMPARE(pointMovedSpy.count(), 0);
QCOMPARE(pointRemovedSpy.count(), 0);
KWayland::Client::TouchPoint *tp = sequenceStartedSpy.first().first().value<KWayland::Client::TouchPoint *>();
QVERIFY(tp);
QCOMPARE(tp->downSerial(), m_seatInterface->display()->serial());
QCOMPARE(tp->id(), 0);
QVERIFY(tp->isDown());
QCOMPARE(tp->position(), QPointF(5, 6));
QCOMPARE(tp->positions().size(), 1);
QCOMPARE(tp->time(), 1u);
QCOMPARE(tp->timestamps().count(), 1);
QCOMPARE(tp->upSerial(), 0u);
QCOMPARE(tp->surface().data(), s);
QCOMPARE(touch->sequence().count(), 1);
QCOMPARE(touch->sequence().first(), tp);
// let's end the frame
m_seatInterface->notifyTouchFrame();
QVERIFY(frameEndedSpy.wait());
QCOMPARE(frameEndedSpy.count(), 1);
// move the one point
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyTouchMotion(0, QPointF(10, 20));
m_seatInterface->notifyTouchFrame();
QVERIFY(frameEndedSpy.wait());
QCOMPARE(sequenceStartedSpy.count(), 1);
QCOMPARE(sequenceEndedSpy.count(), 0);
QCOMPARE(sequenceCanceledSpy.count(), 0);
QCOMPARE(frameEndedSpy.count(), 2);
QCOMPARE(pointAddedSpy.count(), 0);
QCOMPARE(pointMovedSpy.count(), 1);
QCOMPARE(pointRemovedSpy.count(), 0);
QCOMPARE(pointMovedSpy.first().first().value<KWayland::Client::TouchPoint *>(), tp);
QCOMPARE(tp->id(), 0);
QVERIFY(tp->isDown());
QCOMPARE(tp->position(), QPointF(0, 0));
QCOMPARE(tp->positions().size(), 2);
QCOMPARE(tp->time(), 2u);
QCOMPARE(tp->timestamps().count(), 2);
QCOMPARE(tp->upSerial(), 0u);
QCOMPARE(tp->surface().data(), s);
// add onther point
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyTouchDown(serverSurface, surfacePosition, 1, QPointF(15, 26));
m_seatInterface->notifyTouchFrame();
QVERIFY(frameEndedSpy.wait());
QCOMPARE(sequenceStartedSpy.count(), 1);
QCOMPARE(sequenceEndedSpy.count(), 0);
QCOMPARE(sequenceCanceledSpy.count(), 0);
QCOMPARE(frameEndedSpy.count(), 3);
QCOMPARE(pointAddedSpy.count(), 1);
QCOMPARE(pointMovedSpy.count(), 1);
QCOMPARE(pointRemovedSpy.count(), 0);
QCOMPARE(touch->sequence().count(), 2);
QCOMPARE(touch->sequence().first(), tp);
KWayland::Client::TouchPoint *tp2 = pointAddedSpy.first().first().value<KWayland::Client::TouchPoint *>();
QVERIFY(tp2);
QCOMPARE(touch->sequence().last(), tp2);
QCOMPARE(tp2->id(), 1);
QVERIFY(tp2->isDown());
QCOMPARE(tp2->position(), QPointF(5, 6));
QCOMPARE(tp2->positions().size(), 1);
QCOMPARE(tp2->time(), 3u);
QCOMPARE(tp2->timestamps().count(), 1);
QCOMPARE(tp2->upSerial(), 0u);
QCOMPARE(tp2->surface().data(), s);
// send it an up
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyTouchUp(1);
m_seatInterface->notifyTouchFrame();
QVERIFY(frameEndedSpy.wait());
QCOMPARE(sequenceStartedSpy.count(), 1);
QCOMPARE(sequenceEndedSpy.count(), 0);
QCOMPARE(sequenceCanceledSpy.count(), 0);
QCOMPARE(frameEndedSpy.count(), 4);
QCOMPARE(pointAddedSpy.count(), 1);
QCOMPARE(pointMovedSpy.count(), 1);
QCOMPARE(pointRemovedSpy.count(), 1);
QCOMPARE(pointRemovedSpy.first().first().value<KWayland::Client::TouchPoint *>(), tp2);
QCOMPARE(tp2->id(), 1);
QVERIFY(!tp2->isDown());
QCOMPARE(tp2->position(), QPointF(5, 6));
QCOMPARE(tp2->positions().size(), 1);
QCOMPARE(tp2->time(), 4u);
QCOMPARE(tp2->timestamps().count(), 2);
QCOMPARE(tp2->upSerial(), m_seatInterface->display()->serial());
QCOMPARE(tp2->surface().data(), s);
// send another down and up
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyTouchDown(serverSurface, surfacePosition, 1, QPointF(15, 26));
m_seatInterface->notifyTouchFrame();
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyTouchUp(1);
// and send an up for the first point
m_seatInterface->notifyTouchUp(0);
m_seatInterface->notifyTouchFrame();
QVERIFY(frameEndedSpy.wait());
QCOMPARE(sequenceStartedSpy.count(), 1);
QCOMPARE(sequenceEndedSpy.count(), 1);
QCOMPARE(sequenceCanceledSpy.count(), 0);
QCOMPARE(frameEndedSpy.count(), 6);
QCOMPARE(pointAddedSpy.count(), 2);
QCOMPARE(pointMovedSpy.count(), 1);
QCOMPARE(pointRemovedSpy.count(), 3);
QCOMPARE(touch->sequence().count(), 3);
QVERIFY(!touch->sequence().at(0)->isDown());
QVERIFY(!touch->sequence().at(1)->isDown());
QVERIFY(!touch->sequence().at(2)->isDown());
QVERIFY(!m_seatInterface->isTouchSequence());
// try cancel
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->notifyTouchDown(serverSurface, QPointF(15, 26), 0, QPointF(15, 26));
m_seatInterface->notifyTouchFrame();
m_seatInterface->notifyTouchCancel();
QVERIFY(sequenceCanceledSpy.wait());
QCOMPARE(sequenceStartedSpy.count(), 2);
QCOMPARE(sequenceEndedSpy.count(), 1);
QCOMPARE(sequenceCanceledSpy.count(), 1);
QCOMPARE(frameEndedSpy.count(), 7);
QCOMPARE(pointAddedSpy.count(), 2);
QCOMPARE(pointMovedSpy.count(), 1);
QCOMPARE(pointRemovedSpy.count(), 3);
QCOMPARE(touch->sequence().first()->position(), QPointF(0, 0));
}
void TestWaylandSeat::testKeymap()
{
using namespace KWin;
m_seatInterface->setHasKeyboard(true);
QSignalSpy keyboardChangedSpy(m_seat, &KWayland::Client::Seat::hasKeyboardChanged);
QVERIFY(keyboardChangedSpy.wait());
std::unique_ptr<KWayland::Client::Keyboard> keyboard(m_seat->createKeyboard());
// create surface
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &KWin::CompositorInterface::surfaceCreated);
std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
QVERIFY(surface->isValid());
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
QVERIFY(!m_seatInterface->selection());
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
QSignalSpy keymapChangedSpy(keyboard.get(), &KWayland::Client::Keyboard::keymapChanged);
m_seatInterface->keyboard()->setKeymap(QByteArrayLiteral("foo"));
QVERIFY(keymapChangedSpy.wait());
int fd = keymapChangedSpy.first().first().toInt();
QVERIFY(fd != -1);
// Account for null terminator.
QCOMPARE(keymapChangedSpy.first().last().value<quint32>(), 4u);
QFile file;
QVERIFY(file.open(fd, QIODevice::ReadOnly));
const char *address = reinterpret_cast<char *>(file.map(0, keymapChangedSpy.first().last().value<quint32>()));
QVERIFY(address);
QCOMPARE(qstrcmp(address, "foo"), 0);
file.close();
// change the keymap
keymapChangedSpy.clear();
m_seatInterface->keyboard()->setKeymap(QByteArrayLiteral("bar"));
QVERIFY(keymapChangedSpy.wait());
fd = keymapChangedSpy.first().first().toInt();
QVERIFY(fd != -1);
// Account for null terminator.
QCOMPARE(keymapChangedSpy.first().last().value<quint32>(), 4u);
QVERIFY(file.open(fd, QIODevice::ReadWrite));
address = reinterpret_cast<char *>(file.map(0, keymapChangedSpy.first().last().value<quint32>()));
QVERIFY(address);
QCOMPARE(qstrcmp(address, "bar"), 0);
}
QTEST_GUILESS_MAIN(TestWaylandSeat)
#include "test_wayland_seat.moc"