File indexing completed on 2024-11-10 04:56:22
0001 /* 0002 SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 // Qt 0007 #include <QHash> 0008 #include <QSignalSpy> 0009 #include <QTest> 0010 #include <QThread> 0011 // WaylandServer 0012 #include "wayland/compositor.h" 0013 #include "wayland/display.h" 0014 #include "wayland/seat.h" 0015 #include "wayland/tablet_v2.h" 0016 0017 #include "KWayland/Client/compositor.h" 0018 #include "KWayland/Client/connection_thread.h" 0019 #include "KWayland/Client/event_queue.h" 0020 #include "KWayland/Client/registry.h" 0021 #include "KWayland/Client/seat.h" 0022 #include "KWayland/Client/surface.h" 0023 0024 #include "qwayland-tablet-unstable-v2.h" 0025 0026 using namespace KWin; 0027 using namespace std::literals; 0028 0029 class Tablet : public QtWayland::zwp_tablet_v2 0030 { 0031 public: 0032 Tablet(::zwp_tablet_v2 *t) 0033 : QtWayland::zwp_tablet_v2(t) 0034 { 0035 } 0036 }; 0037 0038 class TabletPad : public QObject, public QtWayland::zwp_tablet_pad_v2 0039 { 0040 Q_OBJECT 0041 public: 0042 TabletPad(::zwp_tablet_pad_v2 *t) 0043 : QtWayland::zwp_tablet_pad_v2(t) 0044 { 0045 } 0046 0047 void zwp_tablet_pad_v2_done() override 0048 { 0049 Q_ASSERT(!doneCalled); 0050 doneCalled = true; 0051 } 0052 0053 void zwp_tablet_pad_v2_buttons(uint32_t buttons) override 0054 { 0055 Q_ASSERT(buttons == 1); 0056 } 0057 0058 void zwp_tablet_pad_v2_enter(uint32_t /*serial*/, struct ::zwp_tablet_v2 * /*tablet*/, struct ::wl_surface *surface) override 0059 { 0060 m_currentSurface = surface; 0061 } 0062 0063 void zwp_tablet_pad_v2_button(uint32_t /*time*/, uint32_t button, uint32_t state) override 0064 { 0065 buttonStates[m_currentSurface][button] = state; 0066 Q_EMIT buttonReceived(); 0067 } 0068 0069 ::wl_surface *m_currentSurface = nullptr; 0070 0071 bool doneCalled = false; 0072 QHash<::wl_surface *, QHash<uint32_t, uint32_t>> buttonStates; 0073 0074 Q_SIGNALS: 0075 void buttonReceived(); 0076 }; 0077 0078 class Tool : public QObject, public QtWayland::zwp_tablet_tool_v2 0079 { 0080 Q_OBJECT 0081 public: 0082 Tool(::zwp_tablet_tool_v2 *t) 0083 : QtWayland::zwp_tablet_tool_v2(t) 0084 { 0085 } 0086 0087 void zwp_tablet_tool_v2_proximity_in(uint32_t /*serial*/, struct ::zwp_tablet_v2 * /*tablet*/, struct ::wl_surface *surface) override 0088 { 0089 surfaceApproximated[surface]++; 0090 } 0091 0092 void zwp_tablet_tool_v2_frame(uint32_t time) override 0093 { 0094 Q_EMIT frame(time); 0095 } 0096 0097 QHash<struct ::wl_surface *, int> surfaceApproximated; 0098 Q_SIGNALS: 0099 void frame(quint32 time); 0100 }; 0101 0102 class TabletSeat : public QObject, public QtWayland::zwp_tablet_seat_v2 0103 { 0104 Q_OBJECT 0105 public: 0106 TabletSeat(::zwp_tablet_seat_v2 *seat) 0107 : QtWayland::zwp_tablet_seat_v2(seat) 0108 { 0109 } 0110 0111 void zwp_tablet_seat_v2_tablet_added(struct ::zwp_tablet_v2 *id) override 0112 { 0113 m_tablets << new Tablet(id); 0114 Q_EMIT tabletAdded(); 0115 } 0116 void zwp_tablet_seat_v2_tool_added(struct ::zwp_tablet_tool_v2 *id) override 0117 { 0118 m_tools << new Tool(id); 0119 Q_EMIT toolAdded(); 0120 } 0121 0122 void zwp_tablet_seat_v2_pad_added(struct ::zwp_tablet_pad_v2 *id) override 0123 { 0124 m_pads << new TabletPad(id); 0125 Q_EMIT padAdded(); 0126 } 0127 0128 QList<Tablet *> m_tablets; 0129 QList<TabletPad *> m_pads; 0130 QList<Tool *> m_tools; 0131 0132 Q_SIGNALS: 0133 void padAdded(); 0134 void toolAdded(); 0135 void tabletAdded(); 0136 }; 0137 0138 class TestTabletInterface : public QObject 0139 { 0140 Q_OBJECT 0141 public: 0142 TestTabletInterface() 0143 { 0144 } 0145 ~TestTabletInterface() override; 0146 0147 private Q_SLOTS: 0148 void initTestCase(); 0149 void testAdd(); 0150 void testAddPad(); 0151 void testInteractSimple_data(); 0152 void testInteractSimple(); 0153 void testInteractSurfaceChange_data(); 0154 void testInteractSurfaceChange(); 0155 0156 private: 0157 KWayland::Client::ConnectionThread *m_connection; 0158 KWayland::Client::EventQueue *m_queue; 0159 KWayland::Client::Compositor *m_clientCompositor; 0160 KWayland::Client::Seat *m_clientSeat = nullptr; 0161 0162 QThread *m_thread; 0163 KWin::Display m_display; 0164 SeatInterface *m_seat; 0165 CompositorInterface *m_serverCompositor; 0166 0167 TabletSeat *m_tabletSeatClient = nullptr; 0168 TabletSeat *m_tabletSeatClient2 = nullptr; 0169 0170 TabletManagerV2Interface *m_tabletManager; 0171 QList<KWayland::Client::Surface *> m_surfacesClient; 0172 0173 TabletV2Interface *m_tablet; 0174 TabletPadV2Interface *m_tabletPad = nullptr; 0175 TabletToolV2Interface *m_tool; 0176 0177 QList<SurfaceInterface *> m_surfaces; 0178 }; 0179 0180 static const QString s_socketName = QStringLiteral("kwin-wayland-server-tablet-test-0"); 0181 0182 void TestTabletInterface::initTestCase() 0183 { 0184 m_display.addSocketName(s_socketName); 0185 m_display.start(); 0186 QVERIFY(m_display.isRunning()); 0187 0188 m_seat = new SeatInterface(&m_display, this); 0189 m_serverCompositor = new CompositorInterface(&m_display, this); 0190 m_tabletManager = new TabletManagerV2Interface(&m_display, this); 0191 0192 connect(m_serverCompositor, &CompositorInterface::surfaceCreated, this, [this](SurfaceInterface *surface) { 0193 m_surfaces += surface; 0194 }); 0195 0196 // setup connection 0197 m_connection = new KWayland::Client::ConnectionThread; 0198 QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected); 0199 m_connection->setSocketName(s_socketName); 0200 0201 m_thread = new QThread(this); 0202 m_connection->moveToThread(m_thread); 0203 m_thread->start(); 0204 0205 m_connection->initConnection(); 0206 QVERIFY(connectedSpy.wait()); 0207 QVERIFY(!m_connection->connections().isEmpty()); 0208 0209 m_queue = new KWayland::Client::EventQueue(this); 0210 QVERIFY(!m_queue->isValid()); 0211 m_queue->setup(m_connection); 0212 QVERIFY(m_queue->isValid()); 0213 0214 auto registry = new KWayland::Client::Registry(this); 0215 connect(registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, registry](const QByteArray &interface, quint32 name, quint32 version) { 0216 if (interface == "zwp_tablet_manager_v2") { 0217 auto tabletClient = new QtWayland::zwp_tablet_manager_v2(registry->registry(), name, version); 0218 auto _seat = tabletClient->get_tablet_seat(*m_clientSeat); 0219 m_tabletSeatClient = new TabletSeat(_seat); 0220 auto _seat2 = tabletClient->get_tablet_seat(*m_clientSeat); 0221 m_tabletSeatClient2 = new TabletSeat(_seat2); 0222 } 0223 }); 0224 connect(registry, &KWayland::Client::Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) { 0225 m_clientSeat = registry->createSeat(name, version); 0226 }); 0227 registry->setEventQueue(m_queue); 0228 QSignalSpy compositorSpy(registry, &KWayland::Client::Registry::compositorAnnounced); 0229 registry->create(m_connection->display()); 0230 QVERIFY(registry->isValid()); 0231 registry->setup(); 0232 wl_display_flush(m_connection->display()); 0233 0234 QVERIFY(compositorSpy.wait()); 0235 m_clientCompositor = registry->createCompositor(compositorSpy.first().first().value<quint32>(), compositorSpy.first().last().value<quint32>(), this); 0236 QVERIFY(m_clientCompositor->isValid()); 0237 0238 QSignalSpy surfaceSpy(m_serverCompositor, &CompositorInterface::surfaceCreated); 0239 for (int i = 0; i < 3; ++i) { 0240 m_surfacesClient += m_clientCompositor->createSurface(this); 0241 } 0242 QVERIFY(surfaceSpy.count() < 3 && surfaceSpy.wait(200)); 0243 QVERIFY(m_surfaces.count() == 3); 0244 QVERIFY(m_tabletSeatClient); 0245 } 0246 0247 TestTabletInterface::~TestTabletInterface() 0248 { 0249 if (m_queue) { 0250 delete m_queue; 0251 m_queue = nullptr; 0252 } 0253 if (m_thread) { 0254 m_thread->quit(); 0255 m_thread->wait(); 0256 delete m_thread; 0257 m_thread = nullptr; 0258 } 0259 delete m_tabletSeatClient; 0260 delete m_tabletSeatClient2; 0261 m_connection->deleteLater(); 0262 m_connection = nullptr; 0263 } 0264 0265 void TestTabletInterface::testAdd() 0266 { 0267 TabletSeatV2Interface *seatInterface = m_tabletManager->seat(m_seat); 0268 QVERIFY(seatInterface); 0269 0270 QSignalSpy tabletSpy(m_tabletSeatClient, &TabletSeat::tabletAdded); 0271 m_tablet = seatInterface->addTablet(1, 2, QStringLiteral("event33"), QStringLiteral("my tablet"), {QStringLiteral("/test/event33")}); 0272 QVERIFY(m_tablet); 0273 QVERIFY(tabletSpy.wait() || tabletSpy.count() == 1); 0274 QCOMPARE(m_tabletSeatClient->m_tablets.count(), 1); 0275 0276 QSignalSpy toolSpy(m_tabletSeatClient, &TabletSeat::toolAdded); 0277 m_tool = seatInterface->addTool(KWin::TabletToolV2Interface::Pen, 0, 0, {TabletToolV2Interface::Tilt, TabletToolV2Interface::Pressure}, "my tablet"); 0278 QVERIFY(m_tool); 0279 QVERIFY(toolSpy.wait() || toolSpy.count() == 1); 0280 QCOMPARE(m_tabletSeatClient->m_tools.count(), 1); 0281 0282 QVERIFY(!m_tool->isClientSupported()); // There's no surface in it yet 0283 m_tool->setCurrentSurface(nullptr); 0284 QVERIFY(!m_tool->isClientSupported()); // There's no surface in it 0285 0286 QCOMPARE(m_surfaces.count(), 3); 0287 for (SurfaceInterface *surface : m_surfaces) { 0288 m_tool->setCurrentSurface(surface); 0289 } 0290 m_tool->setCurrentSurface(nullptr); 0291 } 0292 0293 void TestTabletInterface::testAddPad() 0294 { 0295 TabletSeatV2Interface *seatInterface = m_tabletManager->seat(m_seat); 0296 QVERIFY(seatInterface); 0297 0298 QSignalSpy tabletPadSpy(m_tabletSeatClient, &TabletSeat::padAdded); 0299 m_tabletPad = 0300 seatInterface->addTabletPad(QStringLiteral("my tablet pad"), QStringLiteral("tabletpad"), {QStringLiteral("/test/event33")}, 1, 1, 1, 1, 0, m_tablet); 0301 QVERIFY(m_tabletPad); 0302 QVERIFY(tabletPadSpy.wait() || tabletPadSpy.count() == 1); 0303 QCOMPARE(m_tabletSeatClient->m_pads.count(), 1); 0304 QVERIFY(m_tabletSeatClient->m_pads[0]); 0305 0306 QVERIFY(m_tabletPad->ring(0)); 0307 QVERIFY(m_tabletPad->strip(0)); 0308 0309 QCOMPARE(m_surfaces.count(), 3); 0310 QVERIFY(m_tabletSeatClient->m_pads[0]->buttonStates.isEmpty()); 0311 QSignalSpy buttonSpy(m_tabletSeatClient->m_pads[0], &TabletPad::buttonReceived); 0312 m_tabletPad->setCurrentSurface(m_surfaces[0], m_tablet); 0313 m_tabletPad->sendButton(123ms, 0, QtWayland::zwp_tablet_pad_v2::button_state_pressed); 0314 QVERIFY(buttonSpy.count() || buttonSpy.wait(100)); 0315 QCOMPARE(m_tabletSeatClient->m_pads[0]->doneCalled, true); 0316 QCOMPARE(m_tabletSeatClient->m_pads[0]->buttonStates.count(), 1); 0317 QCOMPARE(m_tabletSeatClient->m_pads[0]->buttonStates[*m_surfacesClient[0]][0], QtWayland::zwp_tablet_pad_v2::button_state_pressed); 0318 } 0319 0320 static uint s_serial = 0; 0321 0322 void TestTabletInterface::testInteractSimple_data() 0323 { 0324 QTest::addColumn<TabletSeat *>("tabletSeatClient"); 0325 QTest::newRow("first client") << m_tabletSeatClient; 0326 QTest::newRow("second client") << m_tabletSeatClient2; 0327 } 0328 0329 void TestTabletInterface::testInteractSimple() 0330 { 0331 QFETCH(TabletSeat *, tabletSeatClient); 0332 tabletSeatClient->m_tools[0]->surfaceApproximated.clear(); 0333 QSignalSpy frameSpy(tabletSeatClient->m_tools[0], &Tool::frame); 0334 0335 QVERIFY(!m_tool->isClientSupported()); 0336 m_tool->setCurrentSurface(m_surfaces[0]); 0337 QVERIFY(m_tool->isClientSupported() && m_tablet->isSurfaceSupported(m_surfaces[0])); 0338 m_tool->sendProximityIn(m_tablet); 0339 m_tool->sendPressure(0); 0340 m_tool->sendFrame(s_serial++); 0341 m_tool->sendMotion({3, 3}); 0342 m_tool->sendFrame(s_serial++); 0343 m_tool->sendProximityOut(); 0344 QVERIFY(m_tool->isClientSupported()); 0345 m_tool->sendFrame(s_serial++); 0346 QVERIFY(!m_tool->isClientSupported()); 0347 0348 QVERIFY(frameSpy.wait(500)); 0349 QCOMPARE(tabletSeatClient->m_tools[0]->surfaceApproximated.count(), 1); 0350 } 0351 0352 void TestTabletInterface::testInteractSurfaceChange_data() 0353 { 0354 QTest::addColumn<TabletSeat *>("tabletSeatClient"); 0355 QTest::newRow("first client") << m_tabletSeatClient; 0356 QTest::newRow("second client") << m_tabletSeatClient2; 0357 } 0358 0359 void TestTabletInterface::testInteractSurfaceChange() 0360 { 0361 QFETCH(TabletSeat *, tabletSeatClient); 0362 tabletSeatClient->m_tools[0]->surfaceApproximated.clear(); 0363 QSignalSpy frameSpy(tabletSeatClient->m_tools[0], &Tool::frame); 0364 0365 QVERIFY(!m_tool->isClientSupported()); 0366 m_tool->setCurrentSurface(m_surfaces[0]); 0367 QVERIFY(m_tool->isClientSupported() && m_tablet->isSurfaceSupported(m_surfaces[0])); 0368 m_tool->sendProximityIn(m_tablet); 0369 m_tool->sendPressure(0); 0370 m_tool->sendFrame(s_serial++); 0371 0372 m_tool->setCurrentSurface(m_surfaces[1]); 0373 QVERIFY(m_tool->isClientSupported()); 0374 0375 m_tool->sendMotion({3, 3}); 0376 m_tool->sendFrame(s_serial++); 0377 m_tool->sendProximityOut(); 0378 QVERIFY(m_tool->isClientSupported()); 0379 m_tool->sendFrame(s_serial++); 0380 QVERIFY(!m_tool->isClientSupported()); 0381 0382 QVERIFY(frameSpy.wait(500)); 0383 QCOMPARE(tabletSeatClient->m_tools[0]->surfaceApproximated.count(), 2); 0384 } 0385 0386 QTEST_GUILESS_MAIN(TestTabletInterface) 0387 #include "test_tablet_interface.moc"