diff --git a/autotests/client/test_drag_drop.cpp b/autotests/client/test_drag_drop.cpp index 6158397..5af0461 100644 --- a/autotests/client/test_drag_drop.cpp +++ b/autotests/client/test_drag_drop.cpp @@ -1,480 +1,480 @@ /******************************************************************** Copyright 2016 Martin Gräßlin 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 . *********************************************************************/ // Qt #include // KWin #include "../../src/client/compositor.h" #include "../../src/client/connection_thread.h" #include "../../src/client/datadevice.h" #include "../../src/client/datadevicemanager.h" #include "../../src/client/datasource.h" #include "../../src/client/event_queue.h" #include "../../src/client/pointer.h" #include "../../src/client/registry.h" #include "../../src/client/seat.h" #include "../../src/client/shell.h" #include "../../src/client/shm_pool.h" #include "../../src/client/surface.h" #include "../../src/server/display.h" #include "../../src/server/compositor_interface.h" #include "../../src/server/datadevicemanager_interface.h" #include "../../src/server/datasource_interface.h" #include "../../src/server/seat_interface.h" #include "../../src/server/shell_interface.h" class TestDragAndDrop : public QObject { Q_OBJECT private Q_SLOTS: void init(); void cleanup(); void testDragAndDrop(); void testDragAndDropWithCancelByDestroyDataSource(); void testPointerEventsIgnored(); private: KWayland::Client::Surface *createSurface(); KWayland::Server::SurfaceInterface *getServerSurface(); KWayland::Server::Display *m_display = nullptr; KWayland::Server::CompositorInterface *m_compositorInterface = nullptr; KWayland::Server::DataDeviceManagerInterface *m_dataDeviceManagerInterface = nullptr; KWayland::Server::SeatInterface *m_seatInterface = nullptr; KWayland::Server::ShellInterface *m_shellInterface = nullptr; KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; KWayland::Client::DataDevice *m_dataDevice = nullptr; KWayland::Client::DataSource *m_dataSource = nullptr; QThread *m_thread = nullptr; KWayland::Client::Registry *m_registry = nullptr; KWayland::Client::Seat *m_seat = nullptr; KWayland::Client::Pointer *m_pointer = nullptr; KWayland::Client::DataDeviceManager *m_ddm = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; }; static const QString s_socketName = QStringLiteral("kwayland-test-wayland-drag-n-drop-0"); void TestDragAndDrop::init() { using namespace KWayland::Server; using namespace KWayland::Client; delete m_display; m_display = new Display(this); m_display->setSocketName(s_socketName); m_display->start(); QVERIFY(m_display->isRunning()); // setup connection m_connection = new KWayland::Client::ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_compositorInterface = m_display->createCompositor(m_display); m_compositorInterface->create(); QVERIFY(m_compositorInterface->isValid()); m_seatInterface = m_display->createSeat(m_display); m_seatInterface->setHasPointer(true); m_seatInterface->create(); QVERIFY(m_seatInterface->isValid()); m_dataDeviceManagerInterface = m_display->createDataDeviceManager(m_display); m_dataDeviceManagerInterface->create(); QVERIFY(m_dataDeviceManagerInterface->isValid()); m_display->createShm(); m_shellInterface = m_display->createShell(m_display); m_shellInterface->create(); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); m_registry = new Registry(); QSignalSpy interfacesAnnouncedSpy(m_registry, &Registry::interfaceAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QVERIFY(!m_registry->eventQueue()); m_registry->setEventQueue(m_queue); QCOMPARE(m_registry->eventQueue(), m_queue); m_registry->create(m_connection); QVERIFY(m_registry->isValid()); m_registry->setup(); QVERIFY(interfacesAnnouncedSpy.wait()); #define CREATE(variable, factory, iface) \ variable = m_registry->create##factory(m_registry->interface(Registry::Interface::iface).name, m_registry->interface(Registry::Interface::iface).version, this); \ QVERIFY(variable); CREATE(m_compositor, Compositor, Compositor) CREATE(m_seat, Seat, Seat) CREATE(m_ddm, DataDeviceManager, DataDeviceManager) CREATE(m_shm, ShmPool, Shm) CREATE(m_shell, Shell, Shell) #undef CREATE QSignalSpy pointerSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(pointerSpy.isValid()); QVERIFY(pointerSpy.wait()); m_pointer = m_seat->createPointer(m_seat); QVERIFY(m_pointer->isValid()); m_dataDevice = m_ddm->getDataDevice(m_seat, this); QVERIFY(m_dataDevice->isValid()); m_dataSource = m_ddm->createDataSource(this); QVERIFY(m_dataSource->isValid()); m_dataSource->offer(QStringLiteral("text/plain")); } void TestDragAndDrop::cleanup() { #define DELETE(name) \ if (name) { \ delete name; \ name = nullptr; \ } DELETE(m_dataSource) DELETE(m_dataDevice) DELETE(m_shell) DELETE(m_shm) DELETE(m_compositor) DELETE(m_ddm) DELETE(m_seat) DELETE(m_queue) DELETE(m_registry) #undef DELETE if (m_thread) { m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; } delete m_connection; m_connection = nullptr; delete m_display; m_display = nullptr; } KWayland::Client::Surface *TestDragAndDrop::createSurface() { using namespace KWayland::Client; auto s = m_compositor->createSurface(); QImage img(QSize(100, 200), QImage::Format_RGB32); img.fill(Qt::red); s->attachBuffer(m_shm->createBuffer(img)); s->damage(QRect(0, 0, 100, 200)); s->commit(Surface::CommitFlag::None); return s; } KWayland::Server::SurfaceInterface *TestDragAndDrop::getServerSurface() { using namespace KWayland::Server; QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); if (!surfaceCreatedSpy.isValid()) { return nullptr; } - if (!surfaceCreatedSpy.wait()) { + if (!surfaceCreatedSpy.wait(500)) { return nullptr; } return surfaceCreatedSpy.first().first().value(); } void TestDragAndDrop::testDragAndDrop() { // this test verifies the very basic drag and drop on one surface, an enter, a move and the drop using namespace KWayland::Server; using namespace KWayland::Client; // first create a window QScopedPointer s(createSurface()); auto serverSurface = getServerSurface(); QVERIFY(serverSurface); QSignalSpy dataSourceSelectedActionChangedSpy(m_dataSource, &DataSource::selectedDragAndDropActionChanged); QVERIFY(dataSourceSelectedActionChangedSpy.isValid()); // now we need to pass pointer focus to the Surface and simulate a button press QSignalSpy buttonPressSpy(m_pointer, &Pointer::buttonStateChanged); QVERIFY(buttonPressSpy.isValid()); m_seatInterface->setFocusedPointerSurface(serverSurface); m_seatInterface->setTimestamp(2); m_seatInterface->pointerButtonPressed(1); QVERIFY(buttonPressSpy.wait()); QCOMPARE(buttonPressSpy.first().at(1).value(), quint32(2)); // add some signal spies for client side QSignalSpy dragEnteredSpy(m_dataDevice, &DataDevice::dragEntered); QVERIFY(dragEnteredSpy.isValid()); QSignalSpy dragMotionSpy(m_dataDevice, &DataDevice::dragMotion); QVERIFY(dragMotionSpy.isValid()); QSignalSpy pointerMotionSpy(m_pointer, &Pointer::motion); QVERIFY(pointerMotionSpy.isValid()); QSignalSpy sourceDropSpy(m_dataSource, &DataSource::dragAndDropPerformed); QVERIFY(sourceDropSpy.isValid()); // now we can start the drag and drop QSignalSpy dragStartedSpy(m_seatInterface, &SeatInterface::dragStarted); QVERIFY(dragStartedSpy.isValid()); m_dataSource->setDragAndDropActions(DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move); m_dataDevice->startDrag(buttonPressSpy.first().first().value(), m_dataSource, s.data()); QVERIFY(dragStartedSpy.wait()); QCOMPARE(m_seatInterface->dragSurface(), serverSurface); QCOMPARE(m_seatInterface->dragSurfaceTransformation(), QMatrix4x4()); QVERIFY(!m_seatInterface->dragSource()->icon()); QCOMPARE(m_seatInterface->dragSource()->dragImplicitGrabSerial(), buttonPressSpy.first().first().value()); QVERIFY(dragEnteredSpy.wait()); QCOMPARE(dragEnteredSpy.count(), 1); QCOMPARE(dragEnteredSpy.first().first().value(), m_display->serial()); QCOMPARE(dragEnteredSpy.first().last().toPointF(), QPointF(0, 0)); QCOMPARE(m_dataDevice->dragSurface().data(), s.data()); auto offer = m_dataDevice->dragOffer(); QVERIFY(offer); QCOMPARE(offer->selectedDragAndDropAction(), DataDeviceManager::DnDAction::None); QSignalSpy offerActionChangedSpy(offer, &DataOffer::selectedDragAndDropActionChanged); QVERIFY(offerActionChangedSpy.isValid()); QCOMPARE(m_dataDevice->dragOffer()->offeredMimeTypes().count(), 1); QCOMPARE(m_dataDevice->dragOffer()->offeredMimeTypes().first().name(), QStringLiteral("text/plain")); QTRY_COMPARE(offer->sourceDragAndDropActions(), DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move); offer->setDragAndDropActions(DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move, DataDeviceManager::DnDAction::Move); QVERIFY(offerActionChangedSpy.wait()); QCOMPARE(offerActionChangedSpy.count(), 1); QCOMPARE(offer->selectedDragAndDropAction(), DataDeviceManager::DnDAction::Move); QCOMPARE(dataSourceSelectedActionChangedSpy.count(), 1); QCOMPARE(m_dataSource->selectedDragAndDropAction(), DataDeviceManager::DnDAction::Move); // simulate motion m_seatInterface->setTimestamp(3); m_seatInterface->setPointerPos(QPointF(3, 3)); QVERIFY(dragMotionSpy.wait()); QCOMPARE(dragMotionSpy.count(), 1); QCOMPARE(dragMotionSpy.first().first().toPointF(), QPointF(3, 3)); QCOMPARE(dragMotionSpy.first().last().toUInt(), 3u); // simulate drop QSignalSpy serverDragEndedSpy(m_seatInterface, &SeatInterface::dragEnded); QVERIFY(serverDragEndedSpy.isValid()); QSignalSpy droppedSpy(m_dataDevice, &DataDevice::dropped); QVERIFY(droppedSpy.isValid()); m_seatInterface->setTimestamp(4); m_seatInterface->pointerButtonReleased(1); QVERIFY(sourceDropSpy.isEmpty()); QVERIFY(droppedSpy.wait()); QCOMPARE(sourceDropSpy.count(), 1); QCOMPARE(serverDragEndedSpy.count(), 1); QSignalSpy finishedSpy(m_dataSource, &DataSource::dragAndDropFinished); QVERIFY(finishedSpy.isValid()); offer->dragAndDropFinished(); QVERIFY(finishedSpy.wait()); delete offer; // verify that we did not get any further input events QVERIFY(pointerMotionSpy.isEmpty()); QCOMPARE(buttonPressSpy.count(), 1); } void TestDragAndDrop::testDragAndDropWithCancelByDestroyDataSource() { // this test simulates the problem from BUG 389221 using namespace KWayland::Server; using namespace KWayland::Client; // first create a window QScopedPointer s(createSurface()); auto serverSurface = getServerSurface(); QVERIFY(serverSurface); QSignalSpy dataSourceSelectedActionChangedSpy(m_dataSource, &DataSource::selectedDragAndDropActionChanged); QVERIFY(dataSourceSelectedActionChangedSpy.isValid()); // now we need to pass pointer focus to the Surface and simulate a button press QSignalSpy buttonPressSpy(m_pointer, &Pointer::buttonStateChanged); QVERIFY(buttonPressSpy.isValid()); m_seatInterface->setFocusedPointerSurface(serverSurface); m_seatInterface->setTimestamp(2); m_seatInterface->pointerButtonPressed(1); QVERIFY(buttonPressSpy.wait()); QCOMPARE(buttonPressSpy.first().at(1).value(), quint32(2)); // add some signal spies for client side QSignalSpy dragEnteredSpy(m_dataDevice, &DataDevice::dragEntered); QVERIFY(dragEnteredSpy.isValid()); QSignalSpy dragMotionSpy(m_dataDevice, &DataDevice::dragMotion); QVERIFY(dragMotionSpy.isValid()); QSignalSpy pointerMotionSpy(m_pointer, &Pointer::motion); QVERIFY(pointerMotionSpy.isValid()); QSignalSpy dragLeftSpy(m_dataDevice, &DataDevice::dragLeft); QVERIFY(dragLeftSpy.isValid()); // now we can start the drag and drop QSignalSpy dragStartedSpy(m_seatInterface, &SeatInterface::dragStarted); QVERIFY(dragStartedSpy.isValid()); m_dataSource->setDragAndDropActions(DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move); m_dataDevice->startDrag(buttonPressSpy.first().first().value(), m_dataSource, s.data()); QVERIFY(dragStartedSpy.wait()); QCOMPARE(m_seatInterface->dragSurface(), serverSurface); QCOMPARE(m_seatInterface->dragSurfaceTransformation(), QMatrix4x4()); QVERIFY(!m_seatInterface->dragSource()->icon()); QCOMPARE(m_seatInterface->dragSource()->dragImplicitGrabSerial(), buttonPressSpy.first().first().value()); QVERIFY(dragEnteredSpy.wait()); QCOMPARE(dragEnteredSpy.count(), 1); QCOMPARE(dragEnteredSpy.first().first().value(), m_display->serial()); QCOMPARE(dragEnteredSpy.first().last().toPointF(), QPointF(0, 0)); QCOMPARE(m_dataDevice->dragSurface().data(), s.data()); auto offer = m_dataDevice->dragOffer(); QVERIFY(offer); QCOMPARE(offer->selectedDragAndDropAction(), DataDeviceManager::DnDAction::None); QSignalSpy offerActionChangedSpy(offer, &DataOffer::selectedDragAndDropActionChanged); QVERIFY(offerActionChangedSpy.isValid()); QCOMPARE(m_dataDevice->dragOffer()->offeredMimeTypes().count(), 1); QCOMPARE(m_dataDevice->dragOffer()->offeredMimeTypes().first().name(), QStringLiteral("text/plain")); QTRY_COMPARE(offer->sourceDragAndDropActions(), DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move); offer->setDragAndDropActions(DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move, DataDeviceManager::DnDAction::Move); QVERIFY(offerActionChangedSpy.wait()); QCOMPARE(offerActionChangedSpy.count(), 1); QCOMPARE(offer->selectedDragAndDropAction(), DataDeviceManager::DnDAction::Move); QCOMPARE(dataSourceSelectedActionChangedSpy.count(), 1); QCOMPARE(m_dataSource->selectedDragAndDropAction(), DataDeviceManager::DnDAction::Move); // simulate motion m_seatInterface->setTimestamp(3); m_seatInterface->setPointerPos(QPointF(3, 3)); QVERIFY(dragMotionSpy.wait()); QCOMPARE(dragMotionSpy.count(), 1); QCOMPARE(dragMotionSpy.first().first().toPointF(), QPointF(3, 3)); QCOMPARE(dragMotionSpy.first().last().toUInt(), 3u); // now delete the DataSource delete m_dataSource; m_dataSource = nullptr; QSignalSpy serverDragEndedSpy(m_seatInterface, &SeatInterface::dragEnded); QVERIFY(serverDragEndedSpy.isValid()); QVERIFY(dragLeftSpy.isEmpty()); QVERIFY(dragLeftSpy.wait()); QTRY_COMPARE(dragLeftSpy.count(), 1); QTRY_COMPARE(serverDragEndedSpy.count(), 1); // simulate drop QSignalSpy droppedSpy(m_dataDevice, &DataDevice::dropped); QVERIFY(droppedSpy.isValid()); m_seatInterface->setTimestamp(4); m_seatInterface->pointerButtonReleased(1); - QVERIFY(!droppedSpy.wait()); + QVERIFY(!droppedSpy.wait(500)); // verify that we did not get any further input events QVERIFY(pointerMotionSpy.isEmpty()); QCOMPARE(buttonPressSpy.count(), 2); } void TestDragAndDrop::testPointerEventsIgnored() { // this test verifies that all pointer events are ignored on the focused Pointer device during drag using namespace KWayland::Server; using namespace KWayland::Client; // first create a window QScopedPointer s(createSurface()); auto serverSurface = getServerSurface(); QVERIFY(serverSurface); // pass it pointer focus m_seatInterface->setFocusedPointerSurface(serverSurface); // create signal spies for all the pointer events QSignalSpy pointerEnteredSpy(m_pointer, &Pointer::entered); QVERIFY(pointerEnteredSpy.isValid()); QSignalSpy pointerLeftSpy(m_pointer, &Pointer::left); QVERIFY(pointerLeftSpy.isValid()); QSignalSpy pointerMotionSpy(m_pointer, &Pointer::motion); QVERIFY(pointerMotionSpy.isValid()); QSignalSpy axisSpy(m_pointer, &Pointer::axisChanged); QVERIFY(axisSpy.isValid()); QSignalSpy buttonSpy(m_pointer, &Pointer::buttonStateChanged); QVERIFY(buttonSpy.isValid()); QSignalSpy dragEnteredSpy(m_dataDevice, &DataDevice::dragEntered); QVERIFY(dragEnteredSpy.isValid()); // first simulate a few things quint32 timestamp = 1; m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(10, 10)); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerAxis(Qt::Vertical, 5); // verify that we have those QVERIFY(axisSpy.wait()); QCOMPARE(axisSpy.count(), 1); QCOMPARE(pointerMotionSpy.count(), 1); QCOMPARE(pointerEnteredSpy.count(), 1); QVERIFY(buttonSpy.isEmpty()); QVERIFY(pointerLeftSpy.isEmpty()); // let's start the drag m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonPressed(1); QVERIFY(buttonSpy.wait()); QCOMPARE(buttonSpy.count(), 1); m_dataDevice->startDrag(buttonSpy.first().first().value(), m_dataSource, s.data()); QVERIFY(dragEnteredSpy.wait()); // now simulate all the possible pointer interactions m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonPressed(2); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonReleased(2); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerAxis(Qt::Vertical, 5); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerAxis(Qt::Horizontal, 5); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setFocusedPointerSurface(nullptr); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setFocusedPointerSurface(serverSurface); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(50, 50)); // last but not least, simulate the drop QSignalSpy droppedSpy(m_dataDevice, &DataDevice::dropped); QVERIFY(droppedSpy.isValid()); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonReleased(1); QVERIFY(droppedSpy.wait()); // all the changes should have been ignored QCOMPARE(axisSpy.count(), 1); QCOMPARE(pointerMotionSpy.count(), 1); QCOMPARE(pointerEnteredSpy.count(), 1); QCOMPARE(buttonSpy.count(), 1); QVERIFY(pointerLeftSpy.isEmpty()); } QTEST_GUILESS_MAIN(TestDragAndDrop) #include "test_drag_drop.moc" diff --git a/autotests/client/test_pointer_constraints.cpp b/autotests/client/test_pointer_constraints.cpp index 5c7be6b..adf2ca2 100644 --- a/autotests/client/test_pointer_constraints.cpp +++ b/autotests/client/test_pointer_constraints.cpp @@ -1,443 +1,443 @@ /******************************************************************** Copyright 2016 Martin Gräßlin 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 . *********************************************************************/ // Qt #include // client #include "../../src/client/connection_thread.h" #include "../../src/client/compositor.h" #include "../../src/client/event_queue.h" #include "../../src/client/pointer.h" #include "../../src/client/pointerconstraints.h" #include "../../src/client/registry.h" #include "../../src/client/seat.h" #include "../../src/client/surface.h" // server #include "../../src/server/display.h" #include "../../src/server/compositor_interface.h" #include "../../src/server/pointerconstraints_interface.h" #include "../../src/server/seat_interface.h" #include "../../src/server/surface_interface.h" using namespace KWayland::Client; using namespace KWayland::Server; Q_DECLARE_METATYPE(KWayland::Client::PointerConstraints::LifeTime) Q_DECLARE_METATYPE(KWayland::Server::ConfinedPointerInterface::LifeTime) Q_DECLARE_METATYPE(KWayland::Server::LockedPointerInterface::LifeTime) class TestPointerConstraints : public QObject { Q_OBJECT private Q_SLOTS: void init(); void cleanup(); void testLockPointer_data(); void testLockPointer(); void testConfinePointer_data(); void testConfinePointer(); void testAlreadyConstrained_data(); void testAlreadyConstrained(); private: Display *m_display = nullptr; CompositorInterface *m_compositorInterface = nullptr; SeatInterface *m_seatInterface = nullptr; PointerConstraintsInterface *m_pointerConstraintsInterface = nullptr; ConnectionThread *m_connection = nullptr; QThread *m_thread = nullptr; EventQueue *m_queue = nullptr; Compositor *m_compositor = nullptr; Seat *m_seat = nullptr; Pointer *m_pointer = nullptr; PointerConstraints *m_pointerConstraints = nullptr; }; static const QString s_socketName = QStringLiteral("kwayland-test-pointer_constraint-0"); void TestPointerConstraints::init() { delete m_display; m_display = new Display(this); m_display->setSocketName(s_socketName); m_display->start(); QVERIFY(m_display->isRunning()); m_display->createShm(); m_seatInterface = m_display->createSeat(m_display); m_seatInterface->setHasPointer(true); m_seatInterface->create(); m_compositorInterface = m_display->createCompositor(m_display); m_compositorInterface->create(); m_pointerConstraintsInterface = m_display->createPointerConstraints(PointerConstraintsInterfaceVersion::UnstableV1, m_display); m_pointerConstraintsInterface->create(); // setup connection m_connection = new KWayland::Client::ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); 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 EventQueue(this); m_queue->setup(m_connection); Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QSignalSpy interfaceAnnouncedSpy(®istry, &Registry::interfaceAnnounced); QVERIFY(interfaceAnnouncedSpy.isValid()); registry.setEventQueue(m_queue); registry.create(m_connection); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(interfacesAnnouncedSpy.wait()); m_compositor = registry.createCompositor(registry.interface(Registry::Interface::Compositor).name, registry.interface(Registry::Interface::Compositor).version, this); QVERIFY(m_compositor); QVERIFY(m_compositor->isValid()); m_pointerConstraints = registry.createPointerConstraints(registry.interface(Registry::Interface::PointerConstraintsUnstableV1).name, registry.interface(Registry::Interface::PointerConstraintsUnstableV1).version, this); QVERIFY(m_pointerConstraints); QVERIFY(m_pointerConstraints->isValid()); m_seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name, registry.interface(Registry::Interface::Seat).version, this); QVERIFY(m_seat); QVERIFY(m_seat->isValid()); QSignalSpy pointerChangedSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(pointerChangedSpy.isValid()); QVERIFY(pointerChangedSpy.wait()); m_pointer = m_seat->createPointer(this); QVERIFY(m_pointer); } void TestPointerConstraints::cleanup() { #define CLEANUP(variable) \ if (variable) { \ delete variable; \ variable = nullptr; \ } CLEANUP(m_compositor) CLEANUP(m_pointerConstraints) CLEANUP(m_pointer) CLEANUP(m_seat) CLEANUP(m_queue) if (m_connection) { m_connection->deleteLater(); m_connection = nullptr; } if (m_thread) { m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; } CLEANUP(m_compositorInterface) CLEANUP(m_seatInterface); CLEANUP(m_pointerConstraintsInterface) CLEANUP(m_display) #undef CLEANUP } void TestPointerConstraints::testLockPointer_data() { QTest::addColumn("clientLifeTime"); QTest::addColumn("serverLifeTime"); QTest::addColumn("hasConstraintAfterUnlock"); QTest::addColumn("pointerChangedCount"); QTest::newRow("persistent") << PointerConstraints::LifeTime::Persistent << LockedPointerInterface::LifeTime::Persistent << true << 1; QTest::newRow("oneshot") << PointerConstraints::LifeTime::OneShot << LockedPointerInterface::LifeTime::OneShot << false << 2; } void TestPointerConstraints::testLockPointer() { // this test verifies the basic interaction for lock pointer // first create a surface QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(surface->isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); QVERIFY(serverSurface->lockedPointer().isNull()); QVERIFY(serverSurface->confinedPointer().isNull()); // now create the locked pointer QSignalSpy pointerConstraintsChangedSpy(serverSurface, &SurfaceInterface::pointerConstraintsChanged); QVERIFY(pointerConstraintsChangedSpy.isValid()); QFETCH(PointerConstraints::LifeTime, clientLifeTime); QScopedPointer lockedPointer(m_pointerConstraints->lockPointer(surface.data(), m_pointer, nullptr, clientLifeTime)); QSignalSpy lockedSpy(lockedPointer.data(), &LockedPointer::locked); QVERIFY(lockedSpy.isValid()); QSignalSpy unlockedSpy(lockedPointer.data(), &LockedPointer::unlocked); QVERIFY(unlockedSpy.isValid()); QVERIFY(lockedPointer->isValid()); QVERIFY(pointerConstraintsChangedSpy.wait()); auto serverLockedPointer = serverSurface->lockedPointer(); QVERIFY(serverLockedPointer); QVERIFY(serverSurface->confinedPointer().isNull()); QCOMPARE(serverLockedPointer->isLocked(), false); QCOMPARE(serverLockedPointer->region(), QRegion()); QFETCH(LockedPointerInterface::LifeTime, serverLifeTime); QCOMPARE(serverLockedPointer->lifeTime(), serverLifeTime); // setting to unlocked now should not trigger an unlocked spy serverLockedPointer->setLocked(false); - QVERIFY(!unlockedSpy.wait()); + QVERIFY(!unlockedSpy.wait(500)); // try setting a region QSignalSpy destroyedSpy(serverLockedPointer.data(), &QObject::destroyed); QVERIFY(destroyedSpy.isValid()); QSignalSpy regionChangedSpy(serverLockedPointer.data(), &LockedPointerInterface::regionChanged); QVERIFY(regionChangedSpy.isValid()); lockedPointer->setRegion(m_compositor->createRegion(QRegion(0, 5, 10, 20), m_compositor)); // it's double buffered - QVERIFY(!regionChangedSpy.wait()); + QVERIFY(!regionChangedSpy.wait(500)); surface->commit(Surface::CommitFlag::None); QVERIFY(regionChangedSpy.wait()); QCOMPARE(serverLockedPointer->region(), QRegion(0, 5, 10, 20)); // and unset region again lockedPointer->setRegion(nullptr); surface->commit(Surface::CommitFlag::None); QVERIFY(regionChangedSpy.wait()); QCOMPARE(serverLockedPointer->region(), QRegion()); // let's lock the surface QSignalSpy lockedChangedSpy(serverLockedPointer.data(), &LockedPointerInterface::lockedChanged); QVERIFY(lockedChangedSpy.isValid()); m_seatInterface->setFocusedPointerSurface(serverSurface); QSignalSpy pointerMotionSpy(m_pointer, &Pointer::motion); QVERIFY(pointerMotionSpy.isValid()); m_seatInterface->setPointerPos(QPoint(0, 1)); QVERIFY(pointerMotionSpy.wait()); serverLockedPointer->setLocked(true); QCOMPARE(serverLockedPointer->isLocked(), true); m_seatInterface->setPointerPos(QPoint(1, 1)); QCOMPARE(lockedChangedSpy.count(), 1); QCOMPARE(pointerMotionSpy.count(), 1); QVERIFY(lockedSpy.isEmpty()); QVERIFY(lockedSpy.wait()); QVERIFY(unlockedSpy.isEmpty()); // and unlock again serverLockedPointer->setLocked(false); QCOMPARE(serverLockedPointer->isLocked(), false); QCOMPARE(lockedChangedSpy.count(), 2); QTEST(!serverSurface->lockedPointer().isNull(), "hasConstraintAfterUnlock"); QTEST(pointerConstraintsChangedSpy.count(), "pointerChangedCount"); QVERIFY(unlockedSpy.wait()); QCOMPARE(unlockedSpy.count(), 1); QCOMPARE(lockedSpy.count(), 1); // now motion should work again m_seatInterface->setPointerPos(QPoint(0, 1)); QVERIFY(pointerMotionSpy.wait()); QCOMPARE(pointerMotionSpy.count(), 2); lockedPointer.reset(); QVERIFY(destroyedSpy.wait()); QCOMPARE(pointerConstraintsChangedSpy.count(), 2); } void TestPointerConstraints::testConfinePointer_data() { QTest::addColumn("clientLifeTime"); QTest::addColumn("serverLifeTime"); QTest::addColumn("hasConstraintAfterUnlock"); QTest::addColumn("pointerChangedCount"); QTest::newRow("persistent") << PointerConstraints::LifeTime::Persistent << ConfinedPointerInterface::LifeTime::Persistent << true << 1; QTest::newRow("oneshot") << PointerConstraints::LifeTime::OneShot << ConfinedPointerInterface::LifeTime::OneShot << false << 2; } void TestPointerConstraints::testConfinePointer() { // this test verifies the basic interaction for confined pointer // first create a surface QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(surface->isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); QVERIFY(serverSurface->lockedPointer().isNull()); QVERIFY(serverSurface->confinedPointer().isNull()); // now create the confined pointer QSignalSpy pointerConstraintsChangedSpy(serverSurface, &SurfaceInterface::pointerConstraintsChanged); QVERIFY(pointerConstraintsChangedSpy.isValid()); QFETCH(PointerConstraints::LifeTime, clientLifeTime); QScopedPointer confinedPointer(m_pointerConstraints->confinePointer(surface.data(), m_pointer, nullptr, clientLifeTime)); QSignalSpy confinedSpy(confinedPointer.data(), &ConfinedPointer::confined); QVERIFY(confinedSpy.isValid()); QSignalSpy unconfinedSpy(confinedPointer.data(), &ConfinedPointer::unconfined); QVERIFY(unconfinedSpy.isValid()); QVERIFY(confinedPointer->isValid()); QVERIFY(pointerConstraintsChangedSpy.wait()); auto serverConfinedPointer = serverSurface->confinedPointer(); QVERIFY(serverConfinedPointer); QVERIFY(serverSurface->lockedPointer().isNull()); QCOMPARE(serverConfinedPointer->isConfined(), false); QCOMPARE(serverConfinedPointer->region(), QRegion()); QFETCH(ConfinedPointerInterface::LifeTime, serverLifeTime); QCOMPARE(serverConfinedPointer->lifeTime(), serverLifeTime); // setting to unconfined now should not trigger an unconfined spy serverConfinedPointer->setConfined(false); - QVERIFY(!unconfinedSpy.wait()); + QVERIFY(!unconfinedSpy.wait(500)); // try setting a region QSignalSpy destroyedSpy(serverConfinedPointer.data(), &QObject::destroyed); QVERIFY(destroyedSpy.isValid()); QSignalSpy regionChangedSpy(serverConfinedPointer.data(), &ConfinedPointerInterface::regionChanged); QVERIFY(regionChangedSpy.isValid()); confinedPointer->setRegion(m_compositor->createRegion(QRegion(0, 5, 10, 20), m_compositor)); // it's double buffered - QVERIFY(!regionChangedSpy.wait()); + QVERIFY(!regionChangedSpy.wait(500)); surface->commit(Surface::CommitFlag::None); QVERIFY(regionChangedSpy.wait()); QCOMPARE(serverConfinedPointer->region(), QRegion(0, 5, 10, 20)); // and unset region again confinedPointer->setRegion(nullptr); surface->commit(Surface::CommitFlag::None); QVERIFY(regionChangedSpy.wait()); QCOMPARE(serverConfinedPointer->region(), QRegion()); // let's confine the surface QSignalSpy confinedChangedSpy(serverConfinedPointer.data(), &ConfinedPointerInterface::confinedChanged); QVERIFY(confinedChangedSpy.isValid()); m_seatInterface->setFocusedPointerSurface(serverSurface); serverConfinedPointer->setConfined(true); QCOMPARE(serverConfinedPointer->isConfined(), true); QCOMPARE(confinedChangedSpy.count(), 1); QVERIFY(confinedSpy.isEmpty()); QVERIFY(confinedSpy.wait()); QVERIFY(unconfinedSpy.isEmpty()); // and unconfine again serverConfinedPointer->setConfined(false); QCOMPARE(serverConfinedPointer->isConfined(), false); QCOMPARE(confinedChangedSpy.count(), 2); QTEST(!serverSurface->confinedPointer().isNull(), "hasConstraintAfterUnlock"); QTEST(pointerConstraintsChangedSpy.count(), "pointerChangedCount"); QVERIFY(unconfinedSpy.wait()); QCOMPARE(unconfinedSpy.count(), 1); QCOMPARE(confinedSpy.count(), 1); confinedPointer.reset(); QVERIFY(destroyedSpy.wait()); QCOMPARE(pointerConstraintsChangedSpy.count(), 2); } enum class Constraint { Lock, Confine }; Q_DECLARE_METATYPE(Constraint) void TestPointerConstraints::testAlreadyConstrained_data() { QTest::addColumn("firstConstraint"); QTest::addColumn("secondConstraint"); QTest::newRow("confine-confine") << Constraint::Confine << Constraint::Confine; QTest::newRow("lock-confine") << Constraint::Lock << Constraint::Confine; QTest::newRow("confine-lock") << Constraint::Confine << Constraint::Lock; QTest::newRow("lock-lock") << Constraint::Lock << Constraint::Lock; } void TestPointerConstraints::testAlreadyConstrained() { // this test verifies that creating a pointer constraint for an already constrained surface triggers an error // first create a surface QScopedPointer surface(m_compositor->createSurface()); QVERIFY(surface->isValid()); QFETCH(Constraint, firstConstraint); QScopedPointer confinedPointer; QScopedPointer lockedPointer; switch (firstConstraint) { case Constraint::Lock: lockedPointer.reset(m_pointerConstraints->lockPointer(surface.data(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot)); break; case Constraint::Confine: confinedPointer.reset(m_pointerConstraints->confinePointer(surface.data(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot)); break; default: Q_UNREACHABLE(); } QVERIFY(confinedPointer || lockedPointer); QSignalSpy errorSpy(m_connection, &ConnectionThread::errorOccurred); QVERIFY(errorSpy.isValid()); QFETCH(Constraint, secondConstraint); QScopedPointer confinedPointer2; QScopedPointer lockedPointer2; switch (secondConstraint) { case Constraint::Lock: lockedPointer2.reset(m_pointerConstraints->lockPointer(surface.data(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot)); break; case Constraint::Confine: confinedPointer2.reset(m_pointerConstraints->confinePointer(surface.data(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot)); break; default: Q_UNREACHABLE(); } QVERIFY(errorSpy.wait()); QVERIFY(m_connection->hasError()); if (confinedPointer2) { confinedPointer2->destroy(); } if (lockedPointer2) { lockedPointer2->destroy(); } if (confinedPointer) { confinedPointer->destroy(); } if (lockedPointer) { lockedPointer->destroy(); } surface->destroy(); m_compositor->destroy(); m_pointerConstraints->destroy(); m_pointer->destroy(); m_seat->destroy(); m_queue->destroy(); } QTEST_GUILESS_MAIN(TestPointerConstraints) #include "test_pointer_constraints.moc" diff --git a/autotests/client/test_selection.cpp b/autotests/client/test_selection.cpp index 3acc526..33cf192 100644 --- a/autotests/client/test_selection.cpp +++ b/autotests/client/test_selection.cpp @@ -1,272 +1,272 @@ /******************************************************************** Copyright 2016 Martin Gräßlin 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 . *********************************************************************/ // Qt #include // client #include "../../src/client/connection_thread.h" #include "../../src/client/compositor.h" #include "../../src/client/datadevice.h" #include "../../src/client/datadevicemanager.h" #include "../../src/client/datasource.h" #include "../../src/client/event_queue.h" #include "../../src/client/keyboard.h" #include "../../src/client/registry.h" #include "../../src/client/seat.h" #include "../../src/client/surface.h" // server #include "../../src/server/compositor_interface.h" #include "../../src/server/datadevicemanager_interface.h" #include "../../src/server/display.h" #include "../../src/server/seat_interface.h" using namespace KWayland::Client; using namespace KWayland::Server; class SelectionTest : public QObject { Q_OBJECT private Q_SLOTS: void init(); void cleanup(); void testClearOnEnter(); private: Display *m_display = nullptr; CompositorInterface *m_compositorInterface = nullptr; SeatInterface *m_seatInterface = nullptr; DataDeviceManagerInterface *m_ddmInterface = nullptr; struct Connection { ConnectionThread *connection = nullptr; QThread *thread = nullptr; EventQueue *queue = nullptr; Compositor *compositor = nullptr; Seat *seat = nullptr; DataDeviceManager *ddm = nullptr; Keyboard *keyboard = nullptr; DataDevice *dataDevice = nullptr; }; bool setupConnection(Connection *c); void cleanupConnection(Connection *c); Connection m_client1; Connection m_client2; }; static const QString s_socketName = QStringLiteral("kwayland-test-selection-0"); void SelectionTest::init() { delete m_display; m_display = new Display(this); m_display->setSocketName(s_socketName); m_display->start(); QVERIFY(m_display->isRunning()); m_display->createShm(); m_compositorInterface = m_display->createCompositor(m_display); m_compositorInterface->create(); m_seatInterface = m_display->createSeat(m_display); m_seatInterface->setHasKeyboard(true); m_seatInterface->create(); m_ddmInterface = m_display->createDataDeviceManager(m_display); m_ddmInterface->create(); // setup connection setupConnection(&m_client1); setupConnection(&m_client2); } bool SelectionTest::setupConnection(Connection* c) { c->connection = new ConnectionThread; QSignalSpy connectedSpy(c->connection, &ConnectionThread::connected); if (!connectedSpy.isValid()) { return false; } c->connection->setSocketName(s_socketName); c->thread = new QThread(this); c->connection->moveToThread(c->thread); c->thread->start(); c->connection->initConnection(); - if (!connectedSpy.wait()) { + if (!connectedSpy.wait(500)) { return false; } c->queue = new EventQueue(this); c->queue->setup(c->connection); Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &Registry::interfacesAnnounced); if (!interfacesAnnouncedSpy.isValid()) { return false; } registry.setEventQueue(c->queue); registry.create(c->connection); if (!registry.isValid()) { return false; } registry.setup(); - if (!interfacesAnnouncedSpy.wait()) { + if (!interfacesAnnouncedSpy.wait(500)) { return false; } c->compositor = registry.createCompositor(registry.interface(Registry::Interface::Compositor).name, registry.interface(Registry::Interface::Compositor).version, this); if (!c->compositor->isValid()) { return false; } c->ddm = registry.createDataDeviceManager(registry.interface(Registry::Interface::DataDeviceManager).name, registry.interface(Registry::Interface::DataDeviceManager).version, this); if (!c->ddm->isValid()) { return false; } c->seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name, registry.interface(Registry::Interface::Seat).version, this); if (!c->seat->isValid()) { return false; } QSignalSpy keyboardSpy(c->seat, &Seat::hasKeyboardChanged); if (!keyboardSpy.isValid()) { return false; } - if (!keyboardSpy.wait()) { + if (!keyboardSpy.wait(500)) { return false; } if (!c->seat->hasKeyboard()) { return false; } c->keyboard = c->seat->createKeyboard(c->seat); if (!c->keyboard->isValid()) { return false; } c->dataDevice = c->ddm->getDataDevice(c->seat, this); if (!c->dataDevice->isValid()) { return false; } return true; } void SelectionTest::cleanup() { cleanupConnection(&m_client1); cleanupConnection(&m_client2); #define CLEANUP(variable) \ delete variable; \ variable = nullptr; CLEANUP(m_ddmInterface) CLEANUP(m_seatInterface) CLEANUP(m_compositorInterface) CLEANUP(m_display) #undef CLEANUP } void SelectionTest::cleanupConnection(Connection *c) { delete c->dataDevice; c->dataDevice = nullptr; delete c->keyboard; c->keyboard = nullptr; delete c->ddm; c->ddm = nullptr; delete c->seat; c->seat = nullptr; delete c->compositor; c->compositor = nullptr; delete c->queue; c->queue = nullptr; if (c->connection) { c->connection->deleteLater(); c->connection = nullptr; } if (c->thread) { c->thread->quit(); c->thread->wait(); delete c->thread; c->thread = nullptr; } } void SelectionTest::testClearOnEnter() { // this test verifies that the selection is cleared prior to keyboard enter if there is no current selection QSignalSpy selectionClearedClient1Spy(m_client1.dataDevice, &DataDevice::selectionCleared); QVERIFY(selectionClearedClient1Spy.isValid()); QSignalSpy keyboardEnteredClient1Spy(m_client1.keyboard, &Keyboard::entered); QVERIFY(keyboardEnteredClient1Spy.isValid()); // now create a Surface QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer s1(m_client1.compositor->createSurface()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface1 = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface1); // pass this surface keyboard focus m_seatInterface->setFocusedKeyboardSurface(serverSurface1); // should get a clear QVERIFY(selectionClearedClient1Spy.wait()); // let's set a selection QScopedPointer dataSource(m_client1.ddm->createDataSource()); dataSource->offer(QStringLiteral("text/plain")); m_client1.dataDevice->setSelection(keyboardEnteredClient1Spy.first().first().value(), dataSource.data()); // now let's bring in client 2 QSignalSpy selectionOfferedClient2Spy(m_client2.dataDevice, &DataDevice::selectionOffered); QVERIFY(selectionOfferedClient2Spy.isValid()); QSignalSpy selectionClearedClient2Spy(m_client2.dataDevice, &DataDevice::selectionCleared); QVERIFY(selectionClearedClient2Spy.isValid()); QSignalSpy keyboardEnteredClient2Spy(m_client2.keyboard, &Keyboard::entered); QVERIFY(keyboardEnteredClient2Spy.isValid()); QScopedPointer s2(m_client2.compositor->createSurface()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface2 = surfaceCreatedSpy.last().first().value(); QVERIFY(serverSurface2); // entering that surface should give a selection offer m_seatInterface->setFocusedKeyboardSurface(serverSurface2); QVERIFY(selectionOfferedClient2Spy.wait()); QVERIFY(selectionClearedClient2Spy.isEmpty()); // set a data source but without offers QScopedPointer dataSource2(m_client2.ddm->createDataSource()); m_client2.dataDevice->setSelection(keyboardEnteredClient2Spy.first().first().value(), dataSource2.data()); QVERIFY(selectionOfferedClient2Spy.wait()); // and clear m_client2.dataDevice->clearSelection(keyboardEnteredClient2Spy.first().first().value()); QVERIFY(selectionClearedClient2Spy.wait()); // now pass focus to first surface m_seatInterface->setFocusedKeyboardSurface(serverSurface1); // we should get a clear QVERIFY(selectionClearedClient1Spy.wait()); } QTEST_GUILESS_MAIN(SelectionTest) #include "test_selection.moc" diff --git a/autotests/client/test_text_input.cpp b/autotests/client/test_text_input.cpp index baf5643..7187dc5 100644 --- a/autotests/client/test_text_input.cpp +++ b/autotests/client/test_text_input.cpp @@ -1,936 +1,936 @@ /******************************************************************** Copyright 2016 Martin Gräßlin 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 . *********************************************************************/ // Qt #include // client #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/registry.h" #include "../../src/client/seat.h" #include "../../src/client/surface.h" #include "../../src/client/textinput.h" // server #include "../../src/server/compositor_interface.h" #include "../../src/server/display.h" #include "../../src/server/seat_interface.h" #include "../../src/server/textinput_interface.h" using namespace KWayland::Client; using namespace KWayland::Server; class TextInputTest : public QObject { Q_OBJECT private Q_SLOTS: void init(); void cleanup(); void testEnterLeave_data(); void testEnterLeave(); void testShowHidePanel_data(); void testShowHidePanel(); void testCursorRectangle_data(); void testCursorRectangle(); void testPreferredLanguage_data(); void testPreferredLanguage(); void testReset_data(); void testReset(); void testSurroundingText_data(); void testSurroundingText(); void testContentHints_data(); void testContentHints(); void testContentPurpose_data(); void testContentPurpose(); void testTextDirection_data(); void testTextDirection(); void testLanguage_data(); void testLanguage(); void testKeyEvent_data(); void testKeyEvent(); void testPreEdit_data(); void testPreEdit(); void testCommit_data(); void testCommit(); private: SurfaceInterface *waitForSurface(); TextInput *createTextInput(TextInputInterfaceVersion version); Display *m_display = nullptr; SeatInterface *m_seatInterface = nullptr; CompositorInterface *m_compositorInterface = nullptr; TextInputManagerInterface *m_textInputManagerV0Interface = nullptr; TextInputManagerInterface *m_textInputManagerV2Interface = nullptr; ConnectionThread *m_connection = nullptr; QThread *m_thread = nullptr; EventQueue *m_queue = nullptr; Seat *m_seat = nullptr; Keyboard *m_keyboard = nullptr; Compositor *m_compositor = nullptr; TextInputManager *m_textInputManagerV0 = nullptr; TextInputManager *m_textInputManagerV2 = nullptr; }; static const QString s_socketName = QStringLiteral("kwayland-test-text-input-0"); void TextInputTest::init() { delete m_display; m_display = new Display(this); m_display->setSocketName(s_socketName); m_display->start(); QVERIFY(m_display->isRunning()); m_display->createShm(); m_seatInterface = m_display->createSeat(); m_seatInterface->setHasKeyboard(true); m_seatInterface->setHasTouch(true); m_seatInterface->create(); m_compositorInterface = m_display->createCompositor(); m_compositorInterface->create(); m_textInputManagerV0Interface = m_display->createTextInputManager(TextInputInterfaceVersion::UnstableV0); m_textInputManagerV0Interface->create(); m_textInputManagerV2Interface = m_display->createTextInputManager(TextInputInterfaceVersion::UnstableV2); m_textInputManagerV2Interface->create(); // setup connection m_connection = new KWayland::Client::ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); 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 EventQueue(this); m_queue->setup(m_connection); Registry registry; QSignalSpy interfacesAnnouncedSpy(®istry, &Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); registry.setEventQueue(m_queue); registry.create(m_connection); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(interfacesAnnouncedSpy.wait()); m_seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name, registry.interface(Registry::Interface::Seat).version, this); QVERIFY(m_seat->isValid()); QSignalSpy hasKeyboardSpy(m_seat, &Seat::hasKeyboardChanged); QVERIFY(hasKeyboardSpy.isValid()); QVERIFY(hasKeyboardSpy.wait()); m_keyboard = m_seat->createKeyboard(this); QVERIFY(m_keyboard->isValid()); m_compositor = registry.createCompositor(registry.interface(Registry::Interface::Compositor).name, registry.interface(Registry::Interface::Compositor).version, this); QVERIFY(m_compositor->isValid()); m_textInputManagerV0 = registry.createTextInputManager(registry.interface(Registry::Interface::TextInputManagerUnstableV0).name, registry.interface(Registry::Interface::TextInputManagerUnstableV0).version, this); QVERIFY(m_textInputManagerV0->isValid()); m_textInputManagerV2 = registry.createTextInputManager(registry.interface(Registry::Interface::TextInputManagerUnstableV2).name, registry.interface(Registry::Interface::TextInputManagerUnstableV2).version, this); QVERIFY(m_textInputManagerV2->isValid()); } void TextInputTest::cleanup() { #define CLEANUP(variable) \ if (variable) { \ delete variable; \ variable = nullptr; \ } CLEANUP(m_textInputManagerV0) CLEANUP(m_textInputManagerV2) CLEANUP(m_keyboard) CLEANUP(m_seat) CLEANUP(m_compositor) CLEANUP(m_queue) if (m_connection) { m_connection->deleteLater(); m_connection = nullptr; } if (m_thread) { m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; } CLEANUP(m_textInputManagerV0Interface) CLEANUP(m_textInputManagerV2Interface) CLEANUP(m_compositorInterface) CLEANUP(m_seatInterface) CLEANUP(m_display) #undef CLEANUP } SurfaceInterface *TextInputTest::waitForSurface() { QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); if (!surfaceCreatedSpy.isValid()) { return nullptr; } - if (!surfaceCreatedSpy.wait()) { + if (!surfaceCreatedSpy.wait(500)) { return nullptr; } if (surfaceCreatedSpy.count() != 1) { return nullptr; } return surfaceCreatedSpy.first().first().value(); } TextInput *TextInputTest::createTextInput(TextInputInterfaceVersion version) { switch (version) { case TextInputInterfaceVersion::UnstableV0: return m_textInputManagerV0->createTextInput(m_seat); case TextInputInterfaceVersion::UnstableV2: return m_textInputManagerV2->createTextInput(m_seat); default: Q_UNREACHABLE(); return nullptr; } } void TextInputTest::testEnterLeave_data() { QTest::addColumn("version"); QTest::addColumn("updatesDirectly"); QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0 << false; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2 << true; } void TextInputTest::testEnterLeave() { // this test verifies that enter leave are sent correctly QScopedPointer surface(m_compositor->createSurface()); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QVERIFY(!textInput.isNull()); QSignalSpy enteredSpy(textInput.data(), &TextInput::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(textInput.data(), &TextInput::left); QVERIFY(leftSpy.isValid()); QSignalSpy textInputChangedSpy(m_seatInterface, &SeatInterface::focusedTextInputChanged); QVERIFY(textInputChangedSpy.isValid()); // now let's try to enter it QVERIFY(!m_seatInterface->focusedTextInput()); QVERIFY(!m_seatInterface->focusedTextInputSurface()); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QCOMPARE(m_seatInterface->focusedTextInputSurface(), serverSurface); // text input not yet set for the surface QFETCH(bool, updatesDirectly); QCOMPARE(bool(m_seatInterface->focusedTextInput()), updatesDirectly); QCOMPARE(textInputChangedSpy.isEmpty(), !updatesDirectly); textInput->enable(surface.data()); // this should trigger on server side if (!updatesDirectly) { QVERIFY(textInputChangedSpy.wait()); } QCOMPARE(textInputChangedSpy.count(), 1); auto serverTextInput = m_seatInterface->focusedTextInput(); QVERIFY(serverTextInput); QCOMPARE(serverTextInput->interfaceVersion(), version); QSignalSpy enabledChangedSpy(serverTextInput, &TextInputInterface::enabledChanged); QVERIFY(enabledChangedSpy.isValid()); if (updatesDirectly) { QVERIFY(enabledChangedSpy.wait()); enabledChangedSpy.clear(); } QCOMPARE(serverTextInput->surface().data(), serverSurface); QVERIFY(serverTextInput->isEnabled()); // and trigger an enter if (enteredSpy.isEmpty()) { QVERIFY(enteredSpy.wait()); } QCOMPARE(enteredSpy.count(), 1); QCOMPARE(textInput->enteredSurface(), surface.data()); // now trigger a leave m_seatInterface->setFocusedKeyboardSurface(nullptr); QCOMPARE(textInputChangedSpy.count(), 2); QVERIFY(!m_seatInterface->focusedTextInput()); QVERIFY(leftSpy.wait()); QVERIFY(!textInput->enteredSurface()); QVERIFY(serverTextInput->isEnabled()); // if we enter again we should directly get the text input as it's still activated m_seatInterface->setFocusedKeyboardSurface(serverSurface); QCOMPARE(textInputChangedSpy.count(), 3); QVERIFY(m_seatInterface->focusedTextInput()); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 2); QCOMPARE(textInput->enteredSurface(), surface.data()); QVERIFY(serverTextInput->isEnabled()); // let's deactivate on client side textInput->disable(surface.data()); QVERIFY(enabledChangedSpy.wait()); QCOMPARE(enabledChangedSpy.count(), 1); QVERIFY(!serverTextInput->isEnabled()); // does not trigger a leave QCOMPARE(textInputChangedSpy.count(), 3); // should still be the same text input QCOMPARE(m_seatInterface->focusedTextInput(), serverTextInput); //reset textInput->enable(surface.data()); QVERIFY(enabledChangedSpy.wait()); //trigger an enter again and leave, but this //time we try sending an event after the surface is unbound //but not yet destroyed. It should work without errors QCOMPARE(textInput->enteredSurface(), surface.data()); connect(serverSurface, &Resource::unbound, [=]() { m_seatInterface->setFocusedKeyboardSurface(nullptr); }); //delete the client and wait for the server to catch up QSignalSpy unboundSpy(serverSurface, &QObject::destroyed); surface.reset(); QVERIFY(unboundSpy.wait()); QVERIFY(leftSpy.wait()); QVERIFY(!textInput->enteredSurface()); } void TextInputTest::testShowHidePanel_data() { QTest::addColumn("version"); QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; } void TextInputTest::testShowHidePanel() { // this test verifies that the requests for show/hide panel work // and that status is properly sent to the client QScopedPointer surface(m_compositor->createSurface()); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); QVERIFY(!textInput.isNull()); textInput->enable(surface.data()); m_connection->flush(); m_display->dispatchEvents(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); auto ti = m_seatInterface->focusedTextInput(); QVERIFY(ti); QSignalSpy showPanelRequestedSpy(ti, &TextInputInterface::requestShowInputPanel); QVERIFY(showPanelRequestedSpy.isValid()); QSignalSpy hidePanelRequestedSpy(ti, &TextInputInterface::requestHideInputPanel); QVERIFY(hidePanelRequestedSpy.isValid()); QSignalSpy inputPanelStateChangedSpy(textInput.data(), &TextInput::inputPanelStateChanged); QVERIFY(inputPanelStateChangedSpy.isValid()); QCOMPARE(textInput->isInputPanelVisible(), false); textInput->showInputPanel(); QVERIFY(showPanelRequestedSpy.wait()); ti->setInputPanelState(true, QRect(0, 0, 0, 0)); QVERIFY(inputPanelStateChangedSpy.wait()); QCOMPARE(textInput->isInputPanelVisible(), true); textInput->hideInputPanel(); QVERIFY(hidePanelRequestedSpy.wait()); ti->setInputPanelState(false, QRect(0, 0, 0, 0)); QVERIFY(inputPanelStateChangedSpy.wait()); QCOMPARE(textInput->isInputPanelVisible(), false); } void TextInputTest::testCursorRectangle_data() { QTest::addColumn("version"); QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; } void TextInputTest::testCursorRectangle() { // this test verifies that passing the cursor rectangle from client to server works // and that setting visibility state from server to client works QScopedPointer surface(m_compositor->createSurface()); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); QVERIFY(!textInput.isNull()); textInput->enable(surface.data()); m_connection->flush(); m_display->dispatchEvents(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); auto ti = m_seatInterface->focusedTextInput(); QVERIFY(ti); QCOMPARE(ti->cursorRectangle(), QRect()); QSignalSpy cursorRectangleChangedSpy(ti, &TextInputInterface::cursorRectangleChanged); QVERIFY(cursorRectangleChangedSpy.isValid()); textInput->setCursorRectangle(QRect(10, 20, 30, 40)); QVERIFY(cursorRectangleChangedSpy.wait()); QCOMPARE(ti->cursorRectangle(), QRect(10, 20, 30, 40)); } void TextInputTest::testPreferredLanguage_data() { QTest::addColumn("version"); QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; } void TextInputTest::testPreferredLanguage() { // this test verifies that passing the preferred language from client to server works QScopedPointer surface(m_compositor->createSurface()); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); QVERIFY(!textInput.isNull()); textInput->enable(surface.data()); m_connection->flush(); m_display->dispatchEvents(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); auto ti = m_seatInterface->focusedTextInput(); QVERIFY(ti); QVERIFY(ti->preferredLanguage().isEmpty()); QSignalSpy preferredLanguageChangedSpy(ti, &TextInputInterface::preferredLanguageChanged); QVERIFY(preferredLanguageChangedSpy.isValid()); textInput->setPreferredLanguage(QStringLiteral("foo")); QVERIFY(preferredLanguageChangedSpy.wait()); QCOMPARE(ti->preferredLanguage(), QStringLiteral("foo").toUtf8()); } void TextInputTest::testReset_data() { QTest::addColumn("version"); QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; } void TextInputTest::testReset() { // this test verifies that the reset request is properly passed from client to server QScopedPointer surface(m_compositor->createSurface()); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); QVERIFY(!textInput.isNull()); textInput->enable(surface.data()); m_connection->flush(); m_display->dispatchEvents(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); auto ti = m_seatInterface->focusedTextInput(); QVERIFY(ti); QSignalSpy resetRequestedSpy(ti, &TextInputInterface::requestReset); QVERIFY(resetRequestedSpy.isValid()); textInput->reset(); QVERIFY(resetRequestedSpy.wait()); } void TextInputTest::testSurroundingText_data() { QTest::addColumn("version"); QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; } void TextInputTest::testSurroundingText() { // this test verifies that surrounding text is properly passed around QScopedPointer surface(m_compositor->createSurface()); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); QVERIFY(!textInput.isNull()); textInput->enable(surface.data()); m_connection->flush(); m_display->dispatchEvents(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); auto ti = m_seatInterface->focusedTextInput(); QVERIFY(ti); QVERIFY(ti->surroundingText().isEmpty()); QCOMPARE(ti->surroundingTextCursorPosition(), 0); QCOMPARE(ti->surroundingTextSelectionAnchor(), 0); QSignalSpy surroundingTextChangedSpy(ti, &TextInputInterface::surroundingTextChanged); QVERIFY(surroundingTextChangedSpy.isValid()); textInput->setSurroundingText(QStringLiteral("100 €, 100 $"), 5, 6); QVERIFY(surroundingTextChangedSpy.wait()); QCOMPARE(ti->surroundingText(), QStringLiteral("100 €, 100 $").toUtf8()); QCOMPARE(ti->surroundingTextCursorPosition(), QStringLiteral("100 €, 100 $").toUtf8().indexOf(',')); QCOMPARE(ti->surroundingTextSelectionAnchor(), QStringLiteral("100 €, 100 $").toUtf8().indexOf(' ', ti->surroundingTextCursorPosition())); } void TextInputTest::testContentHints_data() { QTest::addColumn("version"); QTest::addColumn("clientHints"); QTest::addColumn("serverHints"); QTest::newRow("completion/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::AutoCompletion) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCompletion); QTest::newRow("Correction/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::AutoCorrection) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCorrection); QTest::newRow("Capitalization/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::AutoCapitalization) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCapitalization); QTest::newRow("Lowercase/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::LowerCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::LowerCase); QTest::newRow("Uppercase/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::UpperCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::UpperCase); QTest::newRow("Titlecase/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::TitleCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::TitleCase); QTest::newRow("HiddenText/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::HiddenText) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::HiddenText); QTest::newRow("SensitiveData/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::SensitiveData) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::SensitiveData); QTest::newRow("Latin/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::Latin) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::Latin); QTest::newRow("Multiline/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentHints(TextInput::ContentHint::MultiLine) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::MultiLine); QTest::newRow("autos/v0") << TextInputInterfaceVersion::UnstableV0 << (TextInput::ContentHint::AutoCompletion | TextInput::ContentHint::AutoCorrection | TextInput::ContentHint::AutoCapitalization) << (TextInputInterface::ContentHint::AutoCompletion | TextInputInterface::ContentHint::AutoCorrection | TextInputInterface::ContentHint::AutoCapitalization); // all has combinations which don't make sense - what's both lowercase and uppercase? QTest::newRow("all/v0") << TextInputInterfaceVersion::UnstableV0 << (TextInput::ContentHint::AutoCompletion | TextInput::ContentHint::AutoCorrection | TextInput::ContentHint::AutoCapitalization | TextInput::ContentHint::LowerCase | TextInput::ContentHint::UpperCase | TextInput::ContentHint::TitleCase | TextInput::ContentHint::HiddenText | TextInput::ContentHint::SensitiveData | TextInput::ContentHint::Latin | TextInput::ContentHint::MultiLine) << (TextInputInterface::ContentHint::AutoCompletion | TextInputInterface::ContentHint::AutoCorrection | TextInputInterface::ContentHint::AutoCapitalization | TextInputInterface::ContentHint::LowerCase | TextInputInterface::ContentHint::UpperCase | TextInputInterface::ContentHint::TitleCase | TextInputInterface::ContentHint::HiddenText | TextInputInterface::ContentHint::SensitiveData | TextInputInterface::ContentHint::Latin | TextInputInterface::ContentHint::MultiLine); // same for version 2 QTest::newRow("completion/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::AutoCompletion) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCompletion); QTest::newRow("Correction/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::AutoCorrection) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCorrection); QTest::newRow("Capitalization/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::AutoCapitalization) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::AutoCapitalization); QTest::newRow("Lowercase/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::LowerCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::LowerCase); QTest::newRow("Uppercase/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::UpperCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::UpperCase); QTest::newRow("Titlecase/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::TitleCase) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::TitleCase); QTest::newRow("HiddenText/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::HiddenText) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::HiddenText); QTest::newRow("SensitiveData/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::SensitiveData) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::SensitiveData); QTest::newRow("Latin/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::Latin) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::Latin); QTest::newRow("Multiline/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentHints(TextInput::ContentHint::MultiLine) << TextInputInterface::ContentHints(TextInputInterface::ContentHint::MultiLine); QTest::newRow("autos/v2") << TextInputInterfaceVersion::UnstableV2 << (TextInput::ContentHint::AutoCompletion | TextInput::ContentHint::AutoCorrection | TextInput::ContentHint::AutoCapitalization) << (TextInputInterface::ContentHint::AutoCompletion | TextInputInterface::ContentHint::AutoCorrection | TextInputInterface::ContentHint::AutoCapitalization); // all has combinations which don't make sense - what's both lowercase and uppercase? QTest::newRow("all/v2") << TextInputInterfaceVersion::UnstableV2 << (TextInput::ContentHint::AutoCompletion | TextInput::ContentHint::AutoCorrection | TextInput::ContentHint::AutoCapitalization | TextInput::ContentHint::LowerCase | TextInput::ContentHint::UpperCase | TextInput::ContentHint::TitleCase | TextInput::ContentHint::HiddenText | TextInput::ContentHint::SensitiveData | TextInput::ContentHint::Latin | TextInput::ContentHint::MultiLine) << (TextInputInterface::ContentHint::AutoCompletion | TextInputInterface::ContentHint::AutoCorrection | TextInputInterface::ContentHint::AutoCapitalization | TextInputInterface::ContentHint::LowerCase | TextInputInterface::ContentHint::UpperCase | TextInputInterface::ContentHint::TitleCase | TextInputInterface::ContentHint::HiddenText | TextInputInterface::ContentHint::SensitiveData | TextInputInterface::ContentHint::Latin | TextInputInterface::ContentHint::MultiLine); } void TextInputTest::testContentHints() { // this test verifies that content hints are properly passed from client to server QScopedPointer surface(m_compositor->createSurface()); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); QVERIFY(!textInput.isNull()); textInput->enable(surface.data()); m_connection->flush(); m_display->dispatchEvents(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); auto ti = m_seatInterface->focusedTextInput(); QVERIFY(ti); QCOMPARE(ti->contentHints(), TextInputInterface::ContentHints()); QSignalSpy contentTypeChangedSpy(ti, &TextInputInterface::contentTypeChanged); QVERIFY(contentTypeChangedSpy.isValid()); QFETCH(TextInput::ContentHints, clientHints); textInput->setContentType(clientHints, TextInput::ContentPurpose::Normal); QVERIFY(contentTypeChangedSpy.wait()); QTEST(ti->contentHints(), "serverHints"); // setting to same should not trigger an update textInput->setContentType(clientHints, TextInput::ContentPurpose::Normal); QVERIFY(!contentTypeChangedSpy.wait(100)); // unsetting should work textInput->setContentType(TextInput::ContentHints(), TextInput::ContentPurpose::Normal); QVERIFY(contentTypeChangedSpy.wait()); QCOMPARE(ti->contentHints(), TextInputInterface::ContentHints()); } void TextInputTest::testContentPurpose_data() { QTest::addColumn("version"); QTest::addColumn("clientPurpose"); QTest::addColumn("serverPurpose"); QTest::newRow("Alpha/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Alpha << TextInputInterface::ContentPurpose::Alpha; QTest::newRow("Digits/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Digits << TextInputInterface::ContentPurpose::Digits; QTest::newRow("Number/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Number << TextInputInterface::ContentPurpose::Number; QTest::newRow("Phone/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Phone << TextInputInterface::ContentPurpose::Phone; QTest::newRow("Url/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Url << TextInputInterface::ContentPurpose::Url; QTest::newRow("Email/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Email << TextInputInterface::ContentPurpose::Email; QTest::newRow("Name/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Name << TextInputInterface::ContentPurpose::Name; QTest::newRow("Password/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Password << TextInputInterface::ContentPurpose::Password; QTest::newRow("Date/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Date << TextInputInterface::ContentPurpose::Date; QTest::newRow("Time/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Time << TextInputInterface::ContentPurpose::Time; QTest::newRow("Datetime/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::DateTime << TextInputInterface::ContentPurpose::DateTime; QTest::newRow("Terminal/v0") << TextInputInterfaceVersion::UnstableV0 << TextInput::ContentPurpose::Terminal << TextInputInterface::ContentPurpose::Terminal; QTest::newRow("Alpha/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Alpha << TextInputInterface::ContentPurpose::Alpha; QTest::newRow("Digits/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Digits << TextInputInterface::ContentPurpose::Digits; QTest::newRow("Number/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Number << TextInputInterface::ContentPurpose::Number; QTest::newRow("Phone/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Phone << TextInputInterface::ContentPurpose::Phone; QTest::newRow("Url/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Url << TextInputInterface::ContentPurpose::Url; QTest::newRow("Email/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Email << TextInputInterface::ContentPurpose::Email; QTest::newRow("Name/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Name << TextInputInterface::ContentPurpose::Name; QTest::newRow("Password/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Password << TextInputInterface::ContentPurpose::Password; QTest::newRow("Date/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Date << TextInputInterface::ContentPurpose::Date; QTest::newRow("Time/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Time << TextInputInterface::ContentPurpose::Time; QTest::newRow("Datetime/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::DateTime << TextInputInterface::ContentPurpose::DateTime; QTest::newRow("Terminal/v2") << TextInputInterfaceVersion::UnstableV2 << TextInput::ContentPurpose::Terminal << TextInputInterface::ContentPurpose::Terminal; } void TextInputTest::testContentPurpose() { // this test verifies that content purpose are properly passed from client to server QScopedPointer surface(m_compositor->createSurface()); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); QVERIFY(!textInput.isNull()); textInput->enable(surface.data()); m_connection->flush(); m_display->dispatchEvents(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); auto ti = m_seatInterface->focusedTextInput(); QVERIFY(ti); QCOMPARE(ti->contentPurpose(), TextInputInterface::ContentPurpose::Normal); QSignalSpy contentTypeChangedSpy(ti, &TextInputInterface::contentTypeChanged); QVERIFY(contentTypeChangedSpy.isValid()); QFETCH(TextInput::ContentPurpose, clientPurpose); textInput->setContentType(TextInput::ContentHints(), clientPurpose); QVERIFY(contentTypeChangedSpy.wait()); QTEST(ti->contentPurpose(), "serverPurpose"); // setting to same should not trigger an update textInput->setContentType(TextInput::ContentHints(), clientPurpose); QVERIFY(!contentTypeChangedSpy.wait(100)); // unsetting should work textInput->setContentType(TextInput::ContentHints(), TextInput::ContentPurpose::Normal); QVERIFY(contentTypeChangedSpy.wait()); QCOMPARE(ti->contentPurpose(), TextInputInterface::ContentPurpose::Normal); } void TextInputTest::testTextDirection_data() { QTest::addColumn("version"); QTest::addColumn("textDirection"); QTest::newRow("ltr/v0") << TextInputInterfaceVersion::UnstableV0 << Qt::LeftToRight; QTest::newRow("rtl/v0") << TextInputInterfaceVersion::UnstableV0 << Qt::RightToLeft; QTest::newRow("ltr/v2") << TextInputInterfaceVersion::UnstableV2 << Qt::LeftToRight; QTest::newRow("rtl/v2") << TextInputInterfaceVersion::UnstableV2 << Qt::RightToLeft; } void TextInputTest::testTextDirection() { // this test verifies that the text direction is sent from server to client QScopedPointer surface(m_compositor->createSurface()); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); QVERIFY(!textInput.isNull()); // default should be auto QCOMPARE(textInput->textDirection(), Qt::LayoutDirectionAuto); textInput->enable(surface.data()); m_connection->flush(); m_display->dispatchEvents(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); auto ti = m_seatInterface->focusedTextInput(); QVERIFY(ti); // let's send the new text direction QSignalSpy textDirectionChangedSpy(textInput.data(), &TextInput::textDirectionChanged); QVERIFY(textDirectionChangedSpy.isValid()); QFETCH(Qt::LayoutDirection, textDirection); ti->setTextDirection(textDirection); QVERIFY(textDirectionChangedSpy.wait()); QCOMPARE(textInput->textDirection(), textDirection); // setting again should not change ti->setTextDirection(textDirection); QVERIFY(!textDirectionChangedSpy.wait(100)); // setting back to auto ti->setTextDirection(Qt::LayoutDirectionAuto); QVERIFY(textDirectionChangedSpy.wait()); QCOMPARE(textInput->textDirection(), Qt::LayoutDirectionAuto); } void TextInputTest::testLanguage_data() { QTest::addColumn("version"); QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; } void TextInputTest::testLanguage() { // this test verifies that language is sent from server to client QScopedPointer surface(m_compositor->createSurface()); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); QVERIFY(!textInput.isNull()); // default should be empty QVERIFY(textInput->language().isEmpty()); textInput->enable(surface.data()); m_connection->flush(); m_display->dispatchEvents(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); auto ti = m_seatInterface->focusedTextInput(); QVERIFY(ti); // let's send the new language QSignalSpy langugageChangedSpy(textInput.data(), &TextInput::languageChanged); QVERIFY(langugageChangedSpy.isValid()); ti->setLanguage(QByteArrayLiteral("foo")); QVERIFY(langugageChangedSpy.wait()); QCOMPARE(textInput->language(), QByteArrayLiteral("foo")); // setting to same should not trigger ti->setLanguage(QByteArrayLiteral("foo")); QVERIFY(!langugageChangedSpy.wait(100)); // but to something else should trigger again ti->setLanguage(QByteArrayLiteral("bar")); QVERIFY(langugageChangedSpy.wait()); QCOMPARE(textInput->language(), QByteArrayLiteral("bar")); } void TextInputTest::testKeyEvent_data() { QTest::addColumn("version"); QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; } void TextInputTest::testKeyEvent() { qRegisterMetaType(); qRegisterMetaType(); // this test verifies that key events are properly sent to the client QScopedPointer surface(m_compositor->createSurface()); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); QVERIFY(!textInput.isNull()); textInput->enable(surface.data()); m_connection->flush(); m_display->dispatchEvents(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); auto ti = m_seatInterface->focusedTextInput(); QVERIFY(ti); // TODO: test modifiers QSignalSpy keyEventSpy(textInput.data(), &TextInput::keyEvent); QVERIFY(keyEventSpy.isValid()); m_seatInterface->setTimestamp(100); ti->keysymPressed(2); QVERIFY(keyEventSpy.wait()); QCOMPARE(keyEventSpy.count(), 1); QCOMPARE(keyEventSpy.last().at(0).value(), 2u); QCOMPARE(keyEventSpy.last().at(1).value(), TextInput::KeyState::Pressed); QCOMPARE(keyEventSpy.last().at(2).value(), Qt::KeyboardModifiers()); QCOMPARE(keyEventSpy.last().at(3).value(), 100u); m_seatInterface->setTimestamp(101); ti->keysymReleased(2); QVERIFY(keyEventSpy.wait()); QCOMPARE(keyEventSpy.count(), 2); QCOMPARE(keyEventSpy.last().at(0).value(), 2u); QCOMPARE(keyEventSpy.last().at(1).value(), TextInput::KeyState::Released); QCOMPARE(keyEventSpy.last().at(2).value(), Qt::KeyboardModifiers()); QCOMPARE(keyEventSpy.last().at(3).value(), 101u); } void TextInputTest::testPreEdit_data() { QTest::addColumn("version"); QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; } void TextInputTest::testPreEdit() { // this test verifies that pre-edit is correctly passed to the client QScopedPointer surface(m_compositor->createSurface()); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); QVERIFY(!textInput.isNull()); // verify default values QVERIFY(textInput->composingText().isEmpty()); QVERIFY(textInput->composingFallbackText().isEmpty()); QCOMPARE(textInput->composingTextCursorPosition(), 0); textInput->enable(surface.data()); m_connection->flush(); m_display->dispatchEvents(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); auto ti = m_seatInterface->focusedTextInput(); QVERIFY(ti); // now let's pass through some pre-edit events QSignalSpy composingTextChangedSpy(textInput.data(), &TextInput::composingTextChanged); QVERIFY(composingTextChangedSpy.isValid()); ti->setPreEditCursor(1); ti->preEdit(QByteArrayLiteral("foo"), QByteArrayLiteral("bar")); QVERIFY(composingTextChangedSpy.wait()); QCOMPARE(composingTextChangedSpy.count(), 1); QCOMPARE(textInput->composingText(), QByteArrayLiteral("foo")); QCOMPARE(textInput->composingFallbackText(), QByteArrayLiteral("bar")); QCOMPARE(textInput->composingTextCursorPosition(), 1); // when no pre edit cursor is sent, it's at end of text ti->preEdit(QByteArrayLiteral("foobar"), QByteArray()); QVERIFY(composingTextChangedSpy.wait()); QCOMPARE(composingTextChangedSpy.count(), 2); QCOMPARE(textInput->composingText(), QByteArrayLiteral("foobar")); QCOMPARE(textInput->composingFallbackText(), QByteArray()); QCOMPARE(textInput->composingTextCursorPosition(), 6); } void TextInputTest::testCommit_data() { QTest::addColumn("version"); QTest::newRow("UnstableV0") << TextInputInterfaceVersion::UnstableV0; QTest::newRow("UnstableV2") << TextInputInterfaceVersion::UnstableV2; } void TextInputTest::testCommit() { // this test verifies that the commit is handled correctly by the client QScopedPointer surface(m_compositor->createSurface()); auto serverSurface = waitForSurface(); QVERIFY(serverSurface); QFETCH(TextInputInterfaceVersion, version); QScopedPointer textInput(createTextInput(version)); QVERIFY(!textInput.isNull()); // verify default values QCOMPARE(textInput->commitText(), QByteArray()); QCOMPARE(textInput->cursorPosition(), 0); QCOMPARE(textInput->anchorPosition(), 0); QCOMPARE(textInput->deleteSurroundingText().beforeLength, 0u); QCOMPARE(textInput->deleteSurroundingText().afterLength, 0u); textInput->enable(surface.data()); m_connection->flush(); m_display->dispatchEvents(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); auto ti = m_seatInterface->focusedTextInput(); QVERIFY(ti); // now let's commit QSignalSpy committedSpy(textInput.data(), &TextInput::committed); QVERIFY(committedSpy.isValid()); ti->setCursorPosition(3, 4); ti->deleteSurroundingText(2, 1); ti->commit(QByteArrayLiteral("foo")); QVERIFY(committedSpy.wait()); QCOMPARE(textInput->commitText(), QByteArrayLiteral("foo")); QCOMPARE(textInput->cursorPosition(), 3); QCOMPARE(textInput->anchorPosition(), 4); QCOMPARE(textInput->deleteSurroundingText().beforeLength, 2u); QCOMPARE(textInput->deleteSurroundingText().afterLength, 1u); } QTEST_GUILESS_MAIN(TextInputTest) #include "test_text_input.moc" diff --git a/autotests/client/test_wayland_seat.cpp b/autotests/client/test_wayland_seat.cpp index cd6e30b..05c7fbc 100644 --- a/autotests/client/test_wayland_seat.cpp +++ b/autotests/client/test_wayland_seat.cpp @@ -1,2312 +1,2312 @@ /******************************************************************** Copyright 2014 Martin Gräßlin 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 . *********************************************************************/ // Qt #include // KWin #include "../../src/client/compositor.h" #include "../../src/client/connection_thread.h" #include "../../src/client/datadevice.h" #include "../../src/client/datadevicemanager.h" #include "../../src/client/datasource.h" #include "../../src/client/event_queue.h" #include "../../src/client/keyboard.h" #include "../../src/client/pointer.h" #include "../../src/client/pointergestures.h" #include "../../src/client/surface.h" #include "../../src/client/registry.h" #include "../../src/client/relativepointer.h" #include "../../src/client/seat.h" #include "../../src/client/shm_pool.h" #include "../../src/client/subcompositor.h" #include "../../src/client/subsurface.h" #include "../../src/client/touch.h" #include "../../src/server/buffer_interface.h" #include "../../src/server/compositor_interface.h" #include "../../src/server/datadevicemanager_interface.h" #include "../../src/server/display.h" #include "../../src/server/keyboard_interface.h" #include "../../src/server/pointer_interface.h" #include "../../src/server/pointergestures_interface.h" #include "../../src/server/relativepointer_interface.h" #include "../../src/server/seat_interface.h" #include "../../src/server/subcompositor_interface.h" #include "../../src/server/surface_interface.h" // Wayland #include #include // System #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 testPointerTransformation_data(); void testPointerTransformation(); void testPointerButton_data(); void testPointerButton(); void testPointerSubSurfaceTree(); void testPointerSwipeGesture_data(); void testPointerSwipeGesture(); void testPointerPinchGesture_data(); void testPointerPinchGesture(); void testKeyboardSubSurfaceTreeFromPointer(); void testCursor(); void testCursorDamage(); void testKeyboard(); void testCast(); void testDestroy(); void testSelection(); void testSelectionNoDataSource(); void testDataDeviceForKeyboardSurface(); void testTouch(); void testDisconnect(); void testPointerEnterOnUnboundSurface(); // TODO: add test for keymap private: KWayland::Server::Display *m_display; KWayland::Server::CompositorInterface *m_compositorInterface; KWayland::Server::SeatInterface *m_seatInterface; KWayland::Server::SubCompositorInterface *m_subCompositorInterface; KWayland::Server::RelativePointerManagerInterface *m_relativePointerManagerInterface; KWayland::Server::PointerGesturesInterface *m_pointerGesturesInterface; 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_relativePointerManagerInterface(nullptr) , m_pointerGesturesInterface(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 KWayland::Server; delete m_display; m_display = new Display(this); m_display->setSocketName(s_socketName); m_display->start(); QVERIFY(m_display->isRunning()); m_display->createShm(); m_compositorInterface = m_display->createCompositor(m_display); QVERIFY(m_compositorInterface); m_compositorInterface->create(); QVERIFY(m_compositorInterface->isValid()); m_subCompositorInterface = m_display->createSubCompositor(m_display); QVERIFY(m_subCompositorInterface); m_subCompositorInterface->create(); QVERIFY(m_subCompositorInterface->isValid()); m_relativePointerManagerInterface = m_display->createRelativePointerManager(RelativePointerInterfaceVersion::UnstableV1, m_display); QVERIFY(m_relativePointerManagerInterface); m_relativePointerManagerInterface->create(); QVERIFY(m_relativePointerManagerInterface->isValid()); m_pointerGesturesInterface = m_display->createPointerGestures(PointerGesturesInterfaceVersion::UnstableV1, m_display); QVERIFY(m_pointerGesturesInterface); m_pointerGesturesInterface->create(); QVERIFY(m_pointerGesturesInterface->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(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new KWayland::Client::EventQueue(this); m_queue->setup(m_connection); KWayland::Client::Registry registry; QSignalSpy compositorSpy(®istry, SIGNAL(compositorAnnounced(quint32,quint32))); QSignalSpy seatSpy(®istry, SIGNAL(seatAnnounced(quint32,quint32))); QSignalSpy shmSpy(®istry, SIGNAL(shmAnnounced(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(), compositorSpy.first().last().value())); QVERIFY(m_compositor->isValid()); m_seat = registry.createSeat(seatSpy.first().first().value(), seatSpy.first().last().value(), this); QSignalSpy nameSpy(m_seat, SIGNAL(nameChanged(QString))); QVERIFY(nameSpy.wait()); m_shm = new KWayland::Client::ShmPool(this); m_shm->setup(registry.bindShm(shmSpy.first().first().value(), shmSpy.first().last().value())); 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_compositorInterface; m_compositorInterface = nullptr; delete m_seatInterface; m_seatInterface = nullptr; delete m_subCompositorInterface; m_subCompositorInterface = nullptr; delete m_relativePointerManagerInterface; m_relativePointerManagerInterface = nullptr; delete m_pointerGesturesInterface; m_pointerGesturesInterface = 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 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(); QVERIFY(serverSurface); QSignalSpy focusedPointerChangedSpy(m_seatInterface, &SeatInterface::focusedPointerChanged); QVERIFY(focusedPointerChangedSpy.isValid()); m_seatInterface->setPointerPos(QPoint(20, 18)); m_seatInterface->setFocusedPointerSurface(serverSurface, QPoint(10, 15)); QCOMPARE(focusedPointerChangedSpy.count(), 1); QVERIFY(!focusedPointerChangedSpy.first().first().value()); // no pointer yet QVERIFY(m_seatInterface->focusedPointerSurface()); QVERIFY(!m_seatInterface->focusedPointer()); Pointer *p = m_seat->createPointer(m_seat); QSignalSpy frameSpy(p, &Pointer::frame); QVERIFY(frameSpy.isValid()); const Pointer &cp = *p; QVERIFY(p->isValid()); QScopedPointer relativePointer(m_relativePointerManager->createRelativePointer(p)); QVERIFY(relativePointer->isValid()); QSignalSpy pointerCreatedSpy(m_seatInterface, SIGNAL(pointerCreated(KWayland::Server::PointerInterface*))); QVERIFY(pointerCreatedSpy.isValid()); // once the pointer is created it should be set as the focused pointer QVERIFY(pointerCreatedSpy.wait()); QVERIFY(m_seatInterface->focusedPointer()); QCOMPARE(pointerCreatedSpy.first().first().value(), m_seatInterface->focusedPointer()); QCOMPARE(focusedPointerChangedSpy.count(), 2); QCOMPARE(focusedPointerChangedSpy.last().first().value(), m_seatInterface->focusedPointer()); QVERIFY(frameSpy.wait()); QCOMPARE(frameSpy.count(), 1); m_seatInterface->setFocusedPointerSurface(nullptr); QCOMPARE(focusedPointerChangedSpy.count(), 3); QVERIFY(!focusedPointerChangedSpy.last().first().value()); serverSurface->client()->flush(); QVERIFY(frameSpy.wait()); QCOMPARE(frameSpy.count(), 2); 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()); QSignalSpy relativeMotionSpy(relativePointer.data(), &RelativePointer::relativeMotion); QVERIFY(relativeMotionSpy.isValid()); QVERIFY(!p->enteredSurface()); QVERIFY(!cp.enteredSurface()); m_seatInterface->setFocusedPointerSurface(serverSurface, QPoint(10, 15)); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.first().first().value(), m_display->serial()); QCOMPARE(enteredSpy.first().last().toPoint(), QPoint(10, 3)); QCOMPARE(frameSpy.count(), 3); PointerInterface *serverPointer = m_seatInterface->focusedPointer(); QVERIFY(serverPointer); QCOMPARE(p->enteredSurface(), s); QCOMPARE(cp.enteredSurface(), s); QCOMPARE(focusedPointerChangedSpy.count(), 4); QCOMPARE(focusedPointerChangedSpy.last().first().value(), serverPointer); // test motion m_seatInterface->setTimestamp(1); m_seatInterface->setPointerPos(QPoint(10, 16)); QVERIFY(motionSpy.wait()); QCOMPARE(frameSpy.count(), 4); QCOMPARE(motionSpy.first().first().toPoint(), QPoint(0, 1)); QCOMPARE(motionSpy.first().last().value(), quint32(1)); // test relative motion m_seatInterface->relativePointerMotion(QSizeF(1, 2), QSizeF(3, 4), quint64(-1)); 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(-1)); // test axis m_seatInterface->setTimestamp(2); m_seatInterface->pointerAxis(Qt::Horizontal, 10); QVERIFY(axisSpy.wait()); QCOMPARE(frameSpy.count(), 6); m_seatInterface->setTimestamp(3); m_seatInterface->pointerAxis(Qt::Vertical, 20); QVERIFY(axisSpy.wait()); QCOMPARE(frameSpy.count(), 7); 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 m_seatInterface->setTimestamp(4); m_seatInterface->pointerButtonPressed(1); QVERIFY(buttonSpy.wait()); QCOMPARE(frameSpy.count(), 8); QCOMPARE(buttonSpy.at(0).at(0).value(), m_display->serial()); m_seatInterface->setTimestamp(5); m_seatInterface->pointerButtonPressed(2); QVERIFY(buttonSpy.wait()); QCOMPARE(frameSpy.count(), 9); QCOMPARE(buttonSpy.at(1).at(0).value(), m_display->serial()); m_seatInterface->setTimestamp(6); m_seatInterface->pointerButtonReleased(2); QVERIFY(buttonSpy.wait()); QCOMPARE(frameSpy.count(), 10); QCOMPARE(buttonSpy.at(2).at(0).value(), m_display->serial()); m_seatInterface->setTimestamp(7); m_seatInterface->pointerButtonReleased(1); QVERIFY(buttonSpy.wait()); QCOMPARE(frameSpy.count(), 11); 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(), KWayland::Client::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(), KWayland::Client::Pointer::ButtonState::Pressed); QCOMPARE(buttonSpy.at(2).at(0).value(), m_seatInterface->pointerButtonSerial(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(), KWayland::Client::Pointer::ButtonState::Released); QCOMPARE(buttonSpy.at(3).at(0).value(), m_seatInterface->pointerButtonSerial(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(), KWayland::Client::Pointer::ButtonState::Released); // leave the surface m_seatInterface->setFocusedPointerSurface(nullptr); QCOMPARE(focusedPointerChangedSpy.count(), 5); QVERIFY(leftSpy.wait()); QCOMPARE(frameSpy.count(), 12); QCOMPARE(leftSpy.first().first().value(), m_display->serial()); QVERIFY(!p->enteredSurface()); QVERIFY(!cp.enteredSurface()); // now a relative motion should not be sent to the relative pointer m_seatInterface->relativePointerMotion(QSizeF(1, 2), QSizeF(3, 4), quint64(-1)); - QVERIFY(!relativeMotionSpy.wait()); + QVERIFY(!relativeMotionSpy.wait(500)); // enter it again m_seatInterface->setFocusedPointerSurface(serverSurface, QPoint(0, 0)); QCOMPARE(focusedPointerChangedSpy.count(), 6); QVERIFY(enteredSpy.wait()); QCOMPARE(frameSpy.count(), 13); QCOMPARE(p->enteredSurface(), s); QCOMPARE(cp.enteredSurface(), s); // send another relative motion event m_seatInterface->relativePointerMotion(QSizeF(4, 5), QSizeF(6, 7), quint64(1)); 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(1)); // destroy the focused pointer QSignalSpy unboundSpy(serverPointer, &Resource::unbound); QVERIFY(unboundSpy.isValid()); QSignalSpy destroyedSpy(serverPointer, &Resource::destroyed); QVERIFY(destroyedSpy.isValid()); delete p; QVERIFY(unboundSpy.wait()); QCOMPARE(unboundSpy.count(), 1); QCOMPARE(destroyedSpy.count(), 0); // now test that calling into the methods in Seat does not crash QCOMPARE(m_seatInterface->focusedPointer(), serverPointer); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); m_seatInterface->setTimestamp(8); m_seatInterface->setPointerPos(QPoint(10, 15)); m_seatInterface->setTimestamp(9); m_seatInterface->pointerButtonPressed(1); m_seatInterface->setTimestamp(10); m_seatInterface->pointerButtonReleased(1); m_seatInterface->setTimestamp(11); m_seatInterface->pointerAxis(Qt::Horizontal, 10); m_seatInterface->setTimestamp(12); m_seatInterface->pointerAxis(Qt::Vertical, 20); m_seatInterface->setFocusedPointerSurface(nullptr); QCOMPARE(focusedPointerChangedSpy.count(), 7); m_seatInterface->setFocusedPointerSurface(serverSurface); QCOMPARE(focusedPointerChangedSpy.count(), 8); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); QVERIFY(!m_seatInterface->focusedPointer()); // and now destroy QVERIFY(destroyedSpy.wait()); QCOMPARE(unboundSpy.count(), 1); QCOMPARE(destroyedSpy.count(), 1); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); QVERIFY(!m_seatInterface->focusedPointer()); // create a pointer again p = m_seat->createPointer(m_seat); QVERIFY(focusedPointerChangedSpy.wait()); QCOMPARE(focusedPointerChangedSpy.count(), 9); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); serverPointer = m_seatInterface->focusedPointer(); QVERIFY(serverPointer); QSignalSpy entered2Spy(p, &Pointer::entered); QVERIFY(entered2Spy.wait()); QCOMPARE(p->enteredSurface(), s); QSignalSpy leftSpy2(p, &Pointer::left); QVERIFY(leftSpy2.isValid()); delete s; QVERIFY(!p->enteredSurface()); QVERIFY(leftSpy2.wait()); QCOMPARE(focusedPointerChangedSpy.count(), 10); QVERIFY(!m_seatInterface->focusedPointerSurface()); QVERIFY(!m_seatInterface->focusedPointer()); } void TestWaylandSeat::testPointerTransformation_data() { QTest::addColumn("enterTransformation"); // global position at 20/18 QTest::addColumn("expectedEnterPoint"); // global position at 10/16 QTest::addColumn("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 KWayland::Client; using namespace KWayland::Server; QSignalSpy pointerSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(pointerSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(pointerSpy.wait()); QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); Surface *s = m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); m_seatInterface->setPointerPos(QPoint(20, 18)); QFETCH(QMatrix4x4, enterTransformation); m_seatInterface->setFocusedPointerSurface(serverSurface, enterTransformation); QCOMPARE(m_seatInterface->focusedPointerSurfaceTransformation(), enterTransformation); // no pointer yet QVERIFY(m_seatInterface->focusedPointerSurface()); QVERIFY(!m_seatInterface->focusedPointer()); Pointer *p = m_seat->createPointer(m_seat); const Pointer &cp = *p; QVERIFY(p->isValid()); QSignalSpy pointerCreatedSpy(m_seatInterface, &SeatInterface::pointerCreated); QVERIFY(pointerCreatedSpy.isValid()); // once the pointer is created it should be set as the focused pointer QVERIFY(pointerCreatedSpy.wait()); QVERIFY(m_seatInterface->focusedPointer()); QCOMPARE(pointerCreatedSpy.first().first().value(), m_seatInterface->focusedPointer()); m_seatInterface->setFocusedPointerSurface(nullptr); serverSurface->client()->flush(); QTest::qWait(100); QSignalSpy enteredSpy(p, &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(p, &Pointer::left); QVERIFY(leftSpy.isValid()); QSignalSpy motionSpy(p, &Pointer::motion); QVERIFY(motionSpy.isValid()); QVERIFY(!p->enteredSurface()); QVERIFY(!cp.enteredSurface()); m_seatInterface->setFocusedPointerSurface(serverSurface, enterTransformation); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.first().first().value(), m_display->serial()); QTEST(enteredSpy.first().last().toPointF(), "expectedEnterPoint"); PointerInterface *serverPointer = m_seatInterface->focusedPointer(); QVERIFY(serverPointer); QCOMPARE(p->enteredSurface(), s); QCOMPARE(cp.enteredSurface(), s); // test motion m_seatInterface->setTimestamp(1); m_seatInterface->setPointerPos(QPoint(10, 16)); QVERIFY(motionSpy.wait()); QTEST(motionSpy.first().first().toPointF(), "expectedMovePoint"); QCOMPARE(motionSpy.first().last().value(), quint32(1)); // leave the surface m_seatInterface->setFocusedPointerSurface(nullptr); QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.first().first().value(), m_display->serial()); QVERIFY(!p->enteredSurface()); QVERIFY(!cp.enteredSurface()); // enter it again m_seatInterface->setFocusedPointerSurface(serverSurface); QVERIFY(enteredSpy.wait()); QCOMPARE(p->enteredSurface(), s); QCOMPARE(cp.enteredSurface(), s); delete s; wl_display_flush(m_connection->display()); QTest::qWait(100); QVERIFY(!m_seatInterface->focusedPointerSurface()); } Q_DECLARE_METATYPE(Qt::MouseButton) void TestWaylandSeat::testPointerButton_data() { QTest::addColumn("qtButton"); QTest::addColumn("waylandButton"); QTest::newRow("left") << Qt::LeftButton << quint32(BTN_LEFT); QTest::newRow("right") << Qt::RightButton << quint32(BTN_RIGHT); QTest::newRow("mid") << Qt::MidButton << quint32(BTN_MIDDLE); 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 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()); m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); QScopedPointer p(m_seat->createPointer()); QVERIFY(p->isValid()); QSignalSpy buttonChangedSpy(p.data(), SIGNAL(buttonStateChanged(quint32,quint32,quint32,KWayland::Client::Pointer::ButtonState))); QVERIFY(buttonChangedSpy.isValid()); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); m_seatInterface->setPointerPos(QPoint(20, 18)); m_seatInterface->setFocusedPointerSurface(serverSurface, QPoint(10, 15)); QVERIFY(m_seatInterface->focusedPointerSurface()); QVERIFY(m_seatInterface->focusedPointer()); QCoreApplication::processEvents(); m_seatInterface->setFocusedPointerSurface(serverSurface, QPoint(10, 15)); PointerInterface *serverPointer = m_seatInterface->focusedPointer(); QVERIFY(serverPointer); QFETCH(Qt::MouseButton, qtButton); QFETCH(quint32, waylandButton); quint32 msec = QDateTime::currentMSecsSinceEpoch(); QCOMPARE(m_seatInterface->isPointerButtonPressed(waylandButton), false); QCOMPARE(m_seatInterface->isPointerButtonPressed(qtButton), false); m_seatInterface->setTimestamp(msec); m_seatInterface->pointerButtonPressed(qtButton); 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(), m_seatInterface->pointerButtonSerial(waylandButton)); QCOMPARE(buttonChangedSpy.last().at(0).value(), m_seatInterface->pointerButtonSerial(qtButton)); QCOMPARE(buttonChangedSpy.last().at(1).value(), msec); QCOMPARE(buttonChangedSpy.last().at(2).value(), waylandButton); QCOMPARE(buttonChangedSpy.last().at(3).value(), Pointer::ButtonState::Pressed); msec = QDateTime::currentMSecsSinceEpoch(); m_seatInterface->setTimestamp(QDateTime::currentMSecsSinceEpoch()); m_seatInterface->pointerButtonReleased(qtButton); 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(), m_seatInterface->pointerButtonSerial(waylandButton)); QCOMPARE(buttonChangedSpy.last().at(0).value(), m_seatInterface->pointerButtonSerial(qtButton)); QCOMPARE(buttonChangedSpy.last().at(1).value(), msec); QCOMPARE(buttonChangedSpy.last().at(2).value(), waylandButton); QCOMPARE(buttonChangedSpy.last().at(3).value(), 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 KWayland::Client; using namespace KWayland::Server; // first create the pointer QSignalSpy hasPointerChangedSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(hasPointerChangedSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(hasPointerChangedSpy.wait()); QScopedPointer 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); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer parentSurface(m_compositor->createSurface()); QScopedPointer childSurface(m_compositor->createSurface()); QScopedPointer grandChild1Surface(m_compositor->createSurface()); QScopedPointer grandChild2Surface(m_compositor->createSurface()); QScopedPointer childSubSurface(m_subCompositor->createSubSurface(childSurface.data(), parentSurface.data())); QScopedPointer grandChild1SubSurface(m_subCompositor->createSubSurface(grandChild1Surface.data(), childSurface.data())); QScopedPointer grandChild2SubSurface(m_subCompositor->createSubSurface(grandChild2Surface.data(), childSurface.data())); grandChild2SubSurface->setPosition(QPoint(0, 25)); // let's map the surfaces auto render = [this] (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(Surface::CommitFlag::None); }; render(grandChild2Surface.data(), QSize(50, 50)); render(grandChild1Surface.data(), QSize(50, 50)); render(childSurface.data(), QSize(50, 100)); render(parentSurface.data(), QSize(100, 100)); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface->isMapped()); // send in pointer events QSignalSpy enteredSpy(pointer.data(), &Pointer::entered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(pointer.data(), &Pointer::left); QVERIFY(leftSpy.isValid()); QSignalSpy motionSpy(pointer.data(), &Pointer::motion); QVERIFY(motionSpy.isValid()); // first to the grandChild2 in the overlapped area quint32 timestamp = 1; m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(25, 50)); m_seatInterface->setFocusedPointerSurface(serverSurface); 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.data()); // a motion on grandchild2 m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(25, 60)); 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->setPointerPos(QPointF(25, 80)); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 2); QCOMPARE(leftSpy.count(), 1); QCOMPARE(motionSpy.count(), 1); QCOMPARE(enteredSpy.last().last().toPointF(), QPointF(25, 80)); QCOMPARE(pointer->enteredSurface(), childSurface.data()); // a leave for the whole surface m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setFocusedPointerSurface(nullptr); QVERIFY(leftSpy.wait()); QCOMPARE(enteredSpy.count(), 2); QCOMPARE(leftSpy.count(), 2); QCOMPARE(motionSpy.count(), 1); // a new enter on the main surface m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(75, 50)); m_seatInterface->setFocusedPointerSurface(serverSurface); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 3); QCOMPARE(leftSpy.count(), 2); QCOMPARE(motionSpy.count(), 1); QCOMPARE(enteredSpy.last().last().toPointF(), QPointF(75, 50)); QCOMPARE(pointer->enteredSurface(), parentSurface.data()); } void TestWaylandSeat::testPointerSwipeGesture_data() { QTest::addColumn("cancel"); QTest::addColumn("expectedEndCount"); QTest::addColumn("expectedCancelCount"); QTest::newRow("end") << false << 1 << 0; QTest::newRow("cancel") << true << 0 << 1; } void TestWaylandSeat::testPointerSwipeGesture() { using namespace KWayland::Client; using namespace KWayland::Server; // first create the pointer and pointer swipe gesture QSignalSpy hasPointerChangedSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(hasPointerChangedSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(hasPointerChangedSpy.wait()); QScopedPointer pointer(m_seat->createPointer()); QScopedPointer gesture(m_pointerGestures->createSwipeGesture(pointer.data())); QVERIFY(gesture); QVERIFY(gesture->isValid()); QVERIFY(gesture->surface().isNull()); QCOMPARE(gesture->fingerCount(), 0u); QSignalSpy startSpy(gesture.data(), &PointerSwipeGesture::started); QVERIFY(startSpy.isValid()); QSignalSpy updateSpy(gesture.data(), &PointerSwipeGesture::updated); QVERIFY(updateSpy.isValid()); QSignalSpy endSpy(gesture.data(), &PointerSwipeGesture::ended); QVERIFY(endSpy.isValid()); QSignalSpy cancelledSpy(gesture.data(), &PointerSwipeGesture::cancelled); QVERIFY(cancelledSpy.isValid()); // now create a surface QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); m_seatInterface->setFocusedPointerSurface(serverSurface); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); QVERIFY(m_seatInterface->focusedPointer()); // send in the start quint32 timestamp = 1; m_seatInterface->setTimestamp(timestamp++); m_seatInterface->startPointerSwipeGesture(2); QVERIFY(startSpy.wait()); QCOMPARE(startSpy.count(), 1); QCOMPARE(startSpy.first().at(0).value(), m_display->serial()); QCOMPARE(startSpy.first().at(1).value(), 1u); QCOMPARE(gesture->fingerCount(), 2u); QCOMPARE(gesture->surface().data(), surface.data()); // another start should not be possible m_seatInterface->startPointerSwipeGesture(2); - QVERIFY(!startSpy.wait()); + QVERIFY(!startSpy.wait(500)); // send in some updates m_seatInterface->setTimestamp(timestamp++); m_seatInterface->updatePointerSwipeGesture(QSizeF(2, 3)); QVERIFY(updateSpy.wait()); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->updatePointerSwipeGesture(QSizeF(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(), 2u); QCOMPARE(updateSpy.at(1).at(0).toSizeF(), QSizeF(4, 5)); QCOMPARE(updateSpy.at(1).at(1).value(), 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()); QTEST(endSpy.count(), "expectedEndCount"); QTEST(cancelledSpy.count(), "expectedCancelCount"); QCOMPARE(spy->count(), 1); QCOMPARE(spy->first().at(0).value(), m_display->serial()); QCOMPARE(spy->first().at(1).value(), 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->setFocusedPointerSurface(nullptr); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->updatePointerSwipeGesture(QSizeF(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("cancel"); QTest::addColumn("expectedEndCount"); QTest::addColumn("expectedCancelCount"); QTest::newRow("end") << false << 1 << 0; QTest::newRow("cancel") << true << 0 << 1; } void TestWaylandSeat::testPointerPinchGesture() { using namespace KWayland::Client; using namespace KWayland::Server; // first create the pointer and pointer swipe gesture QSignalSpy hasPointerChangedSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(hasPointerChangedSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(hasPointerChangedSpy.wait()); QScopedPointer pointer(m_seat->createPointer()); QScopedPointer gesture(m_pointerGestures->createPinchGesture(pointer.data())); QVERIFY(gesture); QVERIFY(gesture->isValid()); QVERIFY(gesture->surface().isNull()); QCOMPARE(gesture->fingerCount(), 0u); QSignalSpy startSpy(gesture.data(), &PointerPinchGesture::started); QVERIFY(startSpy.isValid()); QSignalSpy updateSpy(gesture.data(), &PointerPinchGesture::updated); QVERIFY(updateSpy.isValid()); QSignalSpy endSpy(gesture.data(), &PointerPinchGesture::ended); QVERIFY(endSpy.isValid()); QSignalSpy cancelledSpy(gesture.data(), &PointerPinchGesture::cancelled); QVERIFY(cancelledSpy.isValid()); // now create a surface QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); m_seatInterface->setFocusedPointerSurface(serverSurface); QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); QVERIFY(m_seatInterface->focusedPointer()); // send in the start quint32 timestamp = 1; m_seatInterface->setTimestamp(timestamp++); m_seatInterface->startPointerPinchGesture(3); QVERIFY(startSpy.wait()); QCOMPARE(startSpy.count(), 1); QCOMPARE(startSpy.first().at(0).value(), m_display->serial()); QCOMPARE(startSpy.first().at(1).value(), 1u); QCOMPARE(gesture->fingerCount(), 3u); QCOMPARE(gesture->surface().data(), surface.data()); // another start should not be possible m_seatInterface->startPointerPinchGesture(3); - QVERIFY(!startSpy.wait()); + QVERIFY(!startSpy.wait(500)); // send in some updates m_seatInterface->setTimestamp(timestamp++); m_seatInterface->updatePointerPinchGesture(QSizeF(2, 3), 2, 45); QVERIFY(updateSpy.wait()); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->updatePointerPinchGesture(QSizeF(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(), 2u); QCOMPARE(updateSpy.at(0).at(2).value(), 45u); QCOMPARE(updateSpy.at(0).at(3).value(), 2u); QCOMPARE(updateSpy.at(1).at(0).toSizeF(), QSizeF(4, 5)); QCOMPARE(updateSpy.at(1).at(1).value(), 1u); QCOMPARE(updateSpy.at(1).at(2).value(), 90u); QCOMPARE(updateSpy.at(1).at(3).value(), 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()); QTEST(endSpy.count(), "expectedEndCount"); QTEST(cancelledSpy.count(), "expectedCancelCount"); QCOMPARE(spy->count(), 1); QCOMPARE(spy->first().at(0).value(), m_display->serial()); QCOMPARE(spy->first().at(1).value(), 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->setFocusedPointerSurface(nullptr); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->updatePointerPinchGesture(QSizeF(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::testKeyboardSubSurfaceTreeFromPointer() { // this test verifies that when clicking on a sub-surface the keyboard focus passes to it using namespace KWayland::Client; using namespace KWayland::Server; // first create the pointer QSignalSpy hasPointerChangedSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(hasPointerChangedSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(hasPointerChangedSpy.wait()); QScopedPointer pointer(m_seat->createPointer()); // and create keyboard QSignalSpy hasKeyboardChangedSpy(m_seat, &Seat::hasKeyboardChanged); QVERIFY(hasKeyboardChangedSpy.isValid()); m_seatInterface->setHasKeyboard(true); QVERIFY(hasKeyboardChangedSpy.wait()); QScopedPointer keyboard(m_seat->createKeyboard()); // 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); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer parentSurface(m_compositor->createSurface()); QScopedPointer childSurface(m_compositor->createSurface()); QScopedPointer grandChild1Surface(m_compositor->createSurface()); QScopedPointer grandChild2Surface(m_compositor->createSurface()); QScopedPointer childSubSurface(m_subCompositor->createSubSurface(childSurface.data(), parentSurface.data())); QScopedPointer grandChild1SubSurface(m_subCompositor->createSubSurface(grandChild1Surface.data(), childSurface.data())); QScopedPointer grandChild2SubSurface(m_subCompositor->createSubSurface(grandChild2Surface.data(), childSurface.data())); grandChild2SubSurface->setPosition(QPoint(0, 25)); // let's map the surfaces auto render = [this] (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(Surface::CommitFlag::None); }; render(grandChild2Surface.data(), QSize(50, 50)); render(grandChild1Surface.data(), QSize(50, 50)); render(childSurface.data(), QSize(50, 100)); render(parentSurface.data(), QSize(100, 100)); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface->isMapped()); // pass keyboard focus to the main surface QSignalSpy enterSpy(keyboard.data(), &Keyboard::entered); QVERIFY(enterSpy.isValid()); QSignalSpy leftSpy(keyboard.data(), &Keyboard::left); QVERIFY(leftSpy.isValid()); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QVERIFY(enterSpy.wait()); QCOMPARE(enterSpy.count(), 1); QCOMPARE(leftSpy.count(), 0); QCOMPARE(keyboard->enteredSurface(), parentSurface.data()); // now pass also pointer focus to the surface QSignalSpy pointerEnterSpy(pointer.data(), &Pointer::entered); QVERIFY(pointerEnterSpy.isValid()); quint32 timestamp = 1; m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(25, 50)); m_seatInterface->setFocusedPointerSurface(serverSurface); QVERIFY(pointerEnterSpy.wait()); QCOMPARE(pointerEnterSpy.count(), 1); // should not have affected the keyboard QCOMPARE(enterSpy.count(), 1); QCOMPARE(leftSpy.count(), 0); // let's click m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonPressed(Qt::LeftButton); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonReleased(Qt::LeftButton); QVERIFY(enterSpy.wait()); QCOMPARE(enterSpy.count(), 2); QCOMPARE(leftSpy.count(), 1); QCOMPARE(keyboard->enteredSurface(), grandChild2Surface.data()); // click on same surface should not trigger another enter m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonPressed(Qt::LeftButton); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonReleased(Qt::LeftButton); QVERIFY(!enterSpy.wait(200)); QCOMPARE(enterSpy.count(), 2); QCOMPARE(leftSpy.count(), 1); QCOMPARE(keyboard->enteredSurface(), grandChild2Surface.data()); // unfocus keyboard m_seatInterface->setFocusedKeyboardSurface(nullptr); QVERIFY(leftSpy.wait()); QCOMPARE(enterSpy.count(), 2); QCOMPARE(leftSpy.count(), 2); } void TestWaylandSeat::testCursor() { 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()); m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); QScopedPointer p(m_seat->createPointer()); QVERIFY(p->isValid()); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QSignalSpy enteredSpy(p.data(), SIGNAL(entered(quint32,QPointF))); QVERIFY(enteredSpy.isValid()); m_seatInterface->setPointerPos(QPoint(20, 18)); m_seatInterface->setFocusedPointerSurface(serverSurface, QPoint(10, 15)); quint32 serial = m_seatInterface->display()->serial(); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.first().first().value(), serial); QVERIFY(m_seatInterface->focusedPointerSurface()); QVERIFY(m_seatInterface->focusedPointer()); QVERIFY(!m_seatInterface->focusedPointer()->cursor()); QSignalSpy cursorChangedSpy(m_seatInterface->focusedPointer(), SIGNAL(cursorChanged())); QVERIFY(cursorChangedSpy.isValid()); // just remove the pointer p->setCursor(nullptr); QVERIFY(cursorChangedSpy.wait()); QCOMPARE(cursorChangedSpy.count(), 1); auto cursor = m_seatInterface->focusedPointer()->cursor(); QVERIFY(cursor); QVERIFY(!cursor->surface()); QCOMPARE(cursor->hotspot(), QPoint()); QCOMPARE(cursor->enteredSerial(), serial); QCOMPARE(cursor->pointer(), m_seatInterface->focusedPointer()); QSignalSpy hotspotChangedSpy(cursor, SIGNAL(hotspotChanged())); QVERIFY(hotspotChangedSpy.isValid()); QSignalSpy surfaceChangedSpy(cursor, SIGNAL(surfaceChanged())); QVERIFY(surfaceChangedSpy.isValid()); QSignalSpy enteredSerialChangedSpy(cursor, SIGNAL(enteredSerialChanged())); QVERIFY(enteredSerialChangedSpy.isValid()); QSignalSpy changedSpy(cursor, SIGNAL(changed())); QVERIFY(changedSpy.isValid()); // test changing hotspot p->setCursor(nullptr, QPoint(1, 2)); QVERIFY(hotspotChangedSpy.wait()); QCOMPARE(hotspotChangedSpy.count(), 1); QCOMPARE(changedSpy.count(), 1); QCOMPARE(cursorChangedSpy.count(), 2); QCOMPARE(cursor->hotspot(), QPoint(1, 2)); QVERIFY(enteredSerialChangedSpy.isEmpty()); QVERIFY(surfaceChangedSpy.isEmpty()); // set surface auto cursorSurface = m_compositor->createSurface(m_compositor); QVERIFY(cursorSurface->isValid()); p->setCursor(cursorSurface, QPoint(1, 2)); QVERIFY(surfaceChangedSpy.wait()); QCOMPARE(surfaceChangedSpy.count(), 1); QCOMPARE(changedSpy.count(), 2); QCOMPARE(cursorChangedSpy.count(), 3); QVERIFY(enteredSerialChangedSpy.isEmpty()); QCOMPARE(cursor->hotspot(), QPoint(1, 2)); QVERIFY(cursor->surface()); // and add an image to the surface QImage img(QSize(10, 20), QImage::Format_RGB32); img.fill(Qt::red); cursorSurface->attachBuffer(m_shm->createBuffer(img)); cursorSurface->damage(QRect(0, 0, 10, 20)); cursorSurface->commit(Surface::CommitFlag::None); QVERIFY(changedSpy.wait()); QCOMPARE(changedSpy.count(), 3); QCOMPARE(cursorChangedSpy.count(), 4); QCOMPARE(surfaceChangedSpy.count(), 1); QCOMPARE(cursor->surface()->buffer()->data(), img); // and add another image to the surface QImage blue(QSize(10, 20), QImage::Format_ARGB32_Premultiplied); blue.fill(Qt::blue); cursorSurface->attachBuffer(m_shm->createBuffer(blue)); cursorSurface->damage(QRect(0, 0, 10, 20)); cursorSurface->commit(Surface::CommitFlag::None); QVERIFY(changedSpy.wait()); QCOMPARE(changedSpy.count(), 4); QCOMPARE(cursorChangedSpy.count(), 5); QCOMPARE(cursor->surface()->buffer()->data(), blue); p->hideCursor(); QVERIFY(surfaceChangedSpy.wait()); QCOMPARE(changedSpy.count(), 5); QCOMPARE(cursorChangedSpy.count(), 6); QCOMPARE(surfaceChangedSpy.count(), 2); QVERIFY(!cursor->surface()); } void TestWaylandSeat::testCursorDamage() { // this test verifies that damaging a cursor surface triggers a cursor changed on the server using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy pointerSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(pointerSpy.isValid()); m_seatInterface->setHasPointer(true); QVERIFY(pointerSpy.wait()); // create pointer QScopedPointer p(m_seat->createPointer()); QVERIFY(p->isValid()); QSignalSpy enteredSpy(p.data(), &Pointer::entered); QVERIFY(enteredSpy.isValid()); // create surface QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); m_compositor->createSurface(m_compositor); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); // send enter to the surface m_seatInterface->setFocusedPointerSurface(serverSurface); QVERIFY(enteredSpy.wait()); // create a signal spy for the cursor changed signal auto pointer = m_seatInterface->focusedPointer(); QSignalSpy cursorChangedSpy(pointer, &PointerInterface::cursorChanged); QVERIFY(cursorChangedSpy.isValid()); // now let's set the cursor Surface *cursorSurface = m_compositor->createSurface(m_compositor); QVERIFY(cursorSurface); QImage red(QSize(10, 10), QImage::Format_ARGB32_Premultiplied); red.fill(Qt::red); cursorSurface->attachBuffer(m_shm->createBuffer(red)); cursorSurface->damage(QRect(0, 0, 10, 10)); cursorSurface->commit(Surface::CommitFlag::None); p->setCursor(cursorSurface, QPoint(0, 0)); QVERIFY(cursorChangedSpy.wait()); QCOMPARE(pointer->cursor()->surface()->buffer()->data(), red); // and damage the surface QImage blue(QSize(10, 10), QImage::Format_ARGB32_Premultiplied); blue.fill(Qt::blue); cursorSurface->attachBuffer(m_shm->createBuffer(blue)); cursorSurface->damage(QRect(0, 0, 10, 10)); cursorSurface->commit(Surface::CommitFlag::None); QVERIFY(cursorChangedSpy.wait()); QCOMPARE(pointer->cursor()->surface()->buffer()->data(), blue); } 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(); QVERIFY(serverSurface); m_seatInterface->setFocusedKeyboardSurface(serverSurface); // no keyboard yet QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); QVERIFY(!m_seatInterface->focusedKeyboard()); Keyboard *keyboard = m_seat->createKeyboard(m_seat); QSignalSpy repeatInfoSpy(keyboard, &Keyboard::keyRepeatChanged); QVERIFY(repeatInfoSpy.isValid()); const Keyboard &ckeyboard = *keyboard; QVERIFY(keyboard->isValid()); QCOMPARE(keyboard->isKeyRepeatEnabled(), false); QCOMPARE(keyboard->keyRepeatDelay(), 0); QCOMPARE(keyboard->keyRepeatRate(), 0); wl_display_flush(m_connection->display()); QTest::qWait(100); auto serverKeyboard = m_seatInterface->focusedKeyboard(); 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->setKeyRepeatInfo(25, 660); m_seatInterface->focusedKeyboard()->client()->flush(); QVERIFY(repeatInfoSpy.wait()); QCOMPARE(repeatInfoSpy.count(), 2); QCOMPARE(keyboard->isKeyRepeatEnabled(), true); QCOMPARE(keyboard->keyRepeatRate(), 25); QCOMPARE(keyboard->keyRepeatDelay(), 660); m_seatInterface->setTimestamp(1); m_seatInterface->keyPressed(KEY_K); m_seatInterface->setTimestamp(2); m_seatInterface->keyPressed(KEY_D); m_seatInterface->setTimestamp(3); m_seatInterface->keyPressed(KEY_E); QSignalSpy modifierSpy(keyboard, SIGNAL(modifiersChanged(quint32,quint32,quint32,quint32))); QVERIFY(modifierSpy.isValid()); QSignalSpy enteredSpy(keyboard, SIGNAL(entered(quint32))); QVERIFY(enteredSpy.isValid()); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); QCOMPARE(m_seatInterface->focusedKeyboard()->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)); QCOMPARE(enteredSpy.count(), 1); // TODO: get through API QCOMPARE(enteredSpy.first().first().value(), m_display->serial() - 1); QSignalSpy keyChangedSpy(keyboard, SIGNAL(keyChanged(quint32,KWayland::Client::Keyboard::KeyState,quint32))); QVERIFY(keyChangedSpy.isValid()); m_seatInterface->setTimestamp(4); m_seatInterface->keyReleased(KEY_E); QVERIFY(keyChangedSpy.wait()); m_seatInterface->setTimestamp(5); m_seatInterface->keyReleased(KEY_D); QVERIFY(keyChangedSpy.wait()); m_seatInterface->setTimestamp(6); m_seatInterface->keyReleased(KEY_K); QVERIFY(keyChangedSpy.wait()); m_seatInterface->setTimestamp(7); m_seatInterface->keyPressed(KEY_F1); QVERIFY(keyChangedSpy.wait()); m_seatInterface->setTimestamp(8); m_seatInterface->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)); // releasing a key which is already released should not set a key changed m_seatInterface->keyReleased(KEY_F1); QVERIFY(!keyChangedSpy.wait(200)); // let's press it again m_seatInterface->keyPressed(KEY_F1); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.count(), 6); // press again should be ignored m_seatInterface->keyPressed(KEY_F1); QVERIFY(!keyChangedSpy.wait(200)); // and release m_seatInterface->keyReleased(KEY_F1); QVERIFY(keyChangedSpy.wait()); QCOMPARE(keyChangedSpy.count(), 7); m_seatInterface->updateKeyboardModifiers(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)); QSignalSpy leftSpy(keyboard, SIGNAL(left(quint32))); QVERIFY(leftSpy.isValid()); m_seatInterface->setFocusedKeyboardSurface(nullptr); QVERIFY(!m_seatInterface->focusedKeyboardSurface()); QVERIFY(!m_seatInterface->focusedKeyboard()); QVERIFY(leftSpy.wait()); QCOMPARE(leftSpy.count(), 1); // TODO: get through API QCOMPARE(leftSpy.first().first().value(), 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->focusedKeyboard()->focusedSurface(), serverSurface); QCOMPARE(enteredSpy.count(), 2); QCOMPARE(keyboard->enteredSurface(), s); QCOMPARE(ckeyboard.enteredSurface(), s); QSignalSpy serverSurfaceDestroyedSpy(serverSurface, &QObject::destroyed); QVERIFY(serverSurfaceDestroyedSpy.isValid()); QCOMPARE(keyboard->enteredSurface(), s); delete s; QVERIFY(!keyboard->enteredSurface()); QVERIFY(leftSpy.wait()); QCOMPARE(serverSurfaceDestroyedSpy.count(), 1); QVERIFY(!m_seatInterface->focusedKeyboardSurface()); QVERIFY(!m_seatInterface->focusedKeyboard()); QVERIFY(!serverKeyboard->focusedSurface()); // let's create a Surface again QScopedPointer s2(m_compositor->createSurface()); QVERIFY(surfaceCreatedSpy.wait()); QCOMPARE(surfaceCreatedSpy.count(), 2); serverSurface = surfaceCreatedSpy.last().first().value(); QVERIFY(serverSurface); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); QCOMPARE(m_seatInterface->focusedKeyboard(), serverKeyboard); // delete the Keyboard QSignalSpy unboundSpy(serverKeyboard, &Resource::unbound); QVERIFY(unboundSpy.isValid()); QSignalSpy destroyedSpy(serverKeyboard, &Resource::destroyed); QVERIFY(destroyedSpy.isValid()); delete keyboard; QVERIFY(unboundSpy.wait()); QCOMPARE(unboundSpy.count(), 1); QCOMPARE(destroyedSpy.count(), 0); // verify that calling into the Keyboard related functionality doesn't crash m_seatInterface->setTimestamp(9); m_seatInterface->keyPressed(KEY_F2); m_seatInterface->setTimestamp(10); m_seatInterface->keyReleased(KEY_F2); m_seatInterface->setKeyRepeatInfo(30, 560); m_seatInterface->setKeyRepeatInfo(25, 660); m_seatInterface->updateKeyboardModifiers(5, 6, 7, 8); m_seatInterface->setKeymap(open("/dev/null", O_RDONLY), 0); m_seatInterface->setFocusedKeyboardSurface(nullptr); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); QVERIFY(!m_seatInterface->focusedKeyboard()); QVERIFY(destroyedSpy.wait()); QCOMPARE(destroyedSpy.count(), 1); // create a second Keyboard to verify that repeat info is announced properly Keyboard *keyboard2 = m_seat->createKeyboard(m_seat); QSignalSpy repeatInfoSpy2(keyboard2, &Keyboard::keyRepeatChanged); QVERIFY(repeatInfoSpy2.isValid()); QVERIFY(keyboard2->isValid()); QCOMPARE(keyboard2->isKeyRepeatEnabled(), false); QCOMPARE(keyboard2->keyRepeatDelay(), 0); QCOMPARE(keyboard2->keyRepeatRate(), 0); wl_display_flush(m_connection->display()); QVERIFY(repeatInfoSpy2.wait()); QCOMPARE(keyboard2->isKeyRepeatEnabled(), true); QCOMPARE(keyboard2->keyRepeatRate(), 25); QCOMPARE(keyboard2->keyRepeatDelay(), 660); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); serverKeyboard = m_seatInterface->focusedKeyboard(); QVERIFY(serverKeyboard); QSignalSpy keyboard2DestroyedSpy(serverKeyboard, &QObject::destroyed); QVERIFY(keyboard2DestroyedSpy.isValid()); delete keyboard2; QVERIFY(keyboard2DestroyedSpy.wait()); // this should have unset it on the server QVERIFY(!m_seatInterface->focusedKeyboard()); // but not the surface QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); } void TestWaylandSeat::testCast() { using namespace KWayland::Client; Registry registry; QSignalSpy seatSpy(®istry, 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(), seatSpy.first().last().value()); 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()); QSignalSpy touchSpy(m_seat, SIGNAL(hasTouchChanged(bool))); QVERIFY(touchSpy.isValid()); m_seatInterface->setHasTouch(true); QVERIFY(touchSpy.wait()); Touch *t = m_seat->createTouch(m_seat); QVERIFY(t->isValid()); delete m_compositor; m_compositor = nullptr; connect(m_connection, &ConnectionThread::connectionDied, m_seat, &Seat::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_shm, &ShmPool::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_subCompositor, &SubCompositor::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_relativePointerManager, &RelativePointerManager::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_pointerGestures, &PointerGestures::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; m_subCompositorInterface = nullptr; m_relativePointerManagerInterface = nullptr; m_pointerGesturesInterface = nullptr; QVERIFY(connectionDiedSpy.wait()); // now the seat should be destroyed; QVERIFY(!m_seat->isValid()); QVERIFY(!k->isValid()); QVERIFY(!p->isValid()); QVERIFY(!t->isValid()); // calling destroy again should not fail m_seat->destroy(); k->destroy(); p->destroy(); t->destroy(); } void TestWaylandSeat::testSelection() { using namespace KWayland::Client; using namespace KWayland::Server; QScopedPointer ddmi(m_display->createDataDeviceManager()); ddmi->create(); Registry registry; QSignalSpy dataDeviceManagerSpy(®istry, SIGNAL(dataDeviceManagerAnnounced(quint32,quint32))); QVERIFY(dataDeviceManagerSpy.isValid()); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(dataDeviceManagerSpy.wait()); QScopedPointer ddm(registry.createDataDeviceManager(dataDeviceManagerSpy.first().first().value(), dataDeviceManagerSpy.first().last().value())); QVERIFY(ddm->isValid()); QScopedPointer dd1(ddm->getDataDevice(m_seat)); QVERIFY(dd1->isValid()); QSignalSpy selectionSpy(dd1.data(), SIGNAL(selectionOffered(KWayland::Client::DataOffer*))); QVERIFY(selectionSpy.isValid()); QSignalSpy selectionClearedSpy(dd1.data(), SIGNAL(selectionCleared())); QVERIFY(selectionClearedSpy.isValid()); QSignalSpy surfaceCreatedSpy(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(surface->isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(!m_seatInterface->selection()); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); QVERIFY(!m_seatInterface->focusedKeyboard()); 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 QScopedPointer ds(ddm->createDataSource()); QVERIFY(ds->isValid()); ds->offer(QStringLiteral("text/plain")); dd1->setSelection(0, ds.data()); QVERIFY(selectionSpy.wait()); QCOMPARE(selectionSpy.count(), 1); auto ddi = m_seatInterface->selection(); QVERIFY(ddi); auto df = selectionSpy.first().first().value(); QCOMPARE(df->offeredMimeTypes().count(), 1); QCOMPARE(df->offeredMimeTypes().first().name(), QStringLiteral("text/plain")); // try to clear dd1->setSelection(0); QVERIFY(selectionClearedSpy.wait()); QCOMPARE(selectionClearedSpy.count(), 1); QCOMPARE(selectionSpy.count(), 1); // unset the keyboard focus m_seatInterface->setFocusedKeyboardSurface(nullptr); QVERIFY(!m_seatInterface->focusedKeyboardSurface()); QVERIFY(!m_seatInterface->focusedKeyboard()); serverSurface->client()->flush(); QCoreApplication::processEvents(); QCoreApplication::processEvents(); // try to set Selection dd1->setSelection(0, ds.data()); 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); // 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(!selectionSpy.wait(100)); // now let's set it manually m_seatInterface->setSelection(ddi); QCOMPARE(m_seatInterface->selection(), ddi); QVERIFY(selectionSpy.wait()); QCOMPARE(selectionSpy.count(), 2); // setting the same again should not change m_seatInterface->setSelection(ddi); QVERIFY(!selectionSpy.wait(100)); // now clear it manully m_seatInterface->setSelection(nullptr); QVERIFY(selectionClearedSpy.wait()); QCOMPARE(selectionSpy.count(), 2); // create a second ddi and a data source QScopedPointer dd2(ddm->getDataDevice(m_seat)); QVERIFY(dd2->isValid()); QScopedPointer ds2(ddm->createDataSource()); QVERIFY(ds2->isValid()); ds2->offer(QStringLiteral("text/plain")); dd2->setSelection(0, ds2.data()); QVERIFY(selectionSpy.wait()); QSignalSpy cancelledSpy(ds2.data(), &DataSource::cancelled); QVERIFY(cancelledSpy.isValid()); m_seatInterface->setSelection(ddi); QVERIFY(cancelledSpy.wait()); // Copy already cleared selection, BUG 383054 ddi->sendSelection(ddi); } void TestWaylandSeat::testSelectionNoDataSource() { // this test verifies that the server doesn't crash when using setSelection with // a DataDevice which doesn't have a DataSource yet using namespace KWayland::Client; using namespace KWayland::Server; // first create the DataDevice QScopedPointer ddmi(m_display->createDataDeviceManager()); ddmi->create(); QSignalSpy ddiCreatedSpy(ddmi.data(), &DataDeviceManagerInterface::dataDeviceCreated); QVERIFY(ddiCreatedSpy.isValid()); Registry registry; QSignalSpy dataDeviceManagerSpy(®istry, &Registry::dataDeviceManagerAnnounced); QVERIFY(dataDeviceManagerSpy.isValid()); registry.setEventQueue(m_queue); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(dataDeviceManagerSpy.wait()); QScopedPointer ddm(registry.createDataDeviceManager(dataDeviceManagerSpy.first().first().value(), dataDeviceManagerSpy.first().last().value())); QVERIFY(ddm->isValid()); QScopedPointer dd(ddm->getDataDevice(m_seat)); QVERIFY(dd->isValid()); QVERIFY(ddiCreatedSpy.wait()); auto ddi = ddiCreatedSpy.first().first().value(); QVERIFY(ddi); // now create a surface and pass it keyboard focus QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(surface->isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(!m_seatInterface->selection()); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); // now let's set the selection m_seatInterface->setSelection(ddi); } 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 KWayland::Client; using namespace KWayland::Server; // create the DataDeviceManager QScopedPointer ddmi(m_display->createDataDeviceManager()); ddmi->create(); QSignalSpy ddiCreatedSpy(ddmi.data(), &DataDeviceManagerInterface::dataDeviceCreated); QVERIFY(ddiCreatedSpy.isValid()); // create a second Wayland client connection to use it for setSelection auto c = new ConnectionThread; QSignalSpy connectedSpy(c, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); c->setSocketName(s_socketName); auto thread = new QThread(this); c->moveToThread(thread); thread->start(); c->initConnection(); QVERIFY(connectedSpy.wait()); QScopedPointer queue(new EventQueue); queue->setup(c); QScopedPointer registry(new Registry); QSignalSpy interfacesAnnouncedSpy(registry.data(), &Registry::interfacesAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); registry->setEventQueue(queue.data()); registry->create(c); QVERIFY(registry->isValid()); registry->setup(); QVERIFY(interfacesAnnouncedSpy.wait()); QScopedPointer seat(registry->createSeat(registry->interface(Registry::Interface::Seat).name, registry->interface(Registry::Interface::Seat).version)); QVERIFY(seat->isValid()); QScopedPointer ddm1(registry->createDataDeviceManager(registry->interface(Registry::Interface::DataDeviceManager).name, registry->interface(Registry::Interface::DataDeviceManager).version)); QVERIFY(ddm1->isValid()); // now create our first datadevice QScopedPointer dd1(ddm1->getDataDevice(seat.data())); QVERIFY(ddiCreatedSpy.wait()); auto ddi = ddiCreatedSpy.first().first().value(); QVERIFY(ddi); m_seatInterface->setSelection(ddi); // switch to other client // create a surface and pass it keyboard focus QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer surface(m_compositor->createSurface()); QVERIFY(surface->isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); m_seatInterface->setFocusedKeyboardSurface(serverSurface); QCOMPARE(m_seatInterface->focusedKeyboardSurface(), serverSurface); // now create a DataDevice Registry registry2; QSignalSpy dataDeviceManagerSpy(®istry2, &Registry::dataDeviceManagerAnnounced); QVERIFY(dataDeviceManagerSpy.isValid()); registry2.setEventQueue(m_queue); registry2.create(m_connection->display()); QVERIFY(registry2.isValid()); registry2.setup(); QVERIFY(dataDeviceManagerSpy.wait()); QScopedPointer ddm(registry2.createDataDeviceManager(dataDeviceManagerSpy.first().first().value(), dataDeviceManagerSpy.first().last().value())); QVERIFY(ddm->isValid()); QScopedPointer 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 KWayland::Client; using namespace KWayland::Server; QSignalSpy touchSpy(m_seat, SIGNAL(hasTouchChanged(bool))); QVERIFY(touchSpy.isValid()); m_seatInterface->setHasTouch(true); QVERIFY(touchSpy.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(); QVERIFY(serverSurface); m_seatInterface->setFocusedTouchSurface(serverSurface); // no keyboard yet QCOMPARE(m_seatInterface->focusedTouchSurface(), serverSurface); QVERIFY(!m_seatInterface->focusedTouch()); QSignalSpy touchCreatedSpy(m_seatInterface, SIGNAL(touchCreated(KWayland::Server::TouchInterface*))); QVERIFY(touchCreatedSpy.isValid()); Touch *touch = m_seat->createTouch(m_seat); QVERIFY(touch->isValid()); QVERIFY(touchCreatedSpy.wait()); auto serverTouch = m_seatInterface->focusedTouch(); QVERIFY(serverTouch); QCOMPARE(touchCreatedSpy.first().first().value(), m_seatInterface->focusedTouch()); QSignalSpy sequenceStartedSpy(touch, SIGNAL(sequenceStarted(KWayland::Client::TouchPoint*))); QVERIFY(sequenceStartedSpy.isValid()); QSignalSpy sequenceEndedSpy(touch, SIGNAL(sequenceEnded())); QVERIFY(sequenceEndedSpy.isValid()); QSignalSpy sequenceCanceledSpy(touch, SIGNAL(sequenceCanceled())); QVERIFY(sequenceCanceledSpy.isValid()); QSignalSpy frameEndedSpy(touch, SIGNAL(frameEnded())); QVERIFY(frameEndedSpy.isValid()); QSignalSpy pointAddedSpy(touch, SIGNAL(pointAdded(KWayland::Client::TouchPoint*))); QVERIFY(pointAddedSpy.isValid()); QSignalSpy pointMovedSpy(touch, SIGNAL(pointMoved(KWayland::Client::TouchPoint*))); QVERIFY(pointMovedSpy.isValid()); QSignalSpy pointRemovedSpy(touch, SIGNAL(pointRemoved(KWayland::Client::TouchPoint*))); QVERIFY(pointRemovedSpy.isValid()); // try a few things m_seatInterface->setFocusedTouchSurfacePosition(QPointF(10, 20)); QCOMPARE(m_seatInterface->focusedTouchSurfacePosition(), QPointF(10, 20)); m_seatInterface->setTimestamp(1); QCOMPARE(m_seatInterface->touchDown(QPointF(15, 26)), 0); 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); TouchPoint *tp = sequenceStartedSpy.first().first().value(); 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->touchFrame(); QVERIFY(frameEndedSpy.wait()); QCOMPARE(frameEndedSpy.count(), 1); // move the one point m_seatInterface->setTimestamp(2); m_seatInterface->touchMove(0, QPointF(10, 20)); m_seatInterface->touchFrame(); 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(), 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(3); QCOMPARE(m_seatInterface->touchDown(QPointF(15, 26)), 1); m_seatInterface->touchFrame(); 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); TouchPoint *tp2 = pointAddedSpy.first().first().value(); 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(4); m_seatInterface->touchUp(1); m_seatInterface->touchFrame(); 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(), 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(5); QCOMPARE(m_seatInterface->touchDown(QPointF(15, 26)), 1); m_seatInterface->touchFrame(); m_seatInterface->setTimestamp(6); m_seatInterface->touchUp(1); // and send an up for the first point m_seatInterface->touchUp(0); m_seatInterface->touchFrame(); 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->setFocusedTouchSurface(serverSurface, QPointF(15, 26)); m_seatInterface->setTimestamp(7); QCOMPARE(m_seatInterface->touchDown(QPointF(15, 26)), 0); m_seatInterface->touchFrame(); m_seatInterface->cancelTouchSequence(); 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)); // destroy touch on client side QSignalSpy unboundSpy(serverTouch, &TouchInterface::unbound); QVERIFY(unboundSpy.isValid()); QSignalSpy destroyedSpy(serverTouch, &TouchInterface::destroyed); QVERIFY(destroyedSpy.isValid()); delete touch; QVERIFY(unboundSpy.wait()); QCOMPARE(unboundSpy.count(), 1); QCOMPARE(destroyedSpy.count(), 0); QVERIFY(!serverTouch->resource()); // try to call into all the the methods of the touch interface, should not crash QCOMPARE(m_seatInterface->focusedTouch(), serverTouch); m_seatInterface->setTimestamp(8); QCOMPARE(m_seatInterface->touchDown(QPointF(15, 26)), 0); m_seatInterface->touchFrame(); m_seatInterface->touchMove(0, QPointF(0, 0)); QCOMPARE(m_seatInterface->touchDown(QPointF(15, 26)), 1); m_seatInterface->cancelTouchSequence(); QVERIFY(destroyedSpy.wait()); QCOMPARE(destroyedSpy.count(), 1); // should have unset the focused touch QVERIFY(!m_seatInterface->focusedTouch()); // but not the focused touch surface QCOMPARE(m_seatInterface->focusedTouchSurface(), serverSurface); } void TestWaylandSeat::testDisconnect() { // this test verifies that disconnecting the client cleans up correctly using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy keyboardCreatedSpy(m_seatInterface, &SeatInterface::keyboardCreated); QVERIFY(keyboardCreatedSpy.isValid()); QSignalSpy pointerCreatedSpy(m_seatInterface, &SeatInterface::pointerCreated); QVERIFY(pointerCreatedSpy.isValid()); QSignalSpy touchCreatedSpy(m_seatInterface, &SeatInterface::touchCreated); QVERIFY(touchCreatedSpy.isValid()); // create the things we need m_seatInterface->setHasKeyboard(true); m_seatInterface->setHasPointer(true); m_seatInterface->setHasTouch(true); QSignalSpy touchSpy(m_seat, &Seat::hasTouchChanged); QVERIFY(touchSpy.isValid()); QVERIFY(touchSpy.wait()); QScopedPointer keyboard(m_seat->createKeyboard()); QVERIFY(!keyboard.isNull()); QVERIFY(keyboardCreatedSpy.wait()); auto serverKeyboard = keyboardCreatedSpy.first().first().value(); QVERIFY(serverKeyboard); QScopedPointer pointer(m_seat->createPointer()); QVERIFY(!pointer.isNull()); QVERIFY(pointerCreatedSpy.wait()); auto serverPointer = pointerCreatedSpy.first().first().value(); QVERIFY(serverPointer); QScopedPointer touch(m_seat->createTouch()); QVERIFY(!touch.isNull()); QVERIFY(touchCreatedSpy.wait()); auto serverTouch = touchCreatedSpy.first().first().value(); QVERIFY(serverTouch); // setup destroys QSignalSpy keyboardDestroyedSpy(serverKeyboard, &QObject::destroyed); QVERIFY(keyboardDestroyedSpy.isValid()); QSignalSpy pointerDestroyedSpy(serverPointer, &QObject::destroyed); QVERIFY(pointerDestroyedSpy.isValid()); QSignalSpy touchDestroyedSpy(serverTouch, &QObject::destroyed); QVERIFY(touchDestroyedSpy.isValid()); QSignalSpy clientDisconnectedSpy(serverKeyboard->client(), &ClientConnection::disconnected); QVERIFY(clientDisconnectedSpy.isValid()); if (m_connection) { m_connection->deleteLater(); m_connection = nullptr; } QVERIFY(clientDisconnectedSpy.wait()); QCOMPARE(clientDisconnectedSpy.count(), 1); QCOMPARE(keyboardDestroyedSpy.count(), 0); QCOMPARE(pointerDestroyedSpy.count(), 0); QCOMPARE(touchDestroyedSpy.count(), 0); QVERIFY(keyboardDestroyedSpy.wait()); QCOMPARE(keyboardDestroyedSpy.count(), 1); QCOMPARE(pointerDestroyedSpy.count(), 1); QCOMPARE(touchDestroyedSpy.count(), 1); keyboard->destroy(); pointer->destroy(); touch->destroy(); m_relativePointerManager->destroy(); m_pointerGestures->destroy(); m_compositor->destroy(); m_seat->destroy(); m_shm->destroy(); m_subCompositor->destroy(); m_queue->destroy(); } void TestWaylandSeat::testPointerEnterOnUnboundSurface() { using namespace KWayland::Client; using namespace KWayland::Server; // create the things we need m_seatInterface->setHasKeyboard(true); m_seatInterface->setHasPointer(true); m_seatInterface->setHasTouch(true); QSignalSpy pointerChangedSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(pointerChangedSpy.isValid()); QVERIFY(pointerChangedSpy.wait()); // create pointer and Surface QScopedPointer pointer(m_seat->createPointer()); QVERIFY(!pointer.isNull()); // create the surface QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QScopedPointer s(m_compositor->createSurface()); QVERIFY(surfaceCreatedSpy.wait()); SurfaceInterface *serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); // unbind the surface again QSignalSpy surfaceUnboundSpy(serverSurface, &SurfaceInterface::unbound); QVERIFY(surfaceUnboundSpy.isValid()); s.reset(); QVERIFY(surfaceUnboundSpy.wait()); QSignalSpy clientErrorSpy(m_connection, &ConnectionThread::errorOccurred); QVERIFY(clientErrorSpy.isValid()); m_seatInterface->setFocusedPointerSurface(serverSurface); QVERIFY(!clientErrorSpy.wait(100)); } QTEST_GUILESS_MAIN(TestWaylandSeat) #include "test_wayland_seat.moc" diff --git a/autotests/client/test_wayland_surface.cpp b/autotests/client/test_wayland_surface.cpp index 476c72c..ee522a6 100644 --- a/autotests/client/test_wayland_surface.cpp +++ b/autotests/client/test_wayland_surface.cpp @@ -1,1172 +1,1172 @@ /******************************************************************** Copyright 2014 Martin Gräßlin 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 . *********************************************************************/ // Qt #include #include #include // KWin #include "../../src/client/compositor.h" #include "../../src/client/connection_thread.h" #include "../../src/client/event_queue.h" #include "../../src/client/idleinhibit.h" #include "../../src/client/output.h" #include "../../src/client/surface.h" #include "../../src/client/region.h" #include "../../src/client/registry.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/idleinhibit_interface.h" #include "../../src/server/surface_interface.h" // Wayland #include using KWayland::Client::Registry; class TestWaylandSurface : public QObject { Q_OBJECT public: explicit TestWaylandSurface(QObject *parent = nullptr); private Q_SLOTS: void init(); void cleanup(); void testStaticAccessor(); void testDamage(); void testFrameCallback(); void testAttachBuffer(); void testMultipleSurfaces(); void testOpaque(); void testInput(); void testScale(); void testDestroy(); void testUnmapOfNotMappedSurface(); void testDamageTracking(); void testSurfaceAt(); void testDestroyAttachedBuffer(); void testDestroyWithPendingCallback(); void testOutput(); void testDisconnect(); void testInhibit(); private: KWayland::Server::Display *m_display; KWayland::Server::CompositorInterface *m_compositorInterface; KWayland::Server::IdleInhibitManagerInterface *m_idleInhibitInterface; KWayland::Client::ConnectionThread *m_connection; KWayland::Client::Compositor *m_compositor; KWayland::Client::ShmPool *m_shm; KWayland::Client::EventQueue *m_queue; KWayland::Client::IdleInhibitManager *m_idleInhibitManager; QThread *m_thread; }; static const QString s_socketName = QStringLiteral("kwin-test-wayland-surface-0"); TestWaylandSurface::TestWaylandSurface(QObject *parent) : QObject(parent) , m_display(nullptr) , m_compositorInterface(nullptr) , m_connection(nullptr) , m_compositor(nullptr) , m_thread(nullptr) { } void TestWaylandSurface::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_display->createShm(); m_compositorInterface = m_display->createCompositor(m_display); QVERIFY(m_compositorInterface); m_compositorInterface->create(); QVERIFY(m_compositorInterface->isValid()); m_idleInhibitInterface = m_display->createIdleInhibitManager(IdleInhibitManagerInterfaceVersion::UnstableV1, m_display); QVERIFY(m_idleInhibitInterface); m_idleInhibitInterface->create(); QVERIFY(m_idleInhibitInterface->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); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); KWayland::Client::Registry registry; registry.setEventQueue(m_queue); QSignalSpy compositorSpy(®istry, SIGNAL(compositorAnnounced(quint32,quint32))); QSignalSpy shmSpy(®istry, SIGNAL(shmAnnounced(quint32,quint32))); QSignalSpy allAnnounced(®istry, SIGNAL(interfacesAnnounced())); QVERIFY(allAnnounced.isValid()); QVERIFY(shmSpy.isValid()); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QVERIFY(!compositorSpy.isEmpty()); QVERIFY(!shmSpy.isEmpty()); m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); QVERIFY(m_compositor->isValid()); m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); QVERIFY(m_shm->isValid()); m_idleInhibitManager = registry.createIdleInhibitManager(registry.interface(Registry::Interface::IdleInhibitManagerUnstableV1).name, registry.interface(Registry::Interface::IdleInhibitManagerUnstableV1).version, this); QVERIFY(m_idleInhibitManager->isValid()); } void TestWaylandSurface::cleanup() { if (m_compositor) { delete m_compositor; m_compositor = nullptr; } if (m_idleInhibitManager) { delete m_idleInhibitManager; m_idleInhibitManager = nullptr; } if (m_shm) { delete m_shm; m_shm = nullptr; } if (m_queue) { delete m_queue; m_queue = 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_idleInhibitInterface; m_idleInhibitInterface = nullptr; delete m_display; m_display = nullptr; } void TestWaylandSurface::testStaticAccessor() { QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); QVERIFY(!KWayland::Server::SurfaceInterface::get(nullptr)); QVERIFY(!KWayland::Server::SurfaceInterface::get(1, nullptr)); QVERIFY(KWayland::Client::Surface::all().isEmpty()); KWayland::Client::Surface *s1 = m_compositor->createSurface(); QVERIFY(s1->isValid()); QCOMPARE(KWayland::Client::Surface::all().count(), 1); QCOMPARE(KWayland::Client::Surface::all().first(), s1); QCOMPARE(KWayland::Client::Surface::get(*s1), s1); QVERIFY(serverSurfaceCreated.wait()); auto serverSurface1 = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface1); QCOMPARE(KWayland::Server::SurfaceInterface::get(serverSurface1->resource()), serverSurface1); QCOMPARE(KWayland::Server::SurfaceInterface::get(serverSurface1->id(), serverSurface1->client()), serverSurface1); QVERIFY(!s1->size().isValid()); QSignalSpy sizeChangedSpy(s1, SIGNAL(sizeChanged(QSize))); QVERIFY(sizeChangedSpy.isValid()); const QSize testSize(200, 300); s1->setSize(testSize); QCOMPARE(s1->size(), testSize); QCOMPARE(sizeChangedSpy.count(), 1); QCOMPARE(sizeChangedSpy.first().first().toSize(), testSize); // add another surface KWayland::Client::Surface *s2 = m_compositor->createSurface(); QVERIFY(s2->isValid()); QCOMPARE(KWayland::Client::Surface::all().count(), 2); QCOMPARE(KWayland::Client::Surface::all().first(), s1); QCOMPARE(KWayland::Client::Surface::all().last(), s2); QCOMPARE(KWayland::Client::Surface::get(*s1), s1); QCOMPARE(KWayland::Client::Surface::get(*s2), s2); serverSurfaceCreated.clear(); QVERIFY(serverSurfaceCreated.wait()); auto serverSurface2 = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface2); QCOMPARE(KWayland::Server::SurfaceInterface::get(serverSurface1->resource()), serverSurface1); QCOMPARE(KWayland::Server::SurfaceInterface::get(serverSurface1->id(), serverSurface1->client()), serverSurface1); QCOMPARE(KWayland::Server::SurfaceInterface::get(serverSurface2->resource()), serverSurface2); QCOMPARE(KWayland::Server::SurfaceInterface::get(serverSurface2->id(), serverSurface2->client()), serverSurface2); // delete s2 again delete s2; QCOMPARE(KWayland::Client::Surface::all().count(), 1); QCOMPARE(KWayland::Client::Surface::all().first(), s1); QCOMPARE(KWayland::Client::Surface::get(*s1), s1); // and finally delete the last one delete s1; QVERIFY(KWayland::Client::Surface::all().isEmpty()); QVERIFY(!KWayland::Client::Surface::get(nullptr)); QSignalSpy unboundSpy(serverSurface1, &KWayland::Server::Resource::unbound); QVERIFY(unboundSpy.isValid()); QVERIFY(unboundSpy.wait()); QVERIFY(!KWayland::Server::SurfaceInterface::get(nullptr)); QVERIFY(!KWayland::Server::SurfaceInterface::get(1, nullptr)); } void TestWaylandSurface::testDamage() { QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); KWayland::Client::Surface *s = m_compositor->createSurface(); QVERIFY(serverSurfaceCreated.wait()); KWayland::Server::SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface); QCOMPARE(serverSurface->damage(), QRegion()); QVERIFY(serverSurface->parentResource()); QVERIFY(!serverSurface->isMapped()); QSignalSpy damageSpy(serverSurface, SIGNAL(damaged(QRegion))); QVERIFY(damageSpy.isValid()); // send damage without a buffer s->damage(QRect(0, 0, 100, 100)); s->commit(KWayland::Client::Surface::CommitFlag::None); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCoreApplication::processEvents(); QVERIFY(damageSpy.isEmpty()); QVERIFY(!serverSurface->isMapped()); QImage img(QSize(10, 10), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::black); auto b = m_shm->createBuffer(img); s->attachBuffer(b); s->damage(QRect(0, 0, 10, 10)); s->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(damageSpy.wait()); QCOMPARE(serverSurface->damage(), QRegion(0, 0, 10, 10)); QCOMPARE(damageSpy.first().first().value(), QRegion(0, 0, 10, 10)); QVERIFY(serverSurface->isMapped()); // damage multiple times QRegion testRegion(5, 8, 3, 6); testRegion = testRegion.united(QRect(10, 20, 30, 15)); img = QImage(QSize(40, 35), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::black); b = m_shm->createBuffer(img); s->attachBuffer(b); s->damage(testRegion); damageSpy.clear(); s->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(damageSpy.wait()); QCOMPARE(serverSurface->damage(), testRegion); QCOMPARE(damageSpy.first().first().value(), testRegion); QVERIFY(serverSurface->isMapped()); } void TestWaylandSurface::testFrameCallback() { QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); KWayland::Client::Surface *s = m_compositor->createSurface(); QVERIFY(serverSurfaceCreated.wait()); KWayland::Server::SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface); QSignalSpy damageSpy(serverSurface, SIGNAL(damaged(QRegion))); QVERIFY(damageSpy.isValid()); QSignalSpy frameRenderedSpy(s, SIGNAL(frameRendered())); QVERIFY(frameRenderedSpy.isValid()); QImage img(QSize(10, 10), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::black); auto b = m_shm->createBuffer(img); s->attachBuffer(b); s->damage(QRect(0, 0, 10, 10)); s->commit(); QVERIFY(damageSpy.wait()); serverSurface->frameRendered(10); QVERIFY(frameRenderedSpy.isEmpty()); QVERIFY(frameRenderedSpy.wait()); QVERIFY(!frameRenderedSpy.isEmpty()); } void TestWaylandSurface::testAttachBuffer() { // create the surface QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); KWayland::Client::Surface *s = m_compositor->createSurface(); QVERIFY(serverSurfaceCreated.wait()); KWayland::Server::SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface); // create three images QImage black(24, 24, QImage::Format_RGB32); black.fill(Qt::black); QImage red(24, 24, QImage::Format_ARGB32); //Note - deliberately not premultiplied red.fill(QColor(255, 0, 0, 128)); QImage blue(24, 24, QImage::Format_ARGB32_Premultiplied); blue.fill(QColor(0, 0, 255, 128)); wl_buffer *blackBuffer = *(m_shm->createBuffer(black).data()); auto redBuffer = m_shm->createBuffer(red); auto blueBuffer = m_shm->createBuffer(blue).toStrongRef(); QCOMPARE(blueBuffer->format(), KWayland::Client::Buffer::Format::ARGB32); QCOMPARE(blueBuffer->size(), blue.size()); QVERIFY(!blueBuffer->isReleased()); QVERIFY(!blueBuffer->isUsed()); QCOMPARE(blueBuffer->stride(), blue.bytesPerLine()); s->attachBuffer(redBuffer.data()); s->attachBuffer(blackBuffer); s->damage(QRect(0, 0, 24, 24)); s->commit(KWayland::Client::Surface::CommitFlag::None); QSignalSpy damageSpy(serverSurface, SIGNAL(damaged(QRegion))); QVERIFY(damageSpy.isValid()); QSignalSpy unmappedSpy(serverSurface, SIGNAL(unmapped())); QVERIFY(unmappedSpy.isValid()); QVERIFY(damageSpy.wait()); QVERIFY(unmappedSpy.isEmpty()); // now the ServerSurface should have the black image attached as a buffer KWayland::Server::BufferInterface *buffer = serverSurface->buffer(); buffer->ref(); QVERIFY(buffer->shmBuffer()); QCOMPARE(buffer->data(), black); QCOMPARE(buffer->data().format(), QImage::Format_RGB32); // render another frame s->attachBuffer(redBuffer); s->damage(QRect(0, 0, 24, 24)); s->commit(KWayland::Client::Surface::CommitFlag::None); damageSpy.clear(); QVERIFY(damageSpy.wait()); QVERIFY(unmappedSpy.isEmpty()); KWayland::Server::BufferInterface *buffer2 = serverSurface->buffer(); buffer2->ref(); QVERIFY(buffer2->shmBuffer()); QCOMPARE(buffer2->data().format(), QImage::Format_ARGB32_Premultiplied); QCOMPARE(buffer2->data().width(), 24); QCOMPARE(buffer2->data().height(), 24); for (int i = 0; i < 24; ++i) { for (int j = 0; j < 24; ++j) { // it's premultiplied in the format QCOMPARE(buffer2->data().pixel(i, j), qRgba(128, 0, 0, 128)); } } buffer2->unref(); QVERIFY(buffer2->isReferenced()); QVERIFY(!redBuffer.data()->isReleased()); // render another frame blueBuffer->setUsed(true); QVERIFY(blueBuffer->isUsed()); s->attachBuffer(blueBuffer.data()); s->damage(QRect(0, 0, 24, 24)); QSignalSpy frameRenderedSpy(s, SIGNAL(frameRendered())); QVERIFY(frameRenderedSpy.isValid()); s->commit(); damageSpy.clear(); QVERIFY(damageSpy.wait()); QVERIFY(unmappedSpy.isEmpty()); QVERIFY(!buffer2->isReferenced()); delete buffer2; // TODO: we should have a signal on when the Buffer gets released QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); if (!redBuffer.data()->isReleased()) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } QVERIFY(redBuffer.data()->isReleased()); KWayland::Server::BufferInterface *buffer3 = serverSurface->buffer(); buffer3->ref(); QVERIFY(buffer3->shmBuffer()); QCOMPARE(buffer3->data().format(), QImage::Format_ARGB32_Premultiplied); QCOMPARE(buffer3->data().width(), 24); QCOMPARE(buffer3->data().height(), 24); for (int i = 0; i < 24; ++i) { for (int j = 0; j < 24; ++j) { // it's premultiplied in the format QCOMPARE(buffer3->data().pixel(i, j), qRgba(0, 0, 128, 128)); } } buffer3->unref(); QVERIFY(buffer3->isReferenced()); serverSurface->frameRendered(1); QVERIFY(frameRenderedSpy.wait()); // commit a different value shouldn't change our buffer QCOMPARE(serverSurface->buffer(), buffer3); QVERIFY(serverSurface->input().isNull()); damageSpy.clear(); s->setInputRegion(m_compositor->createRegion(QRegion(0, 0, 24, 24)).get()); s->commit(KWayland::Client::Surface::CommitFlag::None); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCoreApplication::processEvents(); QCOMPARE(serverSurface->input(), QRegion(0, 0, 24, 24)); QCOMPARE(serverSurface->buffer(), buffer3); QVERIFY(damageSpy.isEmpty()); QVERIFY(unmappedSpy.isEmpty()); QVERIFY(serverSurface->isMapped()); // clear the surface s->attachBuffer(blackBuffer); s->damage(QRect(0, 0, 1, 1)); // TODO: better method s->attachBuffer((wl_buffer*)nullptr); s->damage(QRect(0, 0, 10, 10)); s->commit(KWayland::Client::Surface::CommitFlag::None); QVERIFY(unmappedSpy.wait()); QVERIFY(!unmappedSpy.isEmpty()); QCOMPARE(unmappedSpy.count(), 1); QVERIFY(damageSpy.isEmpty()); QVERIFY(!serverSurface->isMapped()); // TODO: add signal test on release buffer->unref(); } void TestWaylandSurface::testMultipleSurfaces() { using namespace KWayland::Client; using namespace KWayland::Server; Registry registry; QSignalSpy shmSpy(®istry, SIGNAL(shmAnnounced(quint32,quint32))); registry.create(m_connection->display()); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(shmSpy.wait()); ShmPool pool1; ShmPool pool2; pool1.setup(registry.bindShm(shmSpy.first().first().value(), shmSpy.first().last().value())); pool2.setup(registry.bindShm(shmSpy.first().first().value(), shmSpy.first().last().value())); QVERIFY(pool1.isValid()); QVERIFY(pool2.isValid()); // create the surfaces QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); QScopedPointer s1(m_compositor->createSurface()); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface1 = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface1); //second surface QScopedPointer s2(m_compositor->createSurface()); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface2 = serverSurfaceCreated.last().first().value(); QVERIFY(serverSurface2); QVERIFY(serverSurface1->resource() != serverSurface2->resource()); // create two images QImage black(24, 24, QImage::Format_RGB32); black.fill(Qt::black); QImage red(24, 24, QImage::Format_ARGB32_Premultiplied); red.fill(QColor(255, 0, 0, 128)); auto blackBuffer = pool1.createBuffer(black); auto redBuffer = pool2.createBuffer(red); s1->attachBuffer(blackBuffer); s1->damage(QRect(0, 0, 24, 24)); s1->commit(Surface::CommitFlag::None); QSignalSpy damageSpy1(serverSurface1, SIGNAL(damaged(QRegion))); QVERIFY(damageSpy1.isValid()); QVERIFY(damageSpy1.wait()); // now the ServerSurface should have the black image attached as a buffer BufferInterface *buffer1 = serverSurface1->buffer(); QVERIFY(buffer1); QImage buffer1Data = buffer1->data(); QCOMPARE(buffer1Data, black); // accessing the same buffer is OK QImage buffer1Data2 = buffer1->data(); QCOMPARE(buffer1Data2, buffer1Data); buffer1Data = QImage(); QVERIFY(buffer1Data.isNull()); buffer1Data2 = QImage(); QVERIFY(buffer1Data2.isNull()); // attach a buffer for the other surface s2->attachBuffer(redBuffer); s2->damage(QRect(0, 0, 24, 24)); s2->commit(Surface::CommitFlag::None); QSignalSpy damageSpy2(serverSurface2, SIGNAL(damaged(QRegion))); QVERIFY(damageSpy2.isValid()); QVERIFY(damageSpy2.wait()); BufferInterface *buffer2 = serverSurface2->buffer(); QVERIFY(buffer2); QImage buffer2Data = buffer2->data(); QCOMPARE(buffer2Data, red); // while buffer2 is accessed we cannot access buffer1 buffer1Data = buffer1->data(); QVERIFY(buffer1Data.isNull()); // a deep copy can be kept around QImage deepCopy = buffer2Data.copy(); QCOMPARE(deepCopy, red); buffer2Data = QImage(); QVERIFY(buffer2Data.isNull()); QCOMPARE(deepCopy, red); // now that buffer2Data is destroyed we can access buffer1 again buffer1Data = buffer1->data(); QVERIFY(!buffer1Data.isNull()); QCOMPARE(buffer1Data, black); } void TestWaylandSurface::testOpaque() { using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); Surface *s = m_compositor->createSurface(); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface); QSignalSpy opaqueRegionChangedSpy(serverSurface, SIGNAL(opaqueChanged(QRegion))); QVERIFY(opaqueRegionChangedSpy.isValid()); // by default there should be an empty opaque region QCOMPARE(serverSurface->opaque(), QRegion()); // let's install an opaque region s->setOpaqueRegion(m_compositor->createRegion(QRegion(0, 10, 20, 30)).get()); // the region should only be applied after the surface got committed wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCOMPARE(serverSurface->opaque(), QRegion()); QCOMPARE(opaqueRegionChangedSpy.count(), 0); // so let's commit to get the new region s->commit(Surface::CommitFlag::None); QVERIFY(opaqueRegionChangedSpy.wait()); QCOMPARE(opaqueRegionChangedSpy.count(), 1); QCOMPARE(opaqueRegionChangedSpy.last().first().value(), QRegion(0, 10, 20, 30)); QCOMPARE(serverSurface->opaque(), QRegion(0, 10, 20, 30)); // committing without setting a new region shouldn't change s->commit(Surface::CommitFlag::None); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCOMPARE(opaqueRegionChangedSpy.count(), 1); QCOMPARE(serverSurface->opaque(), QRegion(0, 10, 20, 30)); // let's change the opaque region s->setOpaqueRegion(m_compositor->createRegion(QRegion(10, 20, 30, 40)).get()); s->commit(Surface::CommitFlag::None); QVERIFY(opaqueRegionChangedSpy.wait()); QCOMPARE(opaqueRegionChangedSpy.count(), 2); QCOMPARE(opaqueRegionChangedSpy.last().first().value(), QRegion(10, 20, 30, 40)); QCOMPARE(serverSurface->opaque(), QRegion(10, 20, 30, 40)); // and let's go back to an empty region s->setOpaqueRegion(); s->commit(Surface::CommitFlag::None); QVERIFY(opaqueRegionChangedSpy.wait()); QCOMPARE(opaqueRegionChangedSpy.count(), 3); QCOMPARE(opaqueRegionChangedSpy.last().first().value(), QRegion()); QCOMPARE(serverSurface->opaque(), QRegion()); } void TestWaylandSurface::testInput() { using namespace KWayland::Client; using namespace KWayland::Server; QSignalSpy serverSurfaceCreated(m_compositorInterface, SIGNAL(surfaceCreated(KWayland::Server::SurfaceInterface*))); QVERIFY(serverSurfaceCreated.isValid()); Surface *s = m_compositor->createSurface(); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface); QSignalSpy inputRegionChangedSpy(serverSurface, SIGNAL(inputChanged(QRegion))); QVERIFY(inputRegionChangedSpy.isValid()); // by default there should be an empty == infinite input region QCOMPARE(serverSurface->input(), QRegion()); QCOMPARE(serverSurface->inputIsInfinite(), true); // let's install an input region s->setInputRegion(m_compositor->createRegion(QRegion(0, 10, 20, 30)).get()); // the region should only be applied after the surface got committed wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCOMPARE(serverSurface->input(), QRegion()); QCOMPARE(serverSurface->inputIsInfinite(), true); QCOMPARE(inputRegionChangedSpy.count(), 0); // so let's commit to get the new region s->commit(Surface::CommitFlag::None); QVERIFY(inputRegionChangedSpy.wait()); QCOMPARE(inputRegionChangedSpy.count(), 1); QCOMPARE(inputRegionChangedSpy.last().first().value(), QRegion(0, 10, 20, 30)); QCOMPARE(serverSurface->input(), QRegion(0, 10, 20, 30)); QCOMPARE(serverSurface->inputIsInfinite(), false); // committing without setting a new region shouldn't change s->commit(Surface::CommitFlag::None); wl_display_flush(m_connection->display()); QCoreApplication::processEvents(); QCOMPARE(inputRegionChangedSpy.count(), 1); QCOMPARE(serverSurface->input(), QRegion(0, 10, 20, 30)); QCOMPARE(serverSurface->inputIsInfinite(), false); // let's change the input region s->setInputRegion(m_compositor->createRegion(QRegion(10, 20, 30, 40)).get()); s->commit(Surface::CommitFlag::None); QVERIFY(inputRegionChangedSpy.wait()); QCOMPARE(inputRegionChangedSpy.count(), 2); QCOMPARE(inputRegionChangedSpy.last().first().value(), QRegion(10, 20, 30, 40)); QCOMPARE(serverSurface->input(), QRegion(10, 20, 30, 40)); QCOMPARE(serverSurface->inputIsInfinite(), false); // and let's go back to an empty region s->setInputRegion(); s->commit(Surface::CommitFlag::None); QVERIFY(inputRegionChangedSpy.wait()); QCOMPARE(inputRegionChangedSpy.count(), 3); QCOMPARE(inputRegionChangedSpy.last().first().value(), QRegion()); QCOMPARE(serverSurface->input(), QRegion()); QCOMPARE(serverSurface->inputIsInfinite(), true); } void TestWaylandSurface::testScale() { // this test verifies that updating the scale factor is correctly passed to the Wayland server using namespace KWayland::Client; using namespace KWayland::Server; // create surface QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(serverSurfaceCreated.isValid()); QScopedPointer s(m_compositor->createSurface()); QCOMPARE(s->scale(), 1); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QVERIFY(serverSurface); QCOMPARE(serverSurface->scale(), 1); // let's change the scale factor QSignalSpy scaleChangedSpy(serverSurface, &SurfaceInterface::scaleChanged); //changing the scale implicitly changes the size QSignalSpy sizeChangedSpy(serverSurface, &SurfaceInterface::sizeChanged); QVERIFY(scaleChangedSpy.isValid()); s->setScale(2); QCOMPARE(s->scale(), 2); // needs a commit QVERIFY(!scaleChangedSpy.wait(100)); s->commit(Surface::CommitFlag::None); QVERIFY(scaleChangedSpy.wait()); QCOMPARE(scaleChangedSpy.count(), 1); QCOMPARE(scaleChangedSpy.first().first().toInt(), 2); QCOMPARE(serverSurface->scale(), 2); //even though we've changed the scale, if we don't have a buffer we //don't have a size. If we don't have a size it can't have changed QCOMPARE(sizeChangedSpy.count(), 0); QVERIFY(!serverSurface->size().isValid()); // let's try changing to same factor, should not emit changed on server s->setScale(2); s->commit(Surface::CommitFlag::None); QVERIFY(!scaleChangedSpy.wait(100)); // but changing to a different value should still work s->setScale(4); s->commit(Surface::CommitFlag::None); QVERIFY(scaleChangedSpy.wait()); QCOMPARE(scaleChangedSpy.count(), 2); QCOMPARE(scaleChangedSpy.first().first().toInt(), 2); QCOMPARE(scaleChangedSpy.last().first().toInt(), 4); QCOMPARE(serverSurface->scale(), 4); scaleChangedSpy.clear(); //attach a buffer of 100x100, our scale is 4, so this should be a size of 25x25 QImage red(100, 100, QImage::Format_ARGB32_Premultiplied); red.fill(QColor(255, 0, 0, 128)); auto redBuffer = m_shm->createBuffer(red); s->attachBuffer(redBuffer.data()); s->damage(QRect(0,0, 25,25)); s->commit(Surface::CommitFlag::None); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(sizeChangedSpy.count(), 1); QCOMPARE(serverSurface->size(), QSize(25,25)); sizeChangedSpy.clear(); scaleChangedSpy.clear(); //set the scale to 1, buffer is still 100x100 so size should change to 100x100 s->setScale(1); s->commit(Surface::CommitFlag::None); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(sizeChangedSpy.count(), 1); QCOMPARE(scaleChangedSpy.count(), 1); QCOMPARE(serverSurface->scale(), 1); QCOMPARE(serverSurface->size(), QSize(100,100)); sizeChangedSpy.clear(); scaleChangedSpy.clear(); //set scale and size in one commit, buffer is 50x50 at scale 2 so size should be 25x25 QImage blue(50, 50, QImage::Format_ARGB32_Premultiplied); red.fill(QColor(255, 0, 0, 128)); auto blueBuffer = m_shm->createBuffer(blue); s->attachBuffer(blueBuffer.data()); s->setScale(2); s->commit(Surface::CommitFlag::None); QVERIFY(sizeChangedSpy.wait()); QCOMPARE(sizeChangedSpy.count(), 1); QCOMPARE(scaleChangedSpy.count(), 1); QCOMPARE(serverSurface->scale(), 2); QCOMPARE(serverSurface->size(), QSize(25,25)); } void TestWaylandSurface::testDestroy() { using namespace KWayland::Client; Surface *s = m_compositor->createSurface(); connect(m_connection, &ConnectionThread::connectionDied, s, &Surface::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_compositor, &Compositor::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_shm, &ShmPool::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_queue, &EventQueue::destroy); connect(m_connection, &ConnectionThread::connectionDied, m_idleInhibitManager, &IdleInhibitManager::destroy); QVERIFY(s->isValid()); QSignalSpy connectionDiedSpy(m_connection, SIGNAL(connectionDied())); QVERIFY(connectionDiedSpy.isValid()); delete m_compositorInterface; m_compositorInterface = nullptr; delete m_idleInhibitInterface; m_idleInhibitInterface = nullptr; delete m_display; m_display = nullptr; QVERIFY(connectionDiedSpy.wait()); // now the Surface should be destroyed; QVERIFY(!s->isValid()); // calling destroy again should not fail s->destroy(); } void TestWaylandSurface::testUnmapOfNotMappedSurface() { // this test verifies that a surface which doesn't have a buffer attached doesn't trigger the unmapped signal using namespace KWayland::Client; using namespace KWayland::Server; // create surface QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(serverSurfaceCreated.isValid()); QScopedPointer s(m_compositor->createSurface()); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); QSignalSpy unmappedSpy(serverSurface, &SurfaceInterface::unmapped); QVERIFY(unmappedSpy.isValid()); QSignalSpy scaleChanged(serverSurface, &SurfaceInterface::scaleChanged); // let's map a null buffer and change scale to trigger a signal we can wait for s->attachBuffer(Buffer::Ptr()); s->setScale(2); s->commit(Surface::CommitFlag::None); QVERIFY(scaleChanged.wait()); QVERIFY(unmappedSpy.isEmpty()); } void TestWaylandSurface::testDamageTracking() { // this tests the damage tracking feature using namespace KWayland::Client; using namespace KWayland::Server; // create surface QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(serverSurfaceCreated.isValid()); QScopedPointer s(m_compositor->createSurface()); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); // before first commit, the tracked damage should be empty QVERIFY(serverSurface->trackedDamage().isEmpty()); // Now let's damage the surface QSignalSpy damagedSpy(serverSurface, &SurfaceInterface::damaged); QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::red); s->attachBuffer(m_shm->createBuffer(image)); s->damage(QRect(0, 0, 100, 100)); s->commit(Surface::CommitFlag::None); QVERIFY(damagedSpy.wait()); QCOMPARE(serverSurface->trackedDamage(), QRegion(0, 0, 100, 100)); QCOMPARE(serverSurface->damage(), QRegion(0, 0, 100, 100)); // resetting the tracked damage should empty it serverSurface->resetTrackedDamage(); QVERIFY(serverSurface->trackedDamage().isEmpty()); // but not affect the actual damage QCOMPARE(serverSurface->damage(), QRegion(0, 0, 100, 100)); // let's damage some parts of the surface QPainter p; p.begin(&image); p.fillRect(QRect(0, 0, 10, 10), Qt::blue); p.end(); s->attachBuffer(m_shm->createBuffer(image)); s->damage(QRect(0, 0, 10, 10)); s->commit(Surface::CommitFlag::None); QVERIFY(damagedSpy.wait()); QCOMPARE(serverSurface->trackedDamage(), QRegion(0, 0, 10, 10)); QCOMPARE(serverSurface->damage(), QRegion(0, 0, 10, 10)); // and damage some part completely not bounding to the current damage region p.begin(&image); p.fillRect(QRect(50, 40, 20, 30), Qt::blue); p.end(); s->attachBuffer(m_shm->createBuffer(image)); s->damage(QRect(50, 40, 20, 30)); s->commit(Surface::CommitFlag::None); QVERIFY(damagedSpy.wait()); QCOMPARE(serverSurface->trackedDamage(), QRegion(0, 0, 10, 10).united(QRegion(50, 40, 20, 30))); QCOMPARE(serverSurface->trackedDamage().rectCount(), 2); QCOMPARE(serverSurface->damage(), QRegion(50, 40, 20, 30)); // now let's reset the tracked damage again serverSurface->resetTrackedDamage(); QVERIFY(serverSurface->trackedDamage().isEmpty()); // but not affect the actual damage QCOMPARE(serverSurface->damage(), QRegion(50, 40, 20, 30)); } void TestWaylandSurface::testSurfaceAt() { // this test verifies that surfaceAt(const QPointF&) works as expected for the case of no children using namespace KWayland::Client; using namespace KWayland::Server; // create surface QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(serverSurfaceCreated.isValid()); QScopedPointer s(m_compositor->createSurface()); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); // a newly created surface should not be mapped and not provide a surface at a position QVERIFY(!serverSurface->isMapped()); QVERIFY(!serverSurface->surfaceAt(QPointF(0, 0))); // let's damage this surface QSignalSpy sizeChangedSpy(serverSurface, &SurfaceInterface::sizeChanged); QVERIFY(sizeChangedSpy.isValid()); QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::red); s->attachBuffer(m_shm->createBuffer(image)); s->damage(QRect(0, 0, 100, 100)); s->commit(Surface::CommitFlag::None); QVERIFY(sizeChangedSpy.wait()); // now the surface is mapped and surfaceAt should give the surface QVERIFY(serverSurface->isMapped()); QCOMPARE(serverSurface->surfaceAt(QPointF(0, 0)), serverSurface); QCOMPARE(serverSurface->surfaceAt(QPointF(100, 100)), serverSurface); // outside the geometry it should not give a surface QVERIFY(!serverSurface->surfaceAt(QPointF(101, 101))); QVERIFY(!serverSurface->surfaceAt(QPointF(-1, -1))); } void TestWaylandSurface::testDestroyAttachedBuffer() { // this test verifies that destroying of a buffer attached to a surface works using namespace KWayland::Client; using namespace KWayland::Server; // create surface QSignalSpy serverSurfaceCreated(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(serverSurfaceCreated.isValid()); QScopedPointer s(m_compositor->createSurface()); QVERIFY(serverSurfaceCreated.wait()); SurfaceInterface *serverSurface = serverSurfaceCreated.first().first().value(); // let's damage this surface QSignalSpy damagedSpy(serverSurface, &SurfaceInterface::damaged); QVERIFY(damagedSpy.isValid()); QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::red); s->attachBuffer(m_shm->createBuffer(image)); s->damage(QRect(0, 0, 100, 100)); s->commit(Surface::CommitFlag::None); QVERIFY(damagedSpy.wait()); QVERIFY(serverSurface->buffer()); // attach another buffer image.fill(Qt::blue); s->attachBuffer(m_shm->createBuffer(image)); m_connection->flush(); // Let's try to destroy it QSignalSpy destroySpy(serverSurface->buffer(), &BufferInterface::aboutToBeDestroyed); QVERIFY(destroySpy.isValid()); delete m_shm; m_shm = nullptr; QVERIFY(destroySpy.wait()); // TODO: should this emit unmapped? QVERIFY(!serverSurface->buffer()); } void TestWaylandSurface::testDestroyWithPendingCallback() { // this test tries to verify that destroying a surface with a pending callback works correctly // first create surface using namespace KWayland::Client; using namespace KWayland::Server; QScopedPointer s(m_compositor->createSurface()); QVERIFY(!s.isNull()); QVERIFY(s->isValid()); QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); // now render to it QImage img(QSize(10, 10), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::black); auto b = m_shm->createBuffer(img); s->attachBuffer(b); s->damage(QRect(0, 0, 10, 10)); // add some frame callbacks for (int i = 0; i < 1000; i++) { wl_surface_frame(*s); } s->commit(KWayland::Client::Surface::CommitFlag::FrameCallback); QSignalSpy damagedSpy(serverSurface, &SurfaceInterface::damaged); QVERIFY(damagedSpy.isValid()); QVERIFY(damagedSpy.wait()); // now try to destroy the Surface again QSignalSpy destroyedSpy(serverSurface, &QObject::destroyed); QVERIFY(destroyedSpy.isValid()); s.reset(); QVERIFY(destroyedSpy.wait()); } void TestWaylandSurface::testDisconnect() { // this test verifies that the server side correctly tears down the resources when the client disconnects using namespace KWayland::Client; using namespace KWayland::Server; QScopedPointer s(m_compositor->createSurface()); QVERIFY(!s.isNull()); QVERIFY(s->isValid()); QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); // destroy client QSignalSpy clientDisconnectedSpy(serverSurface->client(), &ClientConnection::disconnected); QVERIFY(clientDisconnectedSpy.isValid()); QSignalSpy surfaceDestroyedSpy(serverSurface, &QObject::destroyed); QVERIFY(surfaceDestroyedSpy.isValid()); if (m_connection) { m_connection->deleteLater(); m_connection = nullptr; } QVERIFY(clientDisconnectedSpy.wait()); QCOMPARE(clientDisconnectedSpy.count(), 1); if (surfaceDestroyedSpy.isEmpty()) { QVERIFY(surfaceDestroyedSpy.wait()); } QTRY_COMPARE(surfaceDestroyedSpy.count(), 1); s->destroy(); m_shm->destroy(); m_compositor->destroy(); m_queue->destroy(); m_idleInhibitManager->destroy(); } void TestWaylandSurface::testOutput() { // This test verifies that the enter/leave are sent correctly to the Client using namespace KWayland::Client; using namespace KWayland::Server; qRegisterMetaType(); QScopedPointer s(m_compositor->createSurface()); QVERIFY(!s.isNull()); QVERIFY(s->isValid()); QVERIFY(s->outputs().isEmpty()); QSignalSpy enteredSpy(s.data(), &Surface::outputEntered); QVERIFY(enteredSpy.isValid()); QSignalSpy leftSpy(s.data(), &Surface::outputLeft); QVERIFY(leftSpy.isValid()); // wait for the surface on the Server side QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); QCOMPARE(serverSurface->outputs(), QVector()); // create another registry to get notified about added outputs Registry registry; registry.setEventQueue(m_queue); QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); QVERIFY(allAnnounced.isValid()); registry.create(m_connection); QVERIFY(registry.isValid()); registry.setup(); QVERIFY(allAnnounced.wait()); QSignalSpy outputAnnouncedSpy(®istry, &Registry::outputAnnounced); QVERIFY(outputAnnouncedSpy.isValid()); auto serverOutput = m_display->createOutput(m_display); serverOutput->create(); QVERIFY(outputAnnouncedSpy.wait()); QScopedPointer clientOutput(registry.createOutput(outputAnnouncedSpy.first().first().value(), outputAnnouncedSpy.first().last().value())); QVERIFY(clientOutput->isValid()); m_connection->flush(); m_display->dispatchEvents(); // now enter it serverSurface->setOutputs(QVector{serverOutput}); QCOMPARE(serverSurface->outputs(), QVector{serverOutput}); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 1); QCOMPARE(enteredSpy.first().first().value(), clientOutput.data()); QCOMPARE(s->outputs(), QVector{clientOutput.data()}); // adding to same should not trigger serverSurface->setOutputs(QVector{serverOutput}); // leave again serverSurface->setOutputs(QVector()); QCOMPARE(serverSurface->outputs(), QVector()); QVERIFY(leftSpy.wait()); QCOMPARE(enteredSpy.count(), 1); QCOMPARE(leftSpy.count(), 1); QCOMPARE(leftSpy.first().first().value(), clientOutput.data()); QCOMPARE(s->outputs(), QVector()); // leave again should not trigger serverSurface->setOutputs(QVector()); // and enter again, just to verify serverSurface->setOutputs(QVector{serverOutput}); QCOMPARE(serverSurface->outputs(), QVector{serverOutput}); QVERIFY(enteredSpy.wait()); QCOMPARE(enteredSpy.count(), 2); QCOMPARE(leftSpy.count(), 1); //delete output client is on. //client should get an exit and be left on no outputs (which is allowed) serverOutput->deleteLater(); QVERIFY(leftSpy.wait()); QCOMPARE(serverSurface->outputs(), QVector()); } void TestWaylandSurface::testInhibit() { using namespace KWayland::Client; using namespace KWayland::Server; QScopedPointer s(m_compositor->createSurface()); // wait for the surface on the Server side QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); QVERIFY(surfaceCreatedSpy.isValid()); QVERIFY(surfaceCreatedSpy.wait()); auto serverSurface = surfaceCreatedSpy.first().first().value(); QVERIFY(serverSurface); QCOMPARE(serverSurface->inhibitsIdle(), false); QSignalSpy inhibitsChangedSpy(serverSurface, &SurfaceInterface::inhibitsIdleChanged); QVERIFY(inhibitsChangedSpy.isValid()); // now create an idle inhibition QScopedPointer inhibitor1(m_idleInhibitManager->createInhibitor(s.data())); QVERIFY(inhibitsChangedSpy.wait()); QCOMPARE(serverSurface->inhibitsIdle(), true); // creating a second idle inhibition should not trigger the signal QScopedPointer inhibitor2(m_idleInhibitManager->createInhibitor(s.data())); - QVERIFY(!inhibitsChangedSpy.wait()); + QVERIFY(!inhibitsChangedSpy.wait(500)); QCOMPARE(serverSurface->inhibitsIdle(), true); // and also deleting the first inhibitor should not yet change the inhibition inhibitor1.reset(); - QVERIFY(!inhibitsChangedSpy.wait()); + QVERIFY(!inhibitsChangedSpy.wait(500)); QCOMPARE(serverSurface->inhibitsIdle(), true); // but deleting also the second inhibitor should trigger inhibitor2.reset(); QVERIFY(inhibitsChangedSpy.wait()); QCOMPARE(serverSurface->inhibitsIdle(), false); QCOMPARE(inhibitsChangedSpy.count(), 2); // recreate inhibitor1 should inhibit again inhibitor1.reset(m_idleInhibitManager->createInhibitor(s.data())); QVERIFY(inhibitsChangedSpy.wait()); QCOMPARE(serverSurface->inhibitsIdle(), true); // and destroying should uninhibit inhibitor1.reset(); QVERIFY(inhibitsChangedSpy.wait()); QCOMPARE(serverSurface->inhibitsIdle(), false); QCOMPARE(inhibitsChangedSpy.count(), 4); } QTEST_GUILESS_MAIN(TestWaylandSurface) #include "test_wayland_surface.moc"