kwin/src/wayland/autotests/client/test_wayland_seat.cpp
Martin Gräßlin eb9e5a178f Switch all auto tests to QTEST_GUILESS_MAIN
We don't need a QGuiApplication in the tests.
2014-09-23 12:00:17 +02:00

569 lines
20 KiB
C++

/********************************************************************
Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
// Qt
#include <QtTest/QtTest>
// KWin
#include "../../src/client/compositor.h"
#include "../../src/client/connection_thread.h"
#include "../../src/client/event_queue.h"
#include "../../src/client/keyboard.h"
#include "../../src/client/pointer.h"
#include "../../src/client/surface.h"
#include "../../src/client/registry.h"
#include "../../src/client/seat.h"
#include "../../src/client/shm_pool.h"
#include "../../src/server/buffer_interface.h"
#include "../../src/server/compositor_interface.h"
#include "../../src/server/display.h"
#include "../../src/server/seat_interface.h"
#include "../../src/server/surface_interface.h"
// Wayland
#include <wayland-client-protocol.h>
#include <linux/input.h>
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 testKeyboard();
void testCast();
void testDestroy();
// TODO: add test for keymap
private:
KWayland::Server::Display *m_display;
KWayland::Server::CompositorInterface *m_compositorInterface;
KWayland::Server::SeatInterface *m_seatInterface;
KWayland::Client::ConnectionThread *m_connection;
KWayland::Client::Compositor *m_compositor;
KWayland::Client::Seat *m_seat;
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_connection(nullptr)
, m_compositor(nullptr)
, m_seat(nullptr)
, m_queue(nullptr)
, m_thread(nullptr)
{
}
void TestWaylandSeat::init()
{
using namespace KWayland::Server;
delete m_display;
m_display = new Display(this);
m_display->setSocketName(s_socketName);
m_display->start();
QVERIFY(m_display->isRunning());
m_compositorInterface = m_display->createCompositor(m_display);
QVERIFY(m_compositorInterface);
m_compositorInterface->create();
QVERIFY(m_compositorInterface->isValid());
// setup connection
m_connection = new KWayland::Client::ConnectionThread;
QSignalSpy connectedSpy(m_connection, SIGNAL(connected()));
m_connection->setSocketName(s_socketName);
m_thread = new QThread(this);
m_connection->moveToThread(m_thread);
m_thread->start();
connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, m_connection,
[this]() {
if (m_connection->display()) {
wl_display_flush(m_connection->display());
}
}
);
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, SIGNAL(compositorAnnounced(quint32,quint32)));
QSignalSpy seatSpy(&registry, SIGNAL(seatAnnounced(quint32,quint32)));
registry.setEventQueue(m_queue);
registry.create(m_connection->display());
QVERIFY(registry.isValid());
registry.setup();
QVERIFY(compositorSpy.wait());
m_seatInterface = m_display->createSeat();
QVERIFY(m_seatInterface);
m_seatInterface->setName(QStringLiteral("seat0"));
m_seatInterface->create();
QVERIFY(m_seatInterface->isValid());
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, SIGNAL(nameChanged(QString)));
QVERIFY(nameSpy.wait());
}
void TestWaylandSeat::cleanup()
{
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_compositorInterface;
m_compositorInterface = nullptr;
delete m_seatInterface;
m_seatInterface = nullptr;
delete m_display;
m_display = nullptr;
}
void TestWaylandSeat::testName()
{
// no name set yet
QCOMPARE(m_seat->name(), QStringLiteral("seat0"));
QSignalSpy spy(m_seat, SIGNAL(nameChanged(QString)));
QVERIFY(spy.isValid());
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, SIGNAL(hasPointerChanged(bool)));
QVERIFY(pointerSpy.isValid());
QSignalSpy keyboardSpy(m_seat, SIGNAL(hasKeyboardChanged(bool)));
QVERIFY(keyboardSpy.isValid());
QSignalSpy touchSpy(m_seat, SIGNAL(hasTouchChanged(bool)));
QVERIFY(touchSpy.isValid());
m_seatInterface->setHasPointer(pointer);
m_seatInterface->setHasKeyboard(keyboard);
m_seatInterface->setHasTouch(touch);
// do processing
QCOMPARE(pointerSpy.wait(1000), pointer);
QCOMPARE(pointerSpy.isEmpty(), !pointer);
if (!pointerSpy.isEmpty()) {
QCOMPARE(pointerSpy.first().first().toBool(), pointer);
}
if (keyboardSpy.isEmpty()) {
QCOMPARE(keyboardSpy.wait(1000), keyboard);
}
QCOMPARE(keyboardSpy.isEmpty(), !keyboard);
if (!keyboardSpy.isEmpty()) {
QCOMPARE(keyboardSpy.first().first().toBool(), keyboard);
}
if (touchSpy.isEmpty()) {
QCOMPARE(touchSpy.wait(1000), touch);
}
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 KWayland::Client;
using namespace KWayland::Server;
QSignalSpy pointerSpy(m_seat, SIGNAL(hasPointerChanged(bool)));
QVERIFY(pointerSpy.isValid());
m_seatInterface->setHasPointer(true);
QVERIFY(pointerSpy.wait());
QSignalSpy surfaceCreatedSpy(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*)));
QVERIFY(surfaceCreatedSpy.isValid());
Surface *s = m_compositor->createSurface(m_compositor);
QVERIFY(surfaceCreatedSpy.wait());
SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value<KWayland::Server::SurfaceInterface*>();
QVERIFY(serverSurface);
PointerInterface *serverPointer = m_seatInterface->pointer();
serverPointer->setGlobalPos(QPoint(20, 18));
serverPointer->setFocusedSurface(serverSurface, QPoint(10, 15));
// no pointer yet - won't be set
QVERIFY(!serverPointer->focusedSurface());
Pointer *p = m_seat->createPointer(m_seat);
QVERIFY(p->isValid());
wl_display_flush(m_connection->display());
QTest::qWait(100);
QSignalSpy enteredSpy(p, SIGNAL(entered(quint32,QPointF)));
QVERIFY(enteredSpy.isValid());
QSignalSpy leftSpy(p, SIGNAL(left(quint32)));
QVERIFY(leftSpy.isValid());
QSignalSpy motionSpy(p, SIGNAL(motion(QPointF,quint32)));
QVERIFY(motionSpy.isValid());
QSignalSpy axisSpy(p, SIGNAL(axisChanged(quint32,KWayland::Client::Pointer::Axis,qreal)));
QVERIFY(axisSpy.isValid());
QSignalSpy buttonSpy(p, SIGNAL(buttonStateChanged(quint32,quint32,quint32,KWayland::Client::Pointer::ButtonState)));
QVERIFY(buttonSpy.isValid());
serverPointer->setFocusedSurface(serverSurface, QPoint(10, 15));
QCOMPARE(serverPointer->focusedSurface(), serverSurface);
QVERIFY(enteredSpy.wait());
QCOMPARE(enteredSpy.first().first().value<quint32>(), m_display->serial());
QCOMPARE(enteredSpy.first().last().toPoint(), QPoint(10, 3));
// test motion
serverPointer->updateTimestamp(1);
serverPointer->setGlobalPos(QPoint(10, 16));
QVERIFY(motionSpy.wait());
QCOMPARE(motionSpy.first().first().toPoint(), QPoint(0, 1));
QCOMPARE(motionSpy.first().last().value<quint32>(), quint32(1));
// test axis
serverPointer->updateTimestamp(2);
serverPointer->axis(Qt::Horizontal, 10);
QVERIFY(axisSpy.wait());
serverPointer->updateTimestamp(3);
serverPointer->axis(Qt::Vertical, 20);
QVERIFY(axisSpy.wait());
QCOMPARE(axisSpy.first().at(0).value<quint32>(), quint32(2));
QCOMPARE(axisSpy.first().at(1).value<Pointer::Axis>(), 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<Pointer::Axis>(), Pointer::Axis::Vertical);
QCOMPARE(axisSpy.last().at(2).value<qreal>(), qreal(20));
// test button
serverPointer->updateTimestamp(4);
serverPointer->buttonPressed(1);
QVERIFY(buttonSpy.wait());
QCOMPARE(buttonSpy.at(0).at(0).value<quint32>(), m_display->serial());
serverPointer->updateTimestamp(5);
serverPointer->buttonPressed(2);
QVERIFY(buttonSpy.wait());
QCOMPARE(buttonSpy.at(1).at(0).value<quint32>(), m_display->serial());
serverPointer->updateTimestamp(6);
serverPointer->buttonReleased(2);
QVERIFY(buttonSpy.wait());
QCOMPARE(buttonSpy.at(2).at(0).value<quint32>(), m_display->serial());
serverPointer->updateTimestamp(7);
serverPointer->buttonReleased(1);
QVERIFY(buttonSpy.wait());
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>(), serverPointer->buttonSerial(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>(), serverPointer->buttonSerial(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
serverPointer->setFocusedSurface(nullptr);
QVERIFY(leftSpy.wait());
QCOMPARE(leftSpy.first().first().value<quint32>(), m_display->serial());
// enter it again
serverPointer->setFocusedSurface(serverSurface, QPoint(0, 0));
QVERIFY(enteredSpy.wait());
delete s;
wl_display_flush(m_connection->display());
QTest::qWait(100);
QVERIFY(!serverPointer->focusedSurface());
}
void TestWaylandSeat::testKeyboard()
{
using namespace KWayland::Client;
using namespace KWayland::Server;
QSignalSpy keyboardSpy(m_seat, SIGNAL(hasKeyboardChanged(bool)));
QVERIFY(keyboardSpy.isValid());
m_seatInterface->setHasKeyboard(true);
QVERIFY(keyboardSpy.wait());
// create the surface
QSignalSpy surfaceCreatedSpy(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*)));
QVERIFY(surfaceCreatedSpy.isValid());
Surface *s = m_compositor->createSurface(m_compositor);
QVERIFY(surfaceCreatedSpy.wait());
SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value<KWayland::Server::SurfaceInterface*>();
QVERIFY(serverSurface);
KeyboardInterface *serverKeyboard = m_seatInterface->keyboard();
serverKeyboard->setFocusedSurface(serverSurface);
// no pointer yet - won't be set
QVERIFY(!serverKeyboard->focusedSurface());
Keyboard *keyboard = m_seat->createKeyboard(m_seat);
QVERIFY(keyboard->isValid());
wl_display_flush(m_connection->display());
QTest::qWait(100);
serverKeyboard->updateTimestamp(1);
serverKeyboard->keyPressed(KEY_K);
serverKeyboard->updateTimestamp(2);
serverKeyboard->keyPressed(KEY_D);
serverKeyboard->updateTimestamp(3);
serverKeyboard->keyPressed(KEY_E);
QSignalSpy modifierSpy(keyboard, SIGNAL(modifiersChanged(quint32,quint32,quint32,quint32)));
QVERIFY(modifierSpy.isValid());
// TODO: add a signalspy for enter
serverKeyboard->setFocusedSurface(serverSurface);
QCOMPARE(serverKeyboard->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(0));
QCOMPARE(modifierSpy.first().at(1).value<quint32>(), quint32(0));
QCOMPARE(modifierSpy.first().at(2).value<quint32>(), quint32(0));
QCOMPARE(modifierSpy.first().at(3).value<quint32>(), quint32(0));
QSignalSpy keyChangedSpy(keyboard, SIGNAL(keyChanged(quint32,KWayland::Client::Keyboard::KeyState,quint32)));
QVERIFY(keyChangedSpy.isValid());
serverKeyboard->updateTimestamp(4);
serverKeyboard->keyReleased(KEY_E);
QVERIFY(keyChangedSpy.wait());
serverKeyboard->updateTimestamp(5);
serverKeyboard->keyReleased(KEY_D);
QVERIFY(keyChangedSpy.wait());
serverKeyboard->updateTimestamp(6);
serverKeyboard->keyReleased(KEY_K);
QVERIFY(keyChangedSpy.wait());
serverKeyboard->updateTimestamp(7);
serverKeyboard->keyPressed(KEY_F1);
QVERIFY(keyChangedSpy.wait());
serverKeyboard->updateTimestamp(8);
serverKeyboard->keyReleased(KEY_F1);
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<Keyboard::KeyState>(), 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<Keyboard::KeyState>(), 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<Keyboard::KeyState>(), 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<Keyboard::KeyState>(), 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<Keyboard::KeyState>(), Keyboard::KeyState::Released);
QCOMPARE(keyChangedSpy.at(4).at(2).value<quint32>(), quint32(8));
serverKeyboard->updateModifiers(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));
// TODO: add a test for leave signal
serverKeyboard->setFocusedSurface(nullptr);
QVERIFY(!serverKeyboard->focusedSurface());
// enter it again
serverKeyboard->setFocusedSurface(serverSurface);
QVERIFY(modifierSpy.wait());
QCOMPARE(serverKeyboard->focusedSurface(), serverSurface);
delete s;
wl_display_flush(m_connection->display());
QTest::qWait(100);
QVERIFY(!serverKeyboard->focusedSurface());
}
void TestWaylandSeat::testCast()
{
using namespace KWayland::Client;
Registry registry;
QSignalSpy seatSpy(&registry, SIGNAL(seatAnnounced(quint32,quint32)));
registry.create(m_connection->display());
QVERIFY(registry.isValid());
registry.setup();
QVERIFY(seatSpy.wait());
Seat s;
QVERIFY(!s.isValid());
auto wlSeat = registry.bindSeat(seatSpy.first().first().value<quint32>(), seatSpy.first().last().value<quint32>());
QVERIFY(wlSeat);
s.setup(wlSeat);
QVERIFY(s.isValid());
QCOMPARE((wl_seat*)s, wlSeat);
const Seat &s2(s);
QCOMPARE((wl_seat*)s2, wlSeat);
}
void TestWaylandSeat::testDestroy()
{
using namespace KWayland::Client;
QSignalSpy keyboardSpy(m_seat, SIGNAL(hasKeyboardChanged(bool)));
QVERIFY(keyboardSpy.isValid());
m_seatInterface->setHasKeyboard(true);
QVERIFY(keyboardSpy.wait());
Keyboard *k = m_seat->createKeyboard(m_seat);
QVERIFY(k->isValid());
QSignalSpy pointerSpy(m_seat, SIGNAL(hasPointerChanged(bool)));
QVERIFY(pointerSpy.isValid());
m_seatInterface->setHasPointer(true);
QVERIFY(pointerSpy.wait());
Pointer *p = m_seat->createPointer(m_seat);
QVERIFY(p->isValid());
delete m_compositor;
m_compositor = nullptr;
connect(m_connection, &ConnectionThread::connectionDied, m_seat, &Seat::destroy);
connect(m_connection, &ConnectionThread::connectionDied, m_queue, &EventQueue::destroy);
QVERIFY(m_seat->isValid());
QSignalSpy connectionDiedSpy(m_connection, SIGNAL(connectionDied()));
QVERIFY(connectionDiedSpy.isValid());
delete m_display;
m_display = nullptr;
m_compositorInterface = nullptr;
m_seatInterface = nullptr;
QVERIFY(connectionDiedSpy.wait());
// now the seat should be destroyed;
QVERIFY(!m_seat->isValid());
QVERIFY(!k->isValid());
QVERIFY(!p->isValid());
// calling destroy again should not fail
m_seat->destroy();
k->destroy();
p->destroy();
}
QTEST_GUILESS_MAIN(TestWaylandSeat)
#include "test_wayland_seat.moc"