1 /********************************************************************
2 Copyright © 2016 Martin Gräßlin <mgraesslin@kde.org>
3 Copyright © 2020 Roman Gilg <subdiff@gmail.com>
4 
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) version 3, or any
9 later version accepted by the membership of KDE e.V. (or its
10 successor approved by the membership of KDE e.V.), which shall
11 act as a proxy defined in Section 6 of version 3 of the license.
12 
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 Lesser General Public License for more details.
17 
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library.  If not, see <http://www.gnu.org/licenses/>.
20 *********************************************************************/
21 #include <QtTest>
22 
23 #include "../../src/client/compositor.h"
24 #include "../../src/client/connection_thread.h"
25 #include "../../src/client/event_queue.h"
26 #include "../../src/client/pointer.h"
27 #include "../../src/client/pointerconstraints.h"
28 #include "../../src/client/registry.h"
29 #include "../../src/client/seat.h"
30 #include "../../src/client/surface.h"
31 
32 #include "../../server/compositor.h"
33 #include "../../server/display.h"
34 #include "../../server/pointer_constraints_v1.h"
35 #include "../../server/pointer_pool.h"
36 #include "../../server/seat.h"
37 #include "../../server/surface.h"
38 
39 using namespace Wrapland::Client;
40 
41 Q_DECLARE_METATYPE(Wrapland::Client::PointerConstraints::LifeTime)
42 Q_DECLARE_METATYPE(Wrapland::Server::ConfinedPointerV1::LifeTime)
43 Q_DECLARE_METATYPE(Wrapland::Server::LockedPointerV1::LifeTime)
44 
45 class TestPointerConstraints : public QObject
46 {
47     Q_OBJECT
48 private Q_SLOTS:
49     void init();
50     void cleanup();
51 
52     void testLockPointer_data();
53     void testLockPointer();
54 
55     void testConfinePointer_data();
56     void testConfinePointer();
57     void testAlreadyConstrained_data();
58     void testAlreadyConstrained();
59 
60 private:
61     Wrapland::Server::Display* m_display = nullptr;
62     Wrapland::Server::Compositor* m_serverCompositor = nullptr;
63     Wrapland::Server::Seat* m_serverSeat = nullptr;
64     Wrapland::Server::PointerConstraintsV1* m_pointerConstraintsServer = nullptr;
65     ConnectionThread* m_connection = nullptr;
66     QThread* m_thread = nullptr;
67     EventQueue* m_queue = nullptr;
68     Compositor* m_compositor = nullptr;
69     Wrapland::Client::Seat* m_seat = nullptr;
70     Wrapland::Client::Pointer* m_pointer = nullptr;
71     Wrapland::Client::PointerConstraints* m_pointerConstraints = nullptr;
72 };
73 
74 static const QString s_socketName = QStringLiteral("wrapland-test-pointer_constraint-0");
75 
init()76 void TestPointerConstraints::init()
77 {
78     qRegisterMetaType<Wrapland::Server::Surface*>();
79 
80     m_display = new Wrapland::Server::Display(this);
81     m_display->setSocketName(s_socketName);
82     m_display->start();
83 
84     m_display->createShm();
85     m_serverSeat = m_display->createSeat(m_display);
86     m_serverSeat->setHasPointer(true);
87     m_serverCompositor = m_display->createCompositor(m_display);
88     m_pointerConstraintsServer = m_display->createPointerConstraints(m_display);
89 
90     // setup connection
91     m_connection = new Wrapland::Client::ConnectionThread;
92     QSignalSpy connectedSpy(m_connection, &ConnectionThread::establishedChanged);
93     QVERIFY(connectedSpy.isValid());
94     m_connection->setSocketName(s_socketName);
95 
96     m_thread = new QThread(this);
97     m_connection->moveToThread(m_thread);
98     m_thread->start();
99 
100     m_connection->establishConnection();
101     QVERIFY(connectedSpy.count() || connectedSpy.wait());
102     QCOMPARE(connectedSpy.count(), 1);
103 
104     m_queue = new EventQueue(this);
105     m_queue->setup(m_connection);
106 
107     Registry registry;
108     QSignalSpy interfacesAnnouncedSpy(&registry, &Registry::interfacesAnnounced);
109     QVERIFY(interfacesAnnouncedSpy.isValid());
110     QSignalSpy interfaceAnnouncedSpy(&registry, &Registry::interfaceAnnounced);
111     QVERIFY(interfaceAnnouncedSpy.isValid());
112     registry.setEventQueue(m_queue);
113     registry.create(m_connection);
114     QVERIFY(registry.isValid());
115     registry.setup();
116     QVERIFY(interfacesAnnouncedSpy.wait());
117 
118     m_compositor
119         = registry.createCompositor(registry.interface(Registry::Interface::Compositor).name,
120                                     registry.interface(Registry::Interface::Compositor).version,
121                                     this);
122     QVERIFY(m_compositor);
123     QVERIFY(m_compositor->isValid());
124 
125     m_pointerConstraints = registry.createPointerConstraints(
126         registry.interface(Registry::Interface::PointerConstraintsUnstableV1).name,
127         registry.interface(Registry::Interface::PointerConstraintsUnstableV1).version,
128         this);
129     QVERIFY(m_pointerConstraints);
130     QVERIFY(m_pointerConstraints->isValid());
131 
132     m_seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name,
133                                  registry.interface(Registry::Interface::Seat).version,
134                                  this);
135     QVERIFY(m_seat);
136     QVERIFY(m_seat->isValid());
137     QSignalSpy pointerChangedSpy(m_seat, &Wrapland::Client::Seat::hasPointerChanged);
138     QVERIFY(pointerChangedSpy.isValid());
139     QVERIFY(pointerChangedSpy.wait());
140     m_pointer = m_seat->createPointer(this);
141     QVERIFY(m_pointer);
142 }
143 
cleanup()144 void TestPointerConstraints::cleanup()
145 {
146 #define CLEANUP(variable)                                                                          \
147     if (variable) {                                                                                \
148         delete variable;                                                                           \
149         variable = nullptr;                                                                        \
150     }
151     CLEANUP(m_compositor)
152     CLEANUP(m_pointerConstraints)
153     CLEANUP(m_pointer)
154     CLEANUP(m_seat)
155     CLEANUP(m_queue)
156     if (m_connection) {
157         m_connection->deleteLater();
158         m_connection = nullptr;
159     }
160     if (m_thread) {
161         m_thread->quit();
162         m_thread->wait();
163         delete m_thread;
164         m_thread = nullptr;
165     }
166 
167     CLEANUP(m_serverCompositor)
168     CLEANUP(m_serverSeat);
169     CLEANUP(m_pointerConstraintsServer)
170     CLEANUP(m_display)
171 #undef CLEANUP
172 }
173 
testLockPointer_data()174 void TestPointerConstraints::testLockPointer_data()
175 {
176     QTest::addColumn<Wrapland::Client::PointerConstraints::LifeTime>("clientLifeTime");
177     QTest::addColumn<Wrapland::Server::LockedPointerV1::LifeTime>("serverLifeTime");
178     QTest::addColumn<bool>("hasConstraintAfterUnlock");
179     QTest::addColumn<int>("pointerChangedCount");
180 
181     QTest::newRow("persistent") << PointerConstraints::LifeTime::Persistent
182                                 << Wrapland::Server::LockedPointerV1::LifeTime::Persistent << true
183                                 << 1;
184     QTest::newRow("oneshot") << PointerConstraints::LifeTime::OneShot
185                              << Wrapland::Server::LockedPointerV1::LifeTime::OneShot << false << 2;
186 }
187 
testLockPointer()188 void TestPointerConstraints::testLockPointer()
189 {
190     // This test verifies the basic interaction for lock pointer.
191 
192     // First create a surface.
193     QSignalSpy surfaceCreatedSpy(m_serverCompositor, &Wrapland::Server::Compositor::surfaceCreated);
194     QVERIFY(surfaceCreatedSpy.isValid());
195     std::unique_ptr<Surface> surface(m_compositor->createSurface());
196     QVERIFY(surface->isValid());
197     QVERIFY(surfaceCreatedSpy.wait());
198 
199     auto serverSurface = surfaceCreatedSpy.first().first().value<Wrapland::Server::Surface*>();
200     QVERIFY(serverSurface);
201     QVERIFY(serverSurface->lockedPointer().isNull());
202     QVERIFY(serverSurface->confinedPointer().isNull());
203 
204     // Now create the locked pointer.
205     QSignalSpy pointerConstraintsChangedSpy(serverSurface,
206                                             &Wrapland::Server::Surface::pointerConstraintsChanged);
207     QVERIFY(pointerConstraintsChangedSpy.isValid());
208     QFETCH(PointerConstraints::LifeTime, clientLifeTime);
209 
210     std::unique_ptr<LockedPointer> lockedPointer(
211         m_pointerConstraints->lockPointer(surface.get(), m_pointer, nullptr, clientLifeTime));
212     QSignalSpy lockedSpy(lockedPointer.get(), &LockedPointer::locked);
213     QVERIFY(lockedSpy.isValid());
214     QSignalSpy unlockedSpy(lockedPointer.get(), &LockedPointer::unlocked);
215     QVERIFY(unlockedSpy.isValid());
216     QVERIFY(lockedPointer->isValid());
217     QVERIFY(pointerConstraintsChangedSpy.wait());
218 
219     auto serverLockedPointer = serverSurface->lockedPointer();
220     QVERIFY(serverLockedPointer);
221     QVERIFY(serverSurface->confinedPointer().isNull());
222 
223     QCOMPARE(serverLockedPointer->isLocked(), false);
224     QCOMPARE(serverLockedPointer->region(), QRegion());
225     QFETCH(Wrapland::Server::LockedPointerV1::LifeTime, serverLifeTime);
226     QCOMPARE(serverLockedPointer->lifeTime(), serverLifeTime);
227 
228     // Setting to unlocked now should not trigger an unlocked spy.
229     serverLockedPointer->setLocked(false);
230     QVERIFY(!unlockedSpy.wait(500));
231 
232     // Try setting a region.
233     QSignalSpy regionChangedSpy(serverLockedPointer.data(),
234                                 &Wrapland::Server::LockedPointerV1::regionChanged);
235     QVERIFY(regionChangedSpy.isValid());
236     lockedPointer->setRegion(m_compositor->createRegion(QRegion(0, 5, 10, 20), m_compositor));
237 
238     // It's double buffered.
239     QVERIFY(!regionChangedSpy.wait(500));
240     surface->commit(Surface::CommitFlag::None);
241     QVERIFY(regionChangedSpy.wait());
242     QCOMPARE(serverLockedPointer->region(), QRegion(0, 5, 10, 20));
243 
244     // And unset region again.
245     lockedPointer->setRegion(nullptr);
246     surface->commit(Surface::CommitFlag::None);
247     QVERIFY(regionChangedSpy.wait());
248     QCOMPARE(serverLockedPointer->region(), QRegion());
249 
250     // Let's lock the surface.
251     QSignalSpy lockedChangedSpy(serverLockedPointer.data(),
252                                 &Wrapland::Server::LockedPointerV1::lockedChanged);
253     QVERIFY(lockedChangedSpy.isValid());
254     m_serverSeat->pointers().set_focused_surface(serverSurface);
255 
256     QSignalSpy pointerMotionSpy(m_pointer, &Wrapland::Client::Pointer::motion);
257     QVERIFY(pointerMotionSpy.isValid());
258     m_serverSeat->pointers().set_position(QPoint(0, 1));
259     QVERIFY(pointerMotionSpy.wait());
260 
261     serverLockedPointer->setLocked(true);
262     QCOMPARE(serverLockedPointer->isLocked(), true);
263     m_serverSeat->pointers().set_position(QPoint(1, 1));
264     QCOMPARE(lockedChangedSpy.count(), 1);
265     QCOMPARE(pointerMotionSpy.count(), 1);
266     QVERIFY(lockedSpy.isEmpty());
267     QVERIFY(lockedSpy.wait());
268     QVERIFY(unlockedSpy.isEmpty());
269 
270     const QPointF hint = QPointF(1.5, 0.5);
271     QSignalSpy hintChangedSpy(serverLockedPointer.data(),
272                               &Wrapland::Server::LockedPointerV1::cursorPositionHintChanged);
273     lockedPointer->setCursorPositionHint(hint);
274     QCOMPARE(serverLockedPointer->cursorPositionHint(), QPointF(-1., -1.));
275     surface->commit(Surface::CommitFlag::None);
276     QVERIFY(hintChangedSpy.wait());
277     QCOMPARE(serverLockedPointer->cursorPositionHint(), hint);
278 
279     // And unlock again.
280     serverLockedPointer->setLocked(false);
281     QCOMPARE(serverLockedPointer->isLocked(), false);
282     QCOMPARE(serverLockedPointer->cursorPositionHint(), QPointF(-1., -1.));
283     QCOMPARE(lockedChangedSpy.count(), 2);
284     QTEST(!serverSurface->lockedPointer().isNull(), "hasConstraintAfterUnlock");
285     QTEST(pointerConstraintsChangedSpy.count(), "pointerChangedCount");
286     QVERIFY(unlockedSpy.wait());
287     QCOMPARE(unlockedSpy.count(), 1);
288     QCOMPARE(lockedSpy.count(), 1);
289 
290     // Now motion should work again.
291     m_serverSeat->pointers().set_position(QPoint(0, 1));
292     QVERIFY(pointerMotionSpy.wait());
293     QCOMPARE(pointerMotionSpy.count(), 2);
294 
295     QSignalSpy destroyedSpy(serverLockedPointer.data(),
296                             &Wrapland::Server::LockedPointerV1::resourceDestroyed);
297     QVERIFY(destroyedSpy.isValid());
298     lockedPointer.reset();
299     QVERIFY(destroyedSpy.wait());
300     QCOMPARE(pointerConstraintsChangedSpy.count(), 2);
301 }
302 
testConfinePointer_data()303 void TestPointerConstraints::testConfinePointer_data()
304 {
305     QTest::addColumn<Wrapland::Client::PointerConstraints::LifeTime>("clientLifeTime");
306     QTest::addColumn<Wrapland::Server::ConfinedPointerV1::LifeTime>("serverLifeTime");
307     QTest::addColumn<bool>("hasConstraintAfterUnlock");
308     QTest::addColumn<int>("pointerChangedCount");
309 
310     QTest::newRow("persistent") << PointerConstraints::LifeTime::Persistent
311                                 << Wrapland::Server::ConfinedPointerV1::LifeTime::Persistent << true
312                                 << 1;
313     QTest::newRow("oneshot") << PointerConstraints::LifeTime::OneShot
314                              << Wrapland::Server::ConfinedPointerV1::LifeTime::OneShot << false
315                              << 2;
316 }
317 
testConfinePointer()318 void TestPointerConstraints::testConfinePointer()
319 {
320     // This test verifies the basic interaction for confined pointer.
321 
322     // First create a surface.
323     QSignalSpy surfaceCreatedSpy(m_serverCompositor, &Wrapland::Server::Compositor::surfaceCreated);
324     QVERIFY(surfaceCreatedSpy.isValid());
325 
326     std::unique_ptr<Surface> surface(m_compositor->createSurface());
327     QVERIFY(surface->isValid());
328     QVERIFY(surfaceCreatedSpy.wait());
329 
330     auto serverSurface = surfaceCreatedSpy.first().first().value<Wrapland::Server::Surface*>();
331     QVERIFY(serverSurface);
332     QVERIFY(serverSurface->lockedPointer().isNull());
333     QVERIFY(serverSurface->confinedPointer().isNull());
334 
335     // Now create the confined pointer.
336     QSignalSpy pointerConstraintsChangedSpy(serverSurface,
337                                             &Wrapland::Server::Surface::pointerConstraintsChanged);
338     QVERIFY(pointerConstraintsChangedSpy.isValid());
339     QFETCH(PointerConstraints::LifeTime, clientLifeTime);
340 
341     std::unique_ptr<ConfinedPointer> confinedPointer(
342         m_pointerConstraints->confinePointer(surface.get(), m_pointer, nullptr, clientLifeTime));
343     QSignalSpy confinedSpy(confinedPointer.get(), &ConfinedPointer::confined);
344     QVERIFY(confinedSpy.isValid());
345     QSignalSpy unconfinedSpy(confinedPointer.get(), &ConfinedPointer::unconfined);
346     QVERIFY(unconfinedSpy.isValid());
347     QVERIFY(confinedPointer->isValid());
348     QVERIFY(pointerConstraintsChangedSpy.wait());
349 
350     auto serverConfinedPointer = serverSurface->confinedPointer();
351     QVERIFY(serverConfinedPointer);
352     QVERIFY(serverSurface->lockedPointer().isNull());
353 
354     QCOMPARE(serverConfinedPointer->isConfined(), false);
355     QCOMPARE(serverConfinedPointer->region(), QRegion());
356     QFETCH(Wrapland::Server::ConfinedPointerV1::LifeTime, serverLifeTime);
357     QCOMPARE(serverConfinedPointer->lifeTime(), serverLifeTime);
358 
359     // Setting to unconfined now should not trigger an unconfined spy.
360     serverConfinedPointer->setConfined(false);
361     QVERIFY(!unconfinedSpy.wait(500));
362 
363     // Try setting a region.
364     QSignalSpy destroyedSpy(serverConfinedPointer.data(),
365                             &Wrapland::Server::ConfinedPointerV1::resourceDestroyed);
366     QVERIFY(destroyedSpy.isValid());
367     QSignalSpy regionChangedSpy(serverConfinedPointer.data(),
368                                 &Wrapland::Server::ConfinedPointerV1::regionChanged);
369     QVERIFY(regionChangedSpy.isValid());
370     confinedPointer->setRegion(m_compositor->createRegion(QRegion(0, 5, 10, 20), m_compositor));
371 
372     // It's double buffered.
373     QVERIFY(!regionChangedSpy.wait(500));
374     surface->commit(Surface::CommitFlag::None);
375     QVERIFY(regionChangedSpy.wait());
376     QCOMPARE(serverConfinedPointer->region(), QRegion(0, 5, 10, 20));
377 
378     // And unset region again.
379     confinedPointer->setRegion(nullptr);
380     surface->commit(Surface::CommitFlag::None);
381     QVERIFY(regionChangedSpy.wait());
382     QCOMPARE(serverConfinedPointer->region(), QRegion());
383 
384     // Let's confine the surface.
385     QSignalSpy confinedChangedSpy(serverConfinedPointer.data(),
386                                   &Wrapland::Server::ConfinedPointerV1::confinedChanged);
387     QVERIFY(confinedChangedSpy.isValid());
388     m_serverSeat->pointers().set_focused_surface(serverSurface);
389     serverConfinedPointer->setConfined(true);
390     QCOMPARE(serverConfinedPointer->isConfined(), true);
391     QCOMPARE(confinedChangedSpy.count(), 1);
392     QVERIFY(confinedSpy.isEmpty());
393     QVERIFY(confinedSpy.wait());
394     QVERIFY(unconfinedSpy.isEmpty());
395 
396     // And unconfine again.
397     serverConfinedPointer->setConfined(false);
398     QCOMPARE(serverConfinedPointer->isConfined(), false);
399     QCOMPARE(confinedChangedSpy.count(), 2);
400     QTEST(!serverSurface->confinedPointer().isNull(), "hasConstraintAfterUnlock");
401     QTEST(pointerConstraintsChangedSpy.count(), "pointerChangedCount");
402     QVERIFY(unconfinedSpy.wait());
403     QCOMPARE(unconfinedSpy.count(), 1);
404     QCOMPARE(confinedSpy.count(), 1);
405 
406     confinedPointer.reset();
407     QVERIFY(destroyedSpy.wait());
408     QCOMPARE(pointerConstraintsChangedSpy.count(), 2);
409 }
410 
411 enum class Constraint { Lock, Confine };
412 
Q_DECLARE_METATYPE(Constraint)413 Q_DECLARE_METATYPE(Constraint)
414 
415 void TestPointerConstraints::testAlreadyConstrained_data()
416 {
417     QTest::addColumn<Constraint>("firstConstraint");
418     QTest::addColumn<Constraint>("secondConstraint");
419 
420     QTest::newRow("confine-confine") << Constraint::Confine << Constraint::Confine;
421     QTest::newRow("lock-confine") << Constraint::Lock << Constraint::Confine;
422     QTest::newRow("confine-lock") << Constraint::Confine << Constraint::Lock;
423     QTest::newRow("lock-lock") << Constraint::Lock << Constraint::Lock;
424 }
425 
testAlreadyConstrained()426 void TestPointerConstraints::testAlreadyConstrained()
427 {
428     // this test verifies that creating a pointer constraint for an already constrained surface
429     // triggers an error first create a surface
430     std::unique_ptr<Surface> surface(m_compositor->createSurface());
431     QVERIFY(surface->isValid());
432     QFETCH(Constraint, firstConstraint);
433     std::unique_ptr<ConfinedPointer> confinedPointer;
434     std::unique_ptr<LockedPointer> lockedPointer;
435     switch (firstConstraint) {
436     case Constraint::Lock:
437         lockedPointer.reset(m_pointerConstraints->lockPointer(
438             surface.get(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot));
439         break;
440     case Constraint::Confine:
441         confinedPointer.reset(m_pointerConstraints->confinePointer(
442             surface.get(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot));
443         break;
444     default:
445         Q_UNREACHABLE();
446     }
447     QVERIFY(confinedPointer || lockedPointer);
448 
449     QSignalSpy connectionSpy(m_connection, &ConnectionThread::establishedChanged);
450     QVERIFY(connectionSpy.isValid());
451     QFETCH(Constraint, secondConstraint);
452     std::unique_ptr<ConfinedPointer> confinedPointer2;
453     std::unique_ptr<LockedPointer> lockedPointer2;
454     switch (secondConstraint) {
455     case Constraint::Lock:
456         lockedPointer2.reset(m_pointerConstraints->lockPointer(
457             surface.get(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot));
458         break;
459     case Constraint::Confine:
460         confinedPointer2.reset(m_pointerConstraints->confinePointer(
461             surface.get(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot));
462         break;
463     default:
464         Q_UNREACHABLE();
465     }
466     QVERIFY(connectionSpy.wait());
467     QVERIFY(m_connection->protocolError());
468     if (confinedPointer2) {
469         confinedPointer2->release();
470     }
471     if (lockedPointer2) {
472         lockedPointer2->release();
473     }
474     if (confinedPointer) {
475         confinedPointer->release();
476     }
477     if (lockedPointer) {
478         lockedPointer->release();
479     }
480     surface->release();
481     m_compositor->release();
482     m_pointerConstraints->release();
483     m_pointer->release();
484     m_seat->release();
485     m_queue->release();
486 }
487 
488 QTEST_GUILESS_MAIN(TestPointerConstraints)
489 #include "pointer_constraints.moc"
490