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