/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // Qt #include // KWin #include "../../wayland_client/compositor.h" #include "../../wayland_client/connection_thread.h" #include "../../wayland_client/keyboard.h" #include "../../wayland_client/pointer.h" #include "../../wayland_client/surface.h" #include "../../wayland_client/registry.h" #include "../../wayland_client/seat.h" #include "../../wayland_client/shm_pool.h" #include "../../wayland_server/buffer_interface.h" #include "../../wayland_server/compositor_interface.h" #include "../../wayland_server/display.h" #include "../../wayland_server/seat_interface.h" #include "../../wayland_server/surface_interface.h" // Wayland #include #include 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(); // TODO: add test for keymap private: KWin::WaylandServer::Display *m_display; KWin::WaylandServer::CompositorInterface *m_compositorInterface; KWin::WaylandServer::SeatInterface *m_seatInterface; KWin::Wayland::ConnectionThread *m_connection; KWin::Wayland::Compositor *m_compositor; KWin::Wayland::Seat *m_seat; 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_thread(nullptr) { } void TestWaylandSeat::init() { using namespace KWin::WaylandServer; 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 KWin::Wayland::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()); // TODO: we should destroy the queue wl_event_queue *queue = wl_display_create_queue(m_connection->display()); connect(m_connection, &KWin::Wayland::ConnectionThread::eventsRead, this, [this, queue]() { wl_display_dispatch_queue_pending(m_connection->display(), queue); wl_display_flush(m_connection->display()); }, Qt::QueuedConnection); KWin::Wayland::Registry registry; QSignalSpy compositorSpy(®istry, SIGNAL(compositorAnnounced(quint32,quint32))); QSignalSpy seatSpy(®istry, SIGNAL(seatAnnounced(quint32,quint32))); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); wl_proxy_set_queue((wl_proxy*)registry.registry(), queue); 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 KWin::Wayland::Compositor(this); m_compositor->setup(registry.bindCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value())); QVERIFY(m_compositor->isValid()); m_seat = new KWin::Wayland::Seat(this); QSignalSpy nameSpy(m_seat, SIGNAL(nameChanged(QString))); m_seat->setup(registry.bindSeat(seatSpy.first().first().value(), seatSpy.first().last().value())); 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_thread) { m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; } delete m_connection; m_connection = 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("pointer"); QTest::addColumn("keyboard"); QTest::addColumn("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 KWin::Wayland; using namespace KWin::WaylandServer; QSignalSpy pointerSpy(m_seat, SIGNAL(hasPointerChanged(bool))); QVERIFY(pointerSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(pointerSpy.wait()); QSignalSpy surfaceCreatedSpy(m_compositorInterface, SIGNAL(surfaceCreated(KWin::WaylandServer::SurfaceInterface*))); QVERIFY(surfaceCreatedSpy.isValid()); Surface *s = m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); 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); 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,KWin::Wayland::Pointer::Axis,qreal))); QVERIFY(axisSpy.isValid()); QSignalSpy buttonSpy(p, SIGNAL(buttonStateChanged(quint32,quint32,quint32,KWin::Wayland::Pointer::ButtonState))); QVERIFY(buttonSpy.isValid()); serverPointer->setFocusedSurface(serverSurface, QPoint(10, 15)); QCOMPARE(serverPointer->focusedSurface(), serverSurface); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.first().first().value(), 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(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(2)); QCOMPARE(axisSpy.first().at(1).value(), Pointer::Axis::Horizontal); QCOMPARE(axisSpy.first().at(2).value(), qreal(10)); QCOMPARE(axisSpy.last().at(0).value(), quint32(3)); QCOMPARE(axisSpy.last().at(1).value(), Pointer::Axis::Vertical); QCOMPARE(axisSpy.last().at(2).value(), qreal(20)); // test button serverPointer->updateTimestamp(4); serverPointer->buttonPressed(1); QVERIFY(buttonSpy.wait()); QCOMPARE(buttonSpy.at(0).at(0).value(), m_display->serial()); serverPointer->updateTimestamp(5); serverPointer->buttonPressed(2); QVERIFY(buttonSpy.wait()); QCOMPARE(buttonSpy.at(1).at(0).value(), m_display->serial()); serverPointer->updateTimestamp(6); serverPointer->buttonReleased(2); QVERIFY(buttonSpy.wait()); QCOMPARE(buttonSpy.at(2).at(0).value(), 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(4)); // button QCOMPARE(buttonSpy.at(0).at(2).value(), quint32(1)); QCOMPARE(buttonSpy.at(0).at(3).value(), KWin::Wayland::Pointer::ButtonState::Pressed); // timestamp QCOMPARE(buttonSpy.at(1).at(1).value(), quint32(5)); // button QCOMPARE(buttonSpy.at(1).at(2).value(), quint32(2)); QCOMPARE(buttonSpy.at(1).at(3).value(), KWin::Wayland::Pointer::ButtonState::Pressed); QCOMPARE(buttonSpy.at(2).at(0).value(), serverPointer->buttonSerial(2)); // timestamp QCOMPARE(buttonSpy.at(2).at(1).value(), quint32(6)); // button QCOMPARE(buttonSpy.at(2).at(2).value(), quint32(2)); QCOMPARE(buttonSpy.at(2).at(3).value(), KWin::Wayland::Pointer::ButtonState::Released); QCOMPARE(buttonSpy.at(3).at(0).value(), serverPointer->buttonSerial(1)); // timestamp QCOMPARE(buttonSpy.at(3).at(1).value(), quint32(7)); // button QCOMPARE(buttonSpy.at(3).at(2).value(), quint32(1)); QCOMPARE(buttonSpy.at(3).at(3).value(), KWin::Wayland::Pointer::ButtonState::Released); // leave the surface serverPointer->setFocusedSurface(nullptr); QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.first().first().value(), 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 KWin::Wayland; using namespace KWin::WaylandServer; 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(KWin::WaylandServer::SurfaceInterface*))); QVERIFY(surfaceCreatedSpy.isValid()); Surface *s = m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); 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(); 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(0)); QCOMPARE(modifierSpy.first().at(1).value(), quint32(0)); QCOMPARE(modifierSpy.first().at(2).value(), quint32(0)); QCOMPARE(modifierSpy.first().at(3).value(), quint32(0)); QSignalSpy keyChangedSpy(keyboard, SIGNAL(keyChanged(quint32,KWin::Wayland::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(KEY_E)); QCOMPARE(keyChangedSpy.at(0).at(1).value(), Keyboard::KeyState::Released); QCOMPARE(keyChangedSpy.at(0).at(2).value(), quint32(4)); QCOMPARE(keyChangedSpy.at(1).at(0).value(), quint32(KEY_D)); QCOMPARE(keyChangedSpy.at(1).at(1).value(), Keyboard::KeyState::Released); QCOMPARE(keyChangedSpy.at(1).at(2).value(), quint32(5)); QCOMPARE(keyChangedSpy.at(2).at(0).value(), quint32(KEY_K)); QCOMPARE(keyChangedSpy.at(2).at(1).value(), Keyboard::KeyState::Released); QCOMPARE(keyChangedSpy.at(2).at(2).value(), quint32(6)); QCOMPARE(keyChangedSpy.at(3).at(0).value(), quint32(KEY_F1)); QCOMPARE(keyChangedSpy.at(3).at(1).value(), Keyboard::KeyState::Pressed); QCOMPARE(keyChangedSpy.at(3).at(2).value(), quint32(7)); QCOMPARE(keyChangedSpy.at(4).at(0).value(), quint32(KEY_F1)); QCOMPARE(keyChangedSpy.at(4).at(1).value(), Keyboard::KeyState::Released); QCOMPARE(keyChangedSpy.at(4).at(2).value(), quint32(8)); serverKeyboard->updateModifiers(1, 2, 3, 4); QVERIFY(modifierSpy.wait()); QCOMPARE(modifierSpy.count(), 2); QCOMPARE(modifierSpy.last().at(0).value(), quint32(1)); QCOMPARE(modifierSpy.last().at(1).value(), quint32(2)); QCOMPARE(modifierSpy.last().at(2).value(), quint32(3)); QCOMPARE(modifierSpy.last().at(3).value(), 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()); } QTEST_MAIN(TestWaylandSeat) #include "test_wayland_seat.moc"