KWin
Loading...
Searching...
No Matches
test_tablet_interface.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@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 <QHash>
8#include <QSignalSpy>
9#include <QTest>
10#include <QThread>
11// WaylandServer
12#include "wayland/compositor.h"
13#include "wayland/display.h"
14#include "wayland/seat.h"
15#include "wayland/tablet_v2.h"
16
17#include "KWayland/Client/compositor.h"
18#include "KWayland/Client/connection_thread.h"
19#include "KWayland/Client/event_queue.h"
20#include "KWayland/Client/registry.h"
21#include "KWayland/Client/seat.h"
22#include "KWayland/Client/surface.h"
23
24#include "qwayland-tablet-unstable-v2.h"
25
26using namespace KWin;
27using namespace std::literals;
28
29class Tablet : public QtWayland::zwp_tablet_v2
30{
31public:
32 Tablet(::zwp_tablet_v2 *t)
33 : QtWayland::zwp_tablet_v2(t)
34 {
35 }
36};
37
38class TabletPad : public QObject, public QtWayland::zwp_tablet_pad_v2
39{
40 Q_OBJECT
41public:
42 TabletPad(::zwp_tablet_pad_v2 *t)
43 : QtWayland::zwp_tablet_pad_v2(t)
44 {
45 }
46
47 void zwp_tablet_pad_v2_done() override
48 {
49 Q_ASSERT(!doneCalled);
50 doneCalled = true;
51 }
52
53 void zwp_tablet_pad_v2_buttons(uint32_t buttons) override
54 {
55 Q_ASSERT(buttons == 1);
56 }
57
58 void zwp_tablet_pad_v2_enter(uint32_t /*serial*/, struct ::zwp_tablet_v2 * /*tablet*/, struct ::wl_surface *surface) override
59 {
60 m_currentSurface = surface;
61 }
62
63 void zwp_tablet_pad_v2_button(uint32_t /*time*/, uint32_t button, uint32_t state) override
64 {
65 buttonStates[m_currentSurface][button] = state;
66 Q_EMIT buttonReceived();
67 }
68
69 ::wl_surface *m_currentSurface = nullptr;
70
71 bool doneCalled = false;
72 QHash<::wl_surface *, QHash<uint32_t, uint32_t>> buttonStates;
73
74Q_SIGNALS:
76};
77
78class Tool : public QObject, public QtWayland::zwp_tablet_tool_v2
79{
80 Q_OBJECT
81public:
82 Tool(::zwp_tablet_tool_v2 *t)
83 : QtWayland::zwp_tablet_tool_v2(t)
84 {
85 }
86
87 void zwp_tablet_tool_v2_proximity_in(uint32_t /*serial*/, struct ::zwp_tablet_v2 * /*tablet*/, struct ::wl_surface *surface) override
88 {
89 surfaceApproximated[surface]++;
90 }
91
92 void zwp_tablet_tool_v2_frame(uint32_t time) override
93 {
94 Q_EMIT frame(time);
95 }
96
97 QHash<struct ::wl_surface *, int> surfaceApproximated;
98Q_SIGNALS:
99 void frame(quint32 time);
100};
101
102class TabletSeat : public QObject, public QtWayland::zwp_tablet_seat_v2
103{
104 Q_OBJECT
105public:
106 TabletSeat(::zwp_tablet_seat_v2 *seat)
107 : QtWayland::zwp_tablet_seat_v2(seat)
108 {
109 }
110
111 void zwp_tablet_seat_v2_tablet_added(struct ::zwp_tablet_v2 *id) override
112 {
113 m_tablets << new Tablet(id);
114 Q_EMIT tabletAdded();
115 }
116 void zwp_tablet_seat_v2_tool_added(struct ::zwp_tablet_tool_v2 *id) override
117 {
118 m_tools << new Tool(id);
119 Q_EMIT toolAdded();
120 }
121
122 void zwp_tablet_seat_v2_pad_added(struct ::zwp_tablet_pad_v2 *id) override
123 {
124 m_pads << new TabletPad(id);
125 Q_EMIT padAdded();
126 }
127
128 QList<Tablet *> m_tablets;
129 QList<TabletPad *> m_pads;
130 QList<Tool *> m_tools;
131
132Q_SIGNALS:
133 void padAdded();
134 void toolAdded();
136};
137
138class TestTabletInterface : public QObject
139{
140 Q_OBJECT
141public:
143 {
144 }
145 ~TestTabletInterface() override;
146
147private Q_SLOTS:
148 void initTestCase();
149 void testAdd();
150 void testAddPad();
151 void testInteractSimple_data();
152 void testInteractSimple();
153 void testInteractSurfaceChange_data();
154 void testInteractSurfaceChange();
155
156private:
157 KWayland::Client::ConnectionThread *m_connection;
158 KWayland::Client::EventQueue *m_queue;
159 KWayland::Client::Compositor *m_clientCompositor;
160 KWayland::Client::Seat *m_clientSeat = nullptr;
161
162 QThread *m_thread;
163 KWin::Display m_display;
164 SeatInterface *m_seat;
165 CompositorInterface *m_serverCompositor;
166
167 TabletSeat *m_tabletSeatClient = nullptr;
168 TabletSeat *m_tabletSeatClient2 = nullptr;
169
170 TabletManagerV2Interface *m_tabletManager;
171 QList<KWayland::Client::Surface *> m_surfacesClient;
172
173 TabletV2Interface *m_tablet;
174 TabletPadV2Interface *m_tabletPad = nullptr;
175 TabletToolV2Interface *m_tool;
176
177 QList<SurfaceInterface *> m_surfaces;
178};
179
180static const QString s_socketName = QStringLiteral("kwin-wayland-server-tablet-test-0");
181
182void TestTabletInterface::initTestCase()
183{
184 m_display.addSocketName(s_socketName);
185 m_display.start();
186 QVERIFY(m_display.isRunning());
187
188 m_seat = new SeatInterface(&m_display, this);
189 m_serverCompositor = new CompositorInterface(&m_display, this);
190 m_tabletManager = new TabletManagerV2Interface(&m_display, this);
191
192 connect(m_serverCompositor, &CompositorInterface::surfaceCreated, this, [this](SurfaceInterface *surface) {
193 m_surfaces += surface;
194 });
195
196 // setup connection
197 m_connection = new KWayland::Client::ConnectionThread;
198 QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected);
199 m_connection->setSocketName(s_socketName);
200
201 m_thread = new QThread(this);
202 m_connection->moveToThread(m_thread);
203 m_thread->start();
204
205 m_connection->initConnection();
206 QVERIFY(connectedSpy.wait());
207 QVERIFY(!m_connection->connections().isEmpty());
208
209 m_queue = new KWayland::Client::EventQueue(this);
210 QVERIFY(!m_queue->isValid());
211 m_queue->setup(m_connection);
212 QVERIFY(m_queue->isValid());
213
214 auto registry = new KWayland::Client::Registry(this);
215 connect(registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, registry](const QByteArray &interface, quint32 name, quint32 version) {
216 if (interface == "zwp_tablet_manager_v2") {
217 auto tabletClient = new QtWayland::zwp_tablet_manager_v2(registry->registry(), name, version);
218 auto _seat = tabletClient->get_tablet_seat(*m_clientSeat);
219 m_tabletSeatClient = new TabletSeat(_seat);
220 auto _seat2 = tabletClient->get_tablet_seat(*m_clientSeat);
221 m_tabletSeatClient2 = new TabletSeat(_seat2);
222 }
223 });
224 connect(registry, &KWayland::Client::Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) {
225 m_clientSeat = registry->createSeat(name, version);
226 });
227 registry->setEventQueue(m_queue);
228 QSignalSpy compositorSpy(registry, &KWayland::Client::Registry::compositorAnnounced);
229 registry->create(m_connection->display());
230 QVERIFY(registry->isValid());
231 registry->setup();
232 wl_display_flush(m_connection->display());
233
234 QVERIFY(compositorSpy.wait());
235 m_clientCompositor = registry->createCompositor(compositorSpy.first().first().value<quint32>(), compositorSpy.first().last().value<quint32>(), this);
236 QVERIFY(m_clientCompositor->isValid());
237
238 QSignalSpy surfaceSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
239 for (int i = 0; i < 3; ++i) {
240 m_surfacesClient += m_clientCompositor->createSurface(this);
241 }
242 QVERIFY(surfaceSpy.count() < 3 && surfaceSpy.wait(200));
243 QVERIFY(m_surfaces.count() == 3);
244 QVERIFY(m_tabletSeatClient);
245}
246
248{
249 if (m_queue) {
250 delete m_queue;
251 m_queue = nullptr;
252 }
253 if (m_thread) {
254 m_thread->quit();
255 m_thread->wait();
256 delete m_thread;
257 m_thread = nullptr;
258 }
259 delete m_tabletSeatClient;
260 delete m_tabletSeatClient2;
261 m_connection->deleteLater();
262 m_connection = nullptr;
263}
264
265void TestTabletInterface::testAdd()
266{
267 TabletSeatV2Interface *seatInterface = m_tabletManager->seat(m_seat);
268 QVERIFY(seatInterface);
269
270 QSignalSpy tabletSpy(m_tabletSeatClient, &TabletSeat::tabletAdded);
271 m_tablet = seatInterface->addTablet(1, 2, QStringLiteral("event33"), QStringLiteral("my tablet"), {QStringLiteral("/test/event33")});
272 QVERIFY(m_tablet);
273 QVERIFY(tabletSpy.wait() || tabletSpy.count() == 1);
274 QCOMPARE(m_tabletSeatClient->m_tablets.count(), 1);
275
276 QSignalSpy toolSpy(m_tabletSeatClient, &TabletSeat::toolAdded);
278 QVERIFY(m_tool);
279 QVERIFY(toolSpy.wait() || toolSpy.count() == 1);
280 QCOMPARE(m_tabletSeatClient->m_tools.count(), 1);
281
282 QVERIFY(!m_tool->isClientSupported()); // There's no surface in it yet
283 m_tool->setCurrentSurface(nullptr);
284 QVERIFY(!m_tool->isClientSupported()); // There's no surface in it
285
286 QCOMPARE(m_surfaces.count(), 3);
287 for (SurfaceInterface *surface : m_surfaces) {
288 m_tool->setCurrentSurface(surface);
289 }
290 m_tool->setCurrentSurface(nullptr);
291}
292
293void TestTabletInterface::testAddPad()
294{
295 TabletSeatV2Interface *seatInterface = m_tabletManager->seat(m_seat);
296 QVERIFY(seatInterface);
297
298 QSignalSpy tabletPadSpy(m_tabletSeatClient, &TabletSeat::padAdded);
299 m_tabletPad =
300 seatInterface->addTabletPad(QStringLiteral("my tablet pad"), QStringLiteral("tabletpad"), {QStringLiteral("/test/event33")}, 1, 1, 1, 1, 0, m_tablet);
301 QVERIFY(m_tabletPad);
302 QVERIFY(tabletPadSpy.wait() || tabletPadSpy.count() == 1);
303 QCOMPARE(m_tabletSeatClient->m_pads.count(), 1);
304 QVERIFY(m_tabletSeatClient->m_pads[0]);
305
306 QVERIFY(m_tabletPad->ring(0));
307 QVERIFY(m_tabletPad->strip(0));
308
309 QCOMPARE(m_surfaces.count(), 3);
310 QVERIFY(m_tabletSeatClient->m_pads[0]->buttonStates.isEmpty());
311 QSignalSpy buttonSpy(m_tabletSeatClient->m_pads[0], &TabletPad::buttonReceived);
312 m_tabletPad->setCurrentSurface(m_surfaces[0], m_tablet);
313 m_tabletPad->sendButton(123ms, 0, QtWayland::zwp_tablet_pad_v2::button_state_pressed);
314 QVERIFY(buttonSpy.count() || buttonSpy.wait(100));
315 QCOMPARE(m_tabletSeatClient->m_pads[0]->doneCalled, true);
316 QCOMPARE(m_tabletSeatClient->m_pads[0]->buttonStates.count(), 1);
317 QCOMPARE(m_tabletSeatClient->m_pads[0]->buttonStates[*m_surfacesClient[0]][0], QtWayland::zwp_tablet_pad_v2::button_state_pressed);
318}
319
320static uint s_serial = 0;
321
322void TestTabletInterface::testInteractSimple_data()
323{
324 QTest::addColumn<TabletSeat *>("tabletSeatClient");
325 QTest::newRow("first client") << m_tabletSeatClient;
326 QTest::newRow("second client") << m_tabletSeatClient2;
327}
328
329void TestTabletInterface::testInteractSimple()
330{
331 QFETCH(TabletSeat *, tabletSeatClient);
332 tabletSeatClient->m_tools[0]->surfaceApproximated.clear();
333 QSignalSpy frameSpy(tabletSeatClient->m_tools[0], &Tool::frame);
334
335 QVERIFY(!m_tool->isClientSupported());
336 m_tool->setCurrentSurface(m_surfaces[0]);
337 QVERIFY(m_tool->isClientSupported() && m_tablet->isSurfaceSupported(m_surfaces[0]));
338 m_tool->sendProximityIn(m_tablet);
339 m_tool->sendPressure(0);
340 m_tool->sendFrame(s_serial++);
341 m_tool->sendMotion({3, 3});
342 m_tool->sendFrame(s_serial++);
343 m_tool->sendProximityOut();
344 QVERIFY(m_tool->isClientSupported());
345 m_tool->sendFrame(s_serial++);
346 QVERIFY(!m_tool->isClientSupported());
347
348 QVERIFY(frameSpy.wait(500));
349 QCOMPARE(tabletSeatClient->m_tools[0]->surfaceApproximated.count(), 1);
350}
351
352void TestTabletInterface::testInteractSurfaceChange_data()
353{
354 QTest::addColumn<TabletSeat *>("tabletSeatClient");
355 QTest::newRow("first client") << m_tabletSeatClient;
356 QTest::newRow("second client") << m_tabletSeatClient2;
357}
358
359void TestTabletInterface::testInteractSurfaceChange()
360{
361 QFETCH(TabletSeat *, tabletSeatClient);
362 tabletSeatClient->m_tools[0]->surfaceApproximated.clear();
363 QSignalSpy frameSpy(tabletSeatClient->m_tools[0], &Tool::frame);
364
365 QVERIFY(!m_tool->isClientSupported());
366 m_tool->setCurrentSurface(m_surfaces[0]);
367 QVERIFY(m_tool->isClientSupported() && m_tablet->isSurfaceSupported(m_surfaces[0]));
368 m_tool->sendProximityIn(m_tablet);
369 m_tool->sendPressure(0);
370 m_tool->sendFrame(s_serial++);
371
372 m_tool->setCurrentSurface(m_surfaces[1]);
373 QVERIFY(m_tool->isClientSupported());
374
375 m_tool->sendMotion({3, 3});
376 m_tool->sendFrame(s_serial++);
377 m_tool->sendProximityOut();
378 QVERIFY(m_tool->isClientSupported());
379 m_tool->sendFrame(s_serial++);
380 QVERIFY(!m_tool->isClientSupported());
381
382 QVERIFY(frameSpy.wait(500));
383 QCOMPARE(tabletSeatClient->m_tools[0]->surfaceApproximated.count(), 2);
384}
385
386QTEST_GUILESS_MAIN(TestTabletInterface)
387#include "test_tablet_interface.moc"
void surfaceCreated(KWin::SurfaceInterface *surface)
Class holding the Wayland server display loop.
Definition display.h:34
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
Resource representing a wl_surface.
Definition surface.h:80
TabletSeatV2Interface * seat(SeatInterface *seat) const
TabletPadRingV2Interface * ring(uint at) const
void sendButton(std::chrono::microseconds time, quint32 button, bool pressed)
TabletPadStripV2Interface * strip(uint at) const
void setCurrentSurface(SurfaceInterface *surface, TabletV2Interface *tablet)
TabletPadV2Interface * addTabletPad(const QString &sysname, const QString &name, const QStringList &paths, quint32 buttons, quint32 rings, quint32 strips, quint32 modes, quint32 currentMode, TabletV2Interface *tablet)
TabletToolV2Interface * addTool(TabletToolV2Interface::Type type, quint64 hardwareSerial, quint64 hardwareId, const QList< TabletToolV2Interface::Capability > &capabilities, const QString &deviceSysName)
TabletV2Interface * addTablet(quint32 vendorId, quint32 productId, const QString &sysname, const QString &name, const QStringList &paths)
void sendMotion(const QPointF &pos)
void sendProximityIn(TabletV2Interface *tablet)
void sendPressure(quint32 pressure)
void sendFrame(quint32 time)
@ Pressure
Pressure axis.
Definition tablet_v2.h:100
void setCurrentSurface(SurfaceInterface *surface)
bool isSurfaceSupported(SurfaceInterface *surface) const
Definition tablet_v2.cpp:66
Tablet(::zwp_tablet_v2 *t)
void zwp_tablet_pad_v2_buttons(uint32_t buttons) override
void zwp_tablet_pad_v2_done() override
QHash<::wl_surface *, QHash< uint32_t, uint32_t > > buttonStates
void zwp_tablet_pad_v2_button(uint32_t, uint32_t button, uint32_t state) override
TabletPad(::zwp_tablet_pad_v2 *t)
void buttonReceived()
::wl_surface * m_currentSurface
void zwp_tablet_pad_v2_enter(uint32_t, struct ::zwp_tablet_v2 *, struct ::wl_surface *surface) override
void zwp_tablet_seat_v2_tool_added(struct ::zwp_tablet_tool_v2 *id) override
void padAdded()
void toolAdded()
void tabletAdded()
void zwp_tablet_seat_v2_pad_added(struct ::zwp_tablet_pad_v2 *id) override
TabletSeat(::zwp_tablet_seat_v2 *seat)
QList< TabletPad * > m_pads
void zwp_tablet_seat_v2_tablet_added(struct ::zwp_tablet_v2 *id) override
QList< Tool * > m_tools
QList< Tablet * > m_tablets
Tool(::zwp_tablet_tool_v2 *t)
void zwp_tablet_tool_v2_proximity_in(uint32_t, struct ::zwp_tablet_v2 *, struct ::wl_surface *surface) override
void zwp_tablet_tool_v2_frame(uint32_t time) override
void frame(quint32 time)
QHash< struct ::wl_surface *, int > surfaceApproximated
KWayland::Client::Registry * registry
constexpr int version