File indexing completed on 2024-11-10 04:56:16
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(®istry, &KWayland::Client::Registry::interfacesAnnounced); 0094 QSignalSpy interfaceAnnouncedSpy(®istry, &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"