1 /*
2     SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6 // Qt
7 #include <QtTest>
8 // client
9 #include "KWayland/Client/compositor.h"
10 #include "KWayland/Client/connection_thread.h"
11 #include "KWayland/Client/event_queue.h"
12 #include "KWayland/Client/pointer.h"
13 #include "KWayland/Client/pointerconstraints.h"
14 #include "KWayland/Client/registry.h"
15 #include "KWayland/Client/seat.h"
16 #include "KWayland/Client/surface.h"
17 // server
18 #include "../../src/server/compositor_interface.h"
19 #include "../../src/server/display.h"
20 #include "../../src/server/pointerconstraints_v1_interface.h"
21 #include "../../src/server/seat_interface.h"
22 #include "../../src/server/surface_interface.h"
23 
24 using namespace KWayland::Client;
25 using namespace KWaylandServer;
26 
27 Q_DECLARE_METATYPE(KWayland::Client::PointerConstraints::LifeTime)
28 Q_DECLARE_METATYPE(KWaylandServer::ConfinedPointerV1Interface::LifeTime)
29 Q_DECLARE_METATYPE(KWaylandServer::LockedPointerV1Interface::LifeTime)
30 
31 class TestPointerConstraints : public QObject
32 {
33     Q_OBJECT
34 private Q_SLOTS:
35     void init();
36     void cleanup();
37 
38     void testLockPointer_data();
39     void testLockPointer();
40 
41     void testConfinePointer_data();
42     void testConfinePointer();
43     void testAlreadyConstrained_data();
44     void testAlreadyConstrained();
45 
46 private:
47     Display *m_display = nullptr;
48     CompositorInterface *m_compositorInterface = nullptr;
49     SeatInterface *m_seatInterface = nullptr;
50     PointerConstraintsV1Interface *m_pointerConstraintsInterface = nullptr;
51     ConnectionThread *m_connection = nullptr;
52     QThread *m_thread = nullptr;
53     EventQueue *m_queue = nullptr;
54     Compositor *m_compositor = nullptr;
55     Seat *m_seat = nullptr;
56     Pointer *m_pointer = nullptr;
57     PointerConstraints *m_pointerConstraints = nullptr;
58 };
59 
60 static const QString s_socketName = QStringLiteral("kwayland-test-pointer_constraint-0");
61 
init()62 void TestPointerConstraints::init()
63 {
64     delete m_display;
65     m_display = new Display(this);
66     m_display->addSocketName(s_socketName);
67     m_display->start();
68     QVERIFY(m_display->isRunning());
69     m_display->createShm();
70     m_seatInterface = new SeatInterface(m_display, m_display);
71     m_seatInterface->setHasPointer(true);
72     m_compositorInterface = new CompositorInterface(m_display, m_display);
73     m_pointerConstraintsInterface = new PointerConstraintsV1Interface(m_display, m_display);
74 
75     // setup connection
76     m_connection = new KWayland::Client::ConnectionThread;
77     QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected);
78     QVERIFY(connectedSpy.isValid());
79     m_connection->setSocketName(s_socketName);
80 
81     m_thread = new QThread(this);
82     m_connection->moveToThread(m_thread);
83     m_thread->start();
84 
85     m_connection->initConnection();
86     QVERIFY(connectedSpy.wait());
87 
88     m_queue = new EventQueue(this);
89     m_queue->setup(m_connection);
90 
91     Registry registry;
92     QSignalSpy interfacesAnnouncedSpy(&registry, &Registry::interfacesAnnounced);
93     QVERIFY(interfacesAnnouncedSpy.isValid());
94     QSignalSpy interfaceAnnouncedSpy(&registry, &Registry::interfaceAnnounced);
95     QVERIFY(interfaceAnnouncedSpy.isValid());
96     registry.setEventQueue(m_queue);
97     registry.create(m_connection);
98     QVERIFY(registry.isValid());
99     registry.setup();
100     QVERIFY(interfacesAnnouncedSpy.wait());
101 
102     m_compositor =
103         registry.createCompositor(registry.interface(Registry::Interface::Compositor).name, registry.interface(Registry::Interface::Compositor).version, this);
104     QVERIFY(m_compositor);
105     QVERIFY(m_compositor->isValid());
106 
107     m_pointerConstraints = registry.createPointerConstraints(registry.interface(Registry::Interface::PointerConstraintsUnstableV1).name,
108                                                              registry.interface(Registry::Interface::PointerConstraintsUnstableV1).version,
109                                                              this);
110     QVERIFY(m_pointerConstraints);
111     QVERIFY(m_pointerConstraints->isValid());
112 
113     m_seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name, registry.interface(Registry::Interface::Seat).version, this);
114     QVERIFY(m_seat);
115     QVERIFY(m_seat->isValid());
116     QSignalSpy pointerChangedSpy(m_seat, &Seat::hasPointerChanged);
117     QVERIFY(pointerChangedSpy.isValid());
118     QVERIFY(pointerChangedSpy.wait());
119     m_pointer = m_seat->createPointer(this);
120     QVERIFY(m_pointer);
121 }
122 
cleanup()123 void TestPointerConstraints::cleanup()
124 {
125 #define CLEANUP(variable)                                                                                                                                      \
126     if (variable) {                                                                                                                                            \
127         delete variable;                                                                                                                                       \
128         variable = nullptr;                                                                                                                                    \
129     }
130     CLEANUP(m_compositor)
131     CLEANUP(m_pointerConstraints)
132     CLEANUP(m_pointer)
133     CLEANUP(m_seat)
134     CLEANUP(m_queue)
135     if (m_connection) {
136         m_connection->deleteLater();
137         m_connection = nullptr;
138     }
139     if (m_thread) {
140         m_thread->quit();
141         m_thread->wait();
142         delete m_thread;
143         m_thread = nullptr;
144     }
145 
146     CLEANUP(m_display)
147 #undef CLEANUP
148 
149     // these are the children of the display
150     m_compositorInterface = nullptr;
151     m_seatInterface = nullptr;
152     m_pointerConstraintsInterface = nullptr;
153 }
154 
testLockPointer_data()155 void TestPointerConstraints::testLockPointer_data()
156 {
157     QTest::addColumn<PointerConstraints::LifeTime>("clientLifeTime");
158     QTest::addColumn<LockedPointerV1Interface::LifeTime>("serverLifeTime");
159     QTest::addColumn<bool>("hasConstraintAfterUnlock");
160     QTest::addColumn<int>("pointerChangedCount");
161 
162     QTest::newRow("persistent") << PointerConstraints::LifeTime::Persistent << LockedPointerV1Interface::LifeTime::Persistent << true << 1;
163     QTest::newRow("oneshot") << PointerConstraints::LifeTime::OneShot << LockedPointerV1Interface::LifeTime::OneShot << false << 2;
164 }
165 
testLockPointer()166 void TestPointerConstraints::testLockPointer()
167 {
168     // this test verifies the basic interaction for lock pointer
169     // first create a surface
170     QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
171     QVERIFY(surfaceCreatedSpy.isValid());
172     QScopedPointer<Surface> surface(m_compositor->createSurface());
173     QVERIFY(surface->isValid());
174     QVERIFY(surfaceCreatedSpy.wait());
175 
176     auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
177     QVERIFY(serverSurface);
178     QVERIFY(!serverSurface->lockedPointer());
179     QVERIFY(!serverSurface->confinedPointer());
180 
181     // now create the locked pointer
182     QSignalSpy pointerConstraintsChangedSpy(serverSurface, &SurfaceInterface::pointerConstraintsChanged);
183     QVERIFY(pointerConstraintsChangedSpy.isValid());
184     QFETCH(PointerConstraints::LifeTime, clientLifeTime);
185     QScopedPointer<LockedPointer> lockedPointer(m_pointerConstraints->lockPointer(surface.data(), m_pointer, nullptr, clientLifeTime));
186     QSignalSpy lockedSpy(lockedPointer.data(), &LockedPointer::locked);
187     QVERIFY(lockedSpy.isValid());
188     QSignalSpy unlockedSpy(lockedPointer.data(), &LockedPointer::unlocked);
189     QVERIFY(unlockedSpy.isValid());
190     QVERIFY(lockedPointer->isValid());
191     QVERIFY(pointerConstraintsChangedSpy.wait());
192 
193     auto serverLockedPointer = serverSurface->lockedPointer();
194     QVERIFY(serverLockedPointer);
195     QVERIFY(!serverSurface->confinedPointer());
196 
197     QCOMPARE(serverLockedPointer->isLocked(), false);
198     QCOMPARE(serverLockedPointer->region(), QRegion());
199     QFETCH(LockedPointerV1Interface::LifeTime, serverLifeTime);
200     QCOMPARE(serverLockedPointer->lifeTime(), serverLifeTime);
201     // setting to unlocked now should not trigger an unlocked spy
202     serverLockedPointer->setLocked(false);
203     QVERIFY(!unlockedSpy.wait(500));
204 
205     // try setting a region
206     QSignalSpy destroyedSpy(serverLockedPointer, &QObject::destroyed);
207     QVERIFY(destroyedSpy.isValid());
208     QSignalSpy regionChangedSpy(serverLockedPointer, &LockedPointerV1Interface::regionChanged);
209     QVERIFY(regionChangedSpy.isValid());
210     lockedPointer->setRegion(m_compositor->createRegion(QRegion(0, 5, 10, 20), m_compositor));
211     // it's double buffered
212     QVERIFY(!regionChangedSpy.wait(500));
213     surface->commit(Surface::CommitFlag::None);
214     QVERIFY(regionChangedSpy.wait());
215     QCOMPARE(serverLockedPointer->region(), QRegion(0, 5, 10, 20));
216     // and unset region again
217     lockedPointer->setRegion(nullptr);
218     surface->commit(Surface::CommitFlag::None);
219     QVERIFY(regionChangedSpy.wait());
220     QCOMPARE(serverLockedPointer->region(), QRegion());
221 
222     // let's lock the surface
223     QSignalSpy lockedChangedSpy(serverLockedPointer, &LockedPointerV1Interface::lockedChanged);
224     QVERIFY(lockedChangedSpy.isValid());
225     m_seatInterface->setFocusedPointerSurface(serverSurface);
226     QSignalSpy pointerMotionSpy(m_pointer, &Pointer::motion);
227     QVERIFY(pointerMotionSpy.isValid());
228     m_seatInterface->notifyPointerMotion(QPoint(0, 1));
229     m_seatInterface->notifyPointerFrame();
230     QVERIFY(pointerMotionSpy.wait());
231 
232     serverLockedPointer->setLocked(true);
233     QCOMPARE(serverLockedPointer->isLocked(), true);
234     m_seatInterface->notifyPointerMotion(QPoint(1, 1));
235     m_seatInterface->notifyPointerFrame();
236     QCOMPARE(lockedChangedSpy.count(), 1);
237     QCOMPARE(pointerMotionSpy.count(), 1);
238     QVERIFY(lockedSpy.isEmpty());
239     QVERIFY(lockedSpy.wait());
240     QVERIFY(unlockedSpy.isEmpty());
241 
242     const QPointF hint = QPointF(1.5, 0.5);
243     QSignalSpy hintChangedSpy(serverLockedPointer, &LockedPointerV1Interface::cursorPositionHintChanged);
244     lockedPointer->setCursorPositionHint(hint);
245     QCOMPARE(serverLockedPointer->cursorPositionHint(), QPointF(-1., -1.));
246     surface->commit(Surface::CommitFlag::None);
247     QVERIFY(hintChangedSpy.wait());
248     QCOMPARE(serverLockedPointer->cursorPositionHint(), hint);
249 
250     // and unlock again
251     serverLockedPointer->setLocked(false);
252     QCOMPARE(serverLockedPointer->isLocked(), false);
253     QCOMPARE(serverLockedPointer->cursorPositionHint(), QPointF(-1., -1.));
254     QCOMPARE(lockedChangedSpy.count(), 2);
255     QTEST(bool(serverSurface->lockedPointer()), "hasConstraintAfterUnlock");
256     QTEST(pointerConstraintsChangedSpy.count(), "pointerChangedCount");
257     QVERIFY(unlockedSpy.wait());
258     QCOMPARE(unlockedSpy.count(), 1);
259     QCOMPARE(lockedSpy.count(), 1);
260 
261     // now motion should work again
262     m_seatInterface->notifyPointerMotion(QPoint(0, 1));
263     m_seatInterface->notifyPointerFrame();
264     QVERIFY(pointerMotionSpy.wait());
265     QCOMPARE(pointerMotionSpy.count(), 2);
266 
267     lockedPointer.reset();
268     QVERIFY(destroyedSpy.wait());
269     QCOMPARE(pointerConstraintsChangedSpy.count(), 2);
270 }
271 
testConfinePointer_data()272 void TestPointerConstraints::testConfinePointer_data()
273 {
274     QTest::addColumn<PointerConstraints::LifeTime>("clientLifeTime");
275     QTest::addColumn<ConfinedPointerV1Interface::LifeTime>("serverLifeTime");
276     QTest::addColumn<bool>("hasConstraintAfterUnlock");
277     QTest::addColumn<int>("pointerChangedCount");
278 
279     QTest::newRow("persistent") << PointerConstraints::LifeTime::Persistent << ConfinedPointerV1Interface::LifeTime::Persistent << true << 1;
280     QTest::newRow("oneshot") << PointerConstraints::LifeTime::OneShot << ConfinedPointerV1Interface::LifeTime::OneShot << false << 2;
281 }
282 
testConfinePointer()283 void TestPointerConstraints::testConfinePointer()
284 {
285     // this test verifies the basic interaction for confined pointer
286     // first create a surface
287     QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
288     QVERIFY(surfaceCreatedSpy.isValid());
289     QScopedPointer<Surface> surface(m_compositor->createSurface());
290     QVERIFY(surface->isValid());
291     QVERIFY(surfaceCreatedSpy.wait());
292 
293     auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface *>();
294     QVERIFY(serverSurface);
295     QVERIFY(!serverSurface->lockedPointer());
296     QVERIFY(!serverSurface->confinedPointer());
297 
298     // now create the confined pointer
299     QSignalSpy pointerConstraintsChangedSpy(serverSurface, &SurfaceInterface::pointerConstraintsChanged);
300     QVERIFY(pointerConstraintsChangedSpy.isValid());
301     QFETCH(PointerConstraints::LifeTime, clientLifeTime);
302     QScopedPointer<ConfinedPointer> confinedPointer(m_pointerConstraints->confinePointer(surface.data(), m_pointer, nullptr, clientLifeTime));
303     QSignalSpy confinedSpy(confinedPointer.data(), &ConfinedPointer::confined);
304     QVERIFY(confinedSpy.isValid());
305     QSignalSpy unconfinedSpy(confinedPointer.data(), &ConfinedPointer::unconfined);
306     QVERIFY(unconfinedSpy.isValid());
307     QVERIFY(confinedPointer->isValid());
308     QVERIFY(pointerConstraintsChangedSpy.wait());
309 
310     auto serverConfinedPointer = serverSurface->confinedPointer();
311     QVERIFY(serverConfinedPointer);
312     QVERIFY(!serverSurface->lockedPointer());
313 
314     QCOMPARE(serverConfinedPointer->isConfined(), false);
315     QCOMPARE(serverConfinedPointer->region(), QRegion());
316     QFETCH(ConfinedPointerV1Interface::LifeTime, serverLifeTime);
317     QCOMPARE(serverConfinedPointer->lifeTime(), serverLifeTime);
318     // setting to unconfined now should not trigger an unconfined spy
319     serverConfinedPointer->setConfined(false);
320     QVERIFY(!unconfinedSpy.wait(500));
321 
322     // try setting a region
323     QSignalSpy destroyedSpy(serverConfinedPointer, &QObject::destroyed);
324     QVERIFY(destroyedSpy.isValid());
325     QSignalSpy regionChangedSpy(serverConfinedPointer, &ConfinedPointerV1Interface::regionChanged);
326     QVERIFY(regionChangedSpy.isValid());
327     confinedPointer->setRegion(m_compositor->createRegion(QRegion(0, 5, 10, 20), m_compositor));
328     // it's double buffered
329     QVERIFY(!regionChangedSpy.wait(500));
330     surface->commit(Surface::CommitFlag::None);
331     QVERIFY(regionChangedSpy.wait());
332     QCOMPARE(serverConfinedPointer->region(), QRegion(0, 5, 10, 20));
333     // and unset region again
334     confinedPointer->setRegion(nullptr);
335     surface->commit(Surface::CommitFlag::None);
336     QVERIFY(regionChangedSpy.wait());
337     QCOMPARE(serverConfinedPointer->region(), QRegion());
338 
339     // let's confine the surface
340     QSignalSpy confinedChangedSpy(serverConfinedPointer, &ConfinedPointerV1Interface::confinedChanged);
341     QVERIFY(confinedChangedSpy.isValid());
342     m_seatInterface->setFocusedPointerSurface(serverSurface);
343     serverConfinedPointer->setConfined(true);
344     QCOMPARE(serverConfinedPointer->isConfined(), true);
345     QCOMPARE(confinedChangedSpy.count(), 1);
346     QVERIFY(confinedSpy.isEmpty());
347     QVERIFY(confinedSpy.wait());
348     QVERIFY(unconfinedSpy.isEmpty());
349 
350     // and unconfine again
351     serverConfinedPointer->setConfined(false);
352     QCOMPARE(serverConfinedPointer->isConfined(), false);
353     QCOMPARE(confinedChangedSpy.count(), 2);
354     QTEST(bool(serverSurface->confinedPointer()), "hasConstraintAfterUnlock");
355     QTEST(pointerConstraintsChangedSpy.count(), "pointerChangedCount");
356     QVERIFY(unconfinedSpy.wait());
357     QCOMPARE(unconfinedSpy.count(), 1);
358     QCOMPARE(confinedSpy.count(), 1);
359 
360     confinedPointer.reset();
361     QVERIFY(destroyedSpy.wait());
362     QCOMPARE(pointerConstraintsChangedSpy.count(), 2);
363 }
364 
365 enum class Constraint {
366     Lock,
367     Confine,
368 };
369 
Q_DECLARE_METATYPE(Constraint)370 Q_DECLARE_METATYPE(Constraint)
371 
372 void TestPointerConstraints::testAlreadyConstrained_data()
373 {
374     QTest::addColumn<Constraint>("firstConstraint");
375     QTest::addColumn<Constraint>("secondConstraint");
376 
377     QTest::newRow("confine-confine") << Constraint::Confine << Constraint::Confine;
378     QTest::newRow("lock-confine") << Constraint::Lock << Constraint::Confine;
379     QTest::newRow("confine-lock") << Constraint::Confine << Constraint::Lock;
380     QTest::newRow("lock-lock") << Constraint::Lock << Constraint::Lock;
381 }
382 
testAlreadyConstrained()383 void TestPointerConstraints::testAlreadyConstrained()
384 {
385     // this test verifies that creating a pointer constraint for an already constrained surface triggers an error
386     // first create a surface
387     QScopedPointer<Surface> surface(m_compositor->createSurface());
388     QVERIFY(surface->isValid());
389     QFETCH(Constraint, firstConstraint);
390     QScopedPointer<ConfinedPointer> confinedPointer;
391     QScopedPointer<LockedPointer> lockedPointer;
392     switch (firstConstraint) {
393     case Constraint::Lock:
394         lockedPointer.reset(m_pointerConstraints->lockPointer(surface.data(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot));
395         break;
396     case Constraint::Confine:
397         confinedPointer.reset(m_pointerConstraints->confinePointer(surface.data(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot));
398         break;
399     default:
400         Q_UNREACHABLE();
401     }
402     QVERIFY(confinedPointer || lockedPointer);
403 
404     QSignalSpy errorSpy(m_connection, &ConnectionThread::errorOccurred);
405     QVERIFY(errorSpy.isValid());
406     QFETCH(Constraint, secondConstraint);
407     QScopedPointer<ConfinedPointer> confinedPointer2;
408     QScopedPointer<LockedPointer> lockedPointer2;
409     switch (secondConstraint) {
410     case Constraint::Lock:
411         lockedPointer2.reset(m_pointerConstraints->lockPointer(surface.data(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot));
412         break;
413     case Constraint::Confine:
414         confinedPointer2.reset(m_pointerConstraints->confinePointer(surface.data(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot));
415         break;
416     default:
417         Q_UNREACHABLE();
418     }
419     QVERIFY(errorSpy.wait());
420     QVERIFY(m_connection->hasError());
421     if (confinedPointer2) {
422         confinedPointer2->destroy();
423     }
424     if (lockedPointer2) {
425         lockedPointer2->destroy();
426     }
427     if (confinedPointer) {
428         confinedPointer->destroy();
429     }
430     if (lockedPointer) {
431         lockedPointer->destroy();
432     }
433     surface->destroy();
434     m_compositor->destroy();
435     m_pointerConstraints->destroy();
436     m_pointer->destroy();
437     m_seat->destroy();
438     m_queue->destroy();
439 }
440 
441 QTEST_GUILESS_MAIN(TestPointerConstraints)
442 #include "test_pointer_constraints.moc"
443