File indexing completed on 2024-06-02 05:36:40

0001 /*
0002     SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 // Qt
0007 #include <QSignalSpy>
0008 #include <QTest>
0009 // client
0010 #include "KWayland/Client/compositor.h"
0011 #include "KWayland/Client/connection_thread.h"
0012 #include "KWayland/Client/event_queue.h"
0013 #include "KWayland/Client/pointer.h"
0014 #include "KWayland/Client/pointerconstraints.h"
0015 #include "KWayland/Client/registry.h"
0016 #include "KWayland/Client/seat.h"
0017 #include "KWayland/Client/shm_pool.h"
0018 #include "KWayland/Client/surface.h"
0019 // server
0020 #include "wayland/compositor.h"
0021 #include "wayland/display.h"
0022 #include "wayland/pointerconstraints_v1.h"
0023 #include "wayland/seat.h"
0024 #include "wayland/surface.h"
0025 
0026 using namespace KWin;
0027 
0028 Q_DECLARE_METATYPE(KWayland::Client::PointerConstraints::LifeTime)
0029 Q_DECLARE_METATYPE(KWin::ConfinedPointerV1Interface::LifeTime)
0030 Q_DECLARE_METATYPE(KWin::LockedPointerV1Interface::LifeTime)
0031 
0032 class TestPointerConstraints : public QObject
0033 {
0034     Q_OBJECT
0035 private Q_SLOTS:
0036     void init();
0037     void cleanup();
0038 
0039     void testLockPointer_data();
0040     void testLockPointer();
0041 
0042     void testConfinePointer_data();
0043     void testConfinePointer();
0044     void testAlreadyConstrained_data();
0045     void testAlreadyConstrained();
0046 
0047 private:
0048     KWin::Display *m_display = nullptr;
0049     CompositorInterface *m_compositorInterface = nullptr;
0050     SeatInterface *m_seatInterface = nullptr;
0051     PointerConstraintsV1Interface *m_pointerConstraintsInterface = nullptr;
0052     KWayland::Client::ConnectionThread *m_connection = nullptr;
0053     QThread *m_thread = nullptr;
0054     KWayland::Client::EventQueue *m_queue = nullptr;
0055     KWayland::Client::Compositor *m_compositor = nullptr;
0056     KWayland::Client::Seat *m_seat = nullptr;
0057     KWayland::Client::ShmPool *m_shm = nullptr;
0058     KWayland::Client::Pointer *m_pointer = nullptr;
0059     KWayland::Client::PointerConstraints *m_pointerConstraints = nullptr;
0060 };
0061 
0062 static const QString s_socketName = QStringLiteral("kwayland-test-pointer_constraint-0");
0063 
0064 void TestPointerConstraints::init()
0065 {
0066     delete m_display;
0067     m_display = new KWin::Display(this);
0068     m_display->addSocketName(s_socketName);
0069     m_display->start();
0070     QVERIFY(m_display->isRunning());
0071     m_display->createShm();
0072     m_seatInterface = new SeatInterface(m_display, m_display);
0073     m_seatInterface->setHasPointer(true);
0074     m_compositorInterface = new CompositorInterface(m_display, m_display);
0075     m_pointerConstraintsInterface = new PointerConstraintsV1Interface(m_display, m_display);
0076 
0077     // setup connection
0078     m_connection = new KWayland::Client::ConnectionThread;
0079     QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected);
0080     m_connection->setSocketName(s_socketName);
0081 
0082     m_thread = new QThread(this);
0083     m_connection->moveToThread(m_thread);
0084     m_thread->start();
0085 
0086     m_connection->initConnection();
0087     QVERIFY(connectedSpy.wait());
0088 
0089     m_queue = new KWayland::Client::EventQueue(this);
0090     m_queue->setup(m_connection);
0091 
0092     KWayland::Client::Registry registry;
0093     QSignalSpy interfacesAnnouncedSpy(&registry, &KWayland::Client::Registry::interfacesAnnounced);
0094     QSignalSpy interfaceAnnouncedSpy(&registry, &KWayland::Client::Registry::interfaceAnnounced);
0095     registry.setEventQueue(m_queue);
0096     registry.create(m_connection);
0097     QVERIFY(registry.isValid());
0098     registry.setup();
0099     QVERIFY(interfacesAnnouncedSpy.wait());
0100 
0101     m_shm = new KWayland::Client::ShmPool(this);
0102     m_shm->setup(registry.bindShm(registry.interface(KWayland::Client::Registry::Interface::Shm).name,
0103                                   registry.interface(KWayland::Client::Registry::Interface::Shm).version));
0104     QVERIFY(m_shm->isValid());
0105 
0106     m_compositor =
0107         registry.createCompositor(registry.interface(KWayland::Client::Registry::Interface::Compositor).name, registry.interface(KWayland::Client::Registry::Interface::Compositor).version, this);
0108     QVERIFY(m_compositor);
0109     QVERIFY(m_compositor->isValid());
0110 
0111     m_pointerConstraints = registry.createPointerConstraints(registry.interface(KWayland::Client::Registry::Interface::PointerConstraintsUnstableV1).name,
0112                                                              registry.interface(KWayland::Client::Registry::Interface::PointerConstraintsUnstableV1).version,
0113                                                              this);
0114     QVERIFY(m_pointerConstraints);
0115     QVERIFY(m_pointerConstraints->isValid());
0116 
0117     m_seat = registry.createSeat(registry.interface(KWayland::Client::Registry::Interface::Seat).name, registry.interface(KWayland::Client::Registry::Interface::Seat).version, this);
0118     QVERIFY(m_seat);
0119     QVERIFY(m_seat->isValid());
0120     QSignalSpy pointerChangedSpy(m_seat, &KWayland::Client::Seat::hasPointerChanged);
0121     QVERIFY(pointerChangedSpy.wait());
0122     m_pointer = m_seat->createPointer(this);
0123     QVERIFY(m_pointer);
0124 }
0125 
0126 void TestPointerConstraints::cleanup()
0127 {
0128 #define CLEANUP(variable)   \
0129     if (variable) {         \
0130         delete variable;    \
0131         variable = nullptr; \
0132     }
0133     CLEANUP(m_compositor)
0134     CLEANUP(m_pointerConstraints)
0135     CLEANUP(m_pointer)
0136     CLEANUP(m_shm)
0137     CLEANUP(m_seat)
0138     CLEANUP(m_queue)
0139     if (m_connection) {
0140         m_connection->deleteLater();
0141         m_connection = nullptr;
0142     }
0143     if (m_thread) {
0144         m_thread->quit();
0145         m_thread->wait();
0146         delete m_thread;
0147         m_thread = nullptr;
0148     }
0149 
0150     CLEANUP(m_display)
0151 #undef CLEANUP
0152 
0153     // these are the children of the display
0154     m_compositorInterface = nullptr;
0155     m_seatInterface = nullptr;
0156     m_pointerConstraintsInterface = nullptr;
0157 }
0158 
0159 void TestPointerConstraints::testLockPointer_data()
0160 {
0161     QTest::addColumn<KWayland::Client::PointerConstraints::LifeTime>("clientLifeTime");
0162     QTest::addColumn<LockedPointerV1Interface::LifeTime>("serverLifeTime");
0163     QTest::addColumn<bool>("hasConstraintAfterUnlock");
0164     QTest::addColumn<int>("pointerChangedCount");
0165 
0166     QTest::newRow("persistent") << KWayland::Client::PointerConstraints::LifeTime::Persistent << LockedPointerV1Interface::LifeTime::Persistent << true << 1;
0167     QTest::newRow("oneshot") << KWayland::Client::PointerConstraints::LifeTime::OneShot << LockedPointerV1Interface::LifeTime::OneShot << false << 2;
0168 }
0169 
0170 void TestPointerConstraints::testLockPointer()
0171 {
0172     // this test verifies the basic interaction for lock pointer
0173     // first create a surface
0174     QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
0175     std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
0176     QVERIFY(surface->isValid());
0177     QVERIFY(surfaceCreatedSpy.wait());
0178 
0179     QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
0180     image.fill(Qt::black);
0181     surface->attachBuffer(m_shm->createBuffer(image));
0182     surface->damage(image.rect());
0183     surface->commit(KWayland::Client::Surface::CommitFlag::None);
0184 
0185     auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
0186     QVERIFY(serverSurface);
0187     QVERIFY(!serverSurface->lockedPointer());
0188     QVERIFY(!serverSurface->confinedPointer());
0189 
0190     // now create the locked pointer
0191     QSignalSpy pointerConstraintsChangedSpy(serverSurface, &SurfaceInterface::pointerConstraintsChanged);
0192     QFETCH(KWayland::Client::PointerConstraints::LifeTime, clientLifeTime);
0193     std::unique_ptr<KWayland::Client::LockedPointer> lockedPointer(m_pointerConstraints->lockPointer(surface.get(), m_pointer, nullptr, clientLifeTime));
0194     QSignalSpy lockedSpy(lockedPointer.get(), &KWayland::Client::LockedPointer::locked);
0195     QSignalSpy unlockedSpy(lockedPointer.get(), &KWayland::Client::LockedPointer::unlocked);
0196     QVERIFY(lockedPointer->isValid());
0197     QVERIFY(pointerConstraintsChangedSpy.wait());
0198 
0199     auto serverLockedPointer = serverSurface->lockedPointer();
0200     QVERIFY(serverLockedPointer);
0201     QVERIFY(!serverSurface->confinedPointer());
0202 
0203     QCOMPARE(serverLockedPointer->isLocked(), false);
0204     QCOMPARE(serverLockedPointer->region(), QRegion(0, 0, 100, 100));
0205     QFETCH(LockedPointerV1Interface::LifeTime, serverLifeTime);
0206     QCOMPARE(serverLockedPointer->lifeTime(), serverLifeTime);
0207     // setting to unlocked now should not trigger an unlocked spy
0208     serverLockedPointer->setLocked(false);
0209     QVERIFY(!unlockedSpy.wait(500));
0210 
0211     // try setting a region
0212     QSignalSpy destroyedSpy(serverLockedPointer, &QObject::destroyed);
0213     QSignalSpy regionChangedSpy(serverLockedPointer, &LockedPointerV1Interface::regionChanged);
0214     lockedPointer->setRegion(m_compositor->createRegion(QRegion(0, 5, 10, 20), m_compositor));
0215     // it's double buffered
0216     QVERIFY(!regionChangedSpy.wait(500));
0217     surface->commit(KWayland::Client::Surface::CommitFlag::None);
0218     QVERIFY(regionChangedSpy.wait());
0219     QCOMPARE(serverLockedPointer->region(), QRegion(0, 5, 10, 20));
0220     // and unset region again
0221     lockedPointer->setRegion(nullptr);
0222     surface->commit(KWayland::Client::Surface::CommitFlag::None);
0223     QVERIFY(regionChangedSpy.wait());
0224     QCOMPARE(serverLockedPointer->region(), QRegion(0, 0, 100, 100));
0225 
0226     // let's lock the surface
0227     QSignalSpy lockedChangedSpy(serverLockedPointer, &LockedPointerV1Interface::lockedChanged);
0228     m_seatInterface->notifyPointerEnter(serverSurface, QPointF(0, 0));
0229     QSignalSpy pointerMotionSpy(m_pointer, &KWayland::Client::Pointer::motion);
0230     m_seatInterface->notifyPointerMotion(QPoint(0, 1));
0231     m_seatInterface->notifyPointerFrame();
0232     QVERIFY(pointerMotionSpy.wait());
0233 
0234     serverLockedPointer->setLocked(true);
0235     QCOMPARE(serverLockedPointer->isLocked(), true);
0236     m_seatInterface->notifyPointerMotion(QPoint(1, 1));
0237     m_seatInterface->notifyPointerFrame();
0238     QCOMPARE(lockedChangedSpy.count(), 1);
0239     QCOMPARE(pointerMotionSpy.count(), 1);
0240     QVERIFY(lockedSpy.isEmpty());
0241     QVERIFY(lockedSpy.wait());
0242     QVERIFY(unlockedSpy.isEmpty());
0243 
0244     const QPointF hint = QPointF(1.5, 0.5);
0245     QSignalSpy hintChangedSpy(serverLockedPointer, &LockedPointerV1Interface::cursorPositionHintChanged);
0246     lockedPointer->setCursorPositionHint(hint);
0247     QCOMPARE(serverLockedPointer->cursorPositionHint(), QPointF(-1., -1.));
0248     surface->commit(KWayland::Client::Surface::CommitFlag::None);
0249     QVERIFY(hintChangedSpy.wait());
0250     QCOMPARE(serverLockedPointer->cursorPositionHint(), hint);
0251 
0252     // and unlock again
0253     serverLockedPointer->setLocked(false);
0254     QCOMPARE(serverLockedPointer->isLocked(), false);
0255     QCOMPARE(serverLockedPointer->cursorPositionHint(), QPointF(-1., -1.));
0256     QCOMPARE(lockedChangedSpy.count(), 2);
0257     QTEST(bool(serverSurface->lockedPointer()), "hasConstraintAfterUnlock");
0258     QFETCH(int, pointerChangedCount);
0259     QCOMPARE(pointerConstraintsChangedSpy.count(), pointerChangedCount);
0260     QVERIFY(unlockedSpy.wait());
0261     QCOMPARE(unlockedSpy.count(), 1);
0262     QCOMPARE(lockedSpy.count(), 1);
0263 
0264     // now motion should work again
0265     m_seatInterface->notifyPointerMotion(QPoint(0, 1));
0266     m_seatInterface->notifyPointerFrame();
0267     QVERIFY(pointerMotionSpy.wait());
0268     QCOMPARE(pointerMotionSpy.count(), 2);
0269 
0270     lockedPointer.reset();
0271     QVERIFY(destroyedSpy.wait());
0272     QCOMPARE(pointerConstraintsChangedSpy.count(), 2);
0273 }
0274 
0275 void TestPointerConstraints::testConfinePointer_data()
0276 {
0277     QTest::addColumn<KWayland::Client::PointerConstraints::LifeTime>("clientLifeTime");
0278     QTest::addColumn<ConfinedPointerV1Interface::LifeTime>("serverLifeTime");
0279     QTest::addColumn<bool>("hasConstraintAfterUnlock");
0280     QTest::addColumn<int>("pointerChangedCount");
0281 
0282     QTest::newRow("persistent") << KWayland::Client::PointerConstraints::LifeTime::Persistent << ConfinedPointerV1Interface::LifeTime::Persistent << true << 1;
0283     QTest::newRow("oneshot") << KWayland::Client::PointerConstraints::LifeTime::OneShot << ConfinedPointerV1Interface::LifeTime::OneShot << false << 2;
0284 }
0285 
0286 void TestPointerConstraints::testConfinePointer()
0287 {
0288     // this test verifies the basic interaction for confined pointer
0289     // first create a surface
0290     QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
0291     std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
0292     QVERIFY(surface->isValid());
0293     QVERIFY(surfaceCreatedSpy.wait());
0294 
0295     QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied);
0296     image.fill(Qt::black);
0297     surface->attachBuffer(m_shm->createBuffer(image));
0298     surface->damage(image.rect());
0299     surface->commit(KWayland::Client::Surface::CommitFlag::None);
0300 
0301     auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
0302     QVERIFY(serverSurface);
0303     QVERIFY(!serverSurface->lockedPointer());
0304     QVERIFY(!serverSurface->confinedPointer());
0305 
0306     // now create the confined pointer
0307     QSignalSpy pointerConstraintsChangedSpy(serverSurface, &SurfaceInterface::pointerConstraintsChanged);
0308     QFETCH(KWayland::Client::PointerConstraints::LifeTime, clientLifeTime);
0309     std::unique_ptr<KWayland::Client::ConfinedPointer> confinedPointer(m_pointerConstraints->confinePointer(surface.get(), m_pointer, nullptr, clientLifeTime));
0310     QSignalSpy confinedSpy(confinedPointer.get(), &KWayland::Client::ConfinedPointer::confined);
0311     QSignalSpy unconfinedSpy(confinedPointer.get(), &KWayland::Client::ConfinedPointer::unconfined);
0312     QVERIFY(confinedPointer->isValid());
0313     QVERIFY(pointerConstraintsChangedSpy.wait());
0314 
0315     auto serverConfinedPointer = serverSurface->confinedPointer();
0316     QVERIFY(serverConfinedPointer);
0317     QVERIFY(!serverSurface->lockedPointer());
0318 
0319     QCOMPARE(serverConfinedPointer->isConfined(), false);
0320     QCOMPARE(serverConfinedPointer->region(), QRegion(0, 0, 100, 100));
0321     QFETCH(ConfinedPointerV1Interface::LifeTime, serverLifeTime);
0322     QCOMPARE(serverConfinedPointer->lifeTime(), serverLifeTime);
0323     // setting to unconfined now should not trigger an unconfined spy
0324     serverConfinedPointer->setConfined(false);
0325     QVERIFY(!unconfinedSpy.wait(500));
0326 
0327     // try setting a region
0328     QSignalSpy destroyedSpy(serverConfinedPointer, &QObject::destroyed);
0329     QSignalSpy regionChangedSpy(serverConfinedPointer, &ConfinedPointerV1Interface::regionChanged);
0330     confinedPointer->setRegion(m_compositor->createRegion(QRegion(0, 5, 10, 20), m_compositor));
0331     // it's double buffered
0332     QVERIFY(!regionChangedSpy.wait(500));
0333     surface->commit(KWayland::Client::Surface::CommitFlag::None);
0334     QVERIFY(regionChangedSpy.wait());
0335     QCOMPARE(serverConfinedPointer->region(), QRegion(0, 5, 10, 20));
0336     // and unset region again
0337     confinedPointer->setRegion(nullptr);
0338     surface->commit(KWayland::Client::Surface::CommitFlag::None);
0339     QVERIFY(regionChangedSpy.wait());
0340     QCOMPARE(serverConfinedPointer->region(), QRegion(0, 0, 100, 100));
0341 
0342     // let's confine the surface
0343     QSignalSpy confinedChangedSpy(serverConfinedPointer, &ConfinedPointerV1Interface::confinedChanged);
0344     m_seatInterface->notifyPointerEnter(serverSurface, QPointF(0, 0));
0345     serverConfinedPointer->setConfined(true);
0346     QCOMPARE(serverConfinedPointer->isConfined(), true);
0347     QCOMPARE(confinedChangedSpy.count(), 1);
0348     QVERIFY(confinedSpy.isEmpty());
0349     QVERIFY(confinedSpy.wait());
0350     QVERIFY(unconfinedSpy.isEmpty());
0351 
0352     // and unconfine again
0353     serverConfinedPointer->setConfined(false);
0354     QCOMPARE(serverConfinedPointer->isConfined(), false);
0355     QCOMPARE(confinedChangedSpy.count(), 2);
0356     QTEST(bool(serverSurface->confinedPointer()), "hasConstraintAfterUnlock");
0357     QFETCH(int, pointerChangedCount);
0358     QCOMPARE(pointerConstraintsChangedSpy.count(), pointerChangedCount);
0359     QVERIFY(unconfinedSpy.wait());
0360     QCOMPARE(unconfinedSpy.count(), 1);
0361     QCOMPARE(confinedSpy.count(), 1);
0362 
0363     confinedPointer.reset();
0364     QVERIFY(destroyedSpy.wait());
0365     QCOMPARE(pointerConstraintsChangedSpy.count(), 2);
0366 }
0367 
0368 enum class Constraint {
0369     Lock,
0370     Confine,
0371 };
0372 
0373 Q_DECLARE_METATYPE(Constraint)
0374 
0375 void TestPointerConstraints::testAlreadyConstrained_data()
0376 {
0377     QTest::addColumn<Constraint>("firstConstraint");
0378     QTest::addColumn<Constraint>("secondConstraint");
0379 
0380     QTest::newRow("confine-confine") << Constraint::Confine << Constraint::Confine;
0381     QTest::newRow("lock-confine") << Constraint::Lock << Constraint::Confine;
0382     QTest::newRow("confine-lock") << Constraint::Confine << Constraint::Lock;
0383     QTest::newRow("lock-lock") << Constraint::Lock << Constraint::Lock;
0384 }
0385 
0386 void TestPointerConstraints::testAlreadyConstrained()
0387 {
0388     // this test verifies that creating a pointer constraint for an already constrained surface triggers an error
0389     // first create a surface
0390     std::unique_ptr<KWayland::Client::Surface> surface(m_compositor->createSurface());
0391     QVERIFY(surface->isValid());
0392     QFETCH(Constraint, firstConstraint);
0393     std::unique_ptr<KWayland::Client::ConfinedPointer> confinedPointer;
0394     std::unique_ptr<KWayland::Client::LockedPointer> lockedPointer;
0395     switch (firstConstraint) {
0396     case Constraint::Lock:
0397         lockedPointer.reset(m_pointerConstraints->lockPointer(surface.get(), m_pointer, nullptr, KWayland::Client::PointerConstraints::LifeTime::OneShot));
0398         break;
0399     case Constraint::Confine:
0400         confinedPointer.reset(m_pointerConstraints->confinePointer(surface.get(), m_pointer, nullptr, KWayland::Client::PointerConstraints::LifeTime::OneShot));
0401         break;
0402     default:
0403         Q_UNREACHABLE();
0404     }
0405     QVERIFY(confinedPointer || lockedPointer);
0406 
0407     QSignalSpy errorSpy(m_connection, &KWayland::Client::ConnectionThread::errorOccurred);
0408     QFETCH(Constraint, secondConstraint);
0409     std::unique_ptr<KWayland::Client::ConfinedPointer> confinedPointer2;
0410     std::unique_ptr<KWayland::Client::LockedPointer> lockedPointer2;
0411     switch (secondConstraint) {
0412     case Constraint::Lock:
0413         lockedPointer2.reset(m_pointerConstraints->lockPointer(surface.get(), m_pointer, nullptr, KWayland::Client::PointerConstraints::LifeTime::OneShot));
0414         break;
0415     case Constraint::Confine:
0416         confinedPointer2.reset(m_pointerConstraints->confinePointer(surface.get(), m_pointer, nullptr, KWayland::Client::PointerConstraints::LifeTime::OneShot));
0417         break;
0418     default:
0419         Q_UNREACHABLE();
0420     }
0421     QVERIFY(errorSpy.wait());
0422     QVERIFY(m_connection->hasError());
0423     if (confinedPointer2) {
0424         confinedPointer2->destroy();
0425     }
0426     if (lockedPointer2) {
0427         lockedPointer2->destroy();
0428     }
0429     if (confinedPointer) {
0430         confinedPointer->destroy();
0431     }
0432     if (lockedPointer) {
0433         lockedPointer->destroy();
0434     }
0435     surface->destroy();
0436     m_compositor->destroy();
0437     m_pointerConstraints->destroy();
0438     m_pointer->destroy();
0439     m_seat->destroy();
0440     m_queue->destroy();
0441 }
0442 
0443 QTEST_GUILESS_MAIN(TestPointerConstraints)
0444 #include "test_pointer_constraints.moc"