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