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"