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(®istry, &Registry::interfacesAnnounced);
109 QVERIFY(interfacesAnnouncedSpy.isValid());
110 QSignalSpy interfaceAnnouncedSpy(®istry, &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