You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
386 lines
12 KiB
386 lines
12 KiB
/* |
|
SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org> |
|
|
|
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
|
*/ |
|
// Qt |
|
#include <QHash> |
|
#include <QSignalSpy> |
|
#include <QTest> |
|
#include <QThread> |
|
// WaylandServer |
|
#include "wayland/compositor.h" |
|
#include "wayland/display.h" |
|
#include "wayland/seat.h" |
|
#include "wayland/tablet_v2.h" |
|
|
|
#include "KWayland/Client/compositor.h" |
|
#include "KWayland/Client/connection_thread.h" |
|
#include "KWayland/Client/event_queue.h" |
|
#include "KWayland/Client/registry.h" |
|
#include "KWayland/Client/seat.h" |
|
#include "KWayland/Client/surface.h" |
|
|
|
#include "qwayland-tablet-unstable-v2.h" |
|
|
|
using namespace KWin; |
|
using namespace std::literals; |
|
|
|
class Tablet : public QtWayland::zwp_tablet_v2 |
|
{ |
|
public: |
|
Tablet(::zwp_tablet_v2 *t) |
|
: QtWayland::zwp_tablet_v2(t) |
|
{ |
|
} |
|
}; |
|
|
|
class TabletPad : public QObject, public QtWayland::zwp_tablet_pad_v2 |
|
{ |
|
Q_OBJECT |
|
public: |
|
TabletPad(::zwp_tablet_pad_v2 *t) |
|
: QtWayland::zwp_tablet_pad_v2(t) |
|
{ |
|
} |
|
|
|
void zwp_tablet_pad_v2_done() override |
|
{ |
|
Q_ASSERT(!doneCalled); |
|
doneCalled = true; |
|
} |
|
|
|
void zwp_tablet_pad_v2_buttons(uint32_t buttons) override |
|
{ |
|
Q_ASSERT(buttons == 1); |
|
} |
|
|
|
void zwp_tablet_pad_v2_enter(uint32_t /*serial*/, struct ::zwp_tablet_v2 * /*tablet*/, struct ::wl_surface *surface) override |
|
{ |
|
m_currentSurface = surface; |
|
} |
|
|
|
void zwp_tablet_pad_v2_button(uint32_t /*time*/, uint32_t button, uint32_t state) override |
|
{ |
|
buttonStates[m_currentSurface][button] = state; |
|
Q_EMIT buttonReceived(); |
|
} |
|
|
|
::wl_surface *m_currentSurface = nullptr; |
|
|
|
bool doneCalled = false; |
|
QHash<::wl_surface *, QHash<uint32_t, uint32_t>> buttonStates; |
|
|
|
Q_SIGNALS: |
|
void buttonReceived(); |
|
}; |
|
|
|
class Tool : public QObject, public QtWayland::zwp_tablet_tool_v2 |
|
{ |
|
Q_OBJECT |
|
public: |
|
Tool(::zwp_tablet_tool_v2 *t) |
|
: QtWayland::zwp_tablet_tool_v2(t) |
|
{ |
|
} |
|
|
|
void zwp_tablet_tool_v2_proximity_in(uint32_t /*serial*/, struct ::zwp_tablet_v2 * /*tablet*/, struct ::wl_surface *surface) override |
|
{ |
|
surfaceApproximated[surface]++; |
|
} |
|
|
|
void zwp_tablet_tool_v2_frame(uint32_t time) override |
|
{ |
|
Q_EMIT frame(time); |
|
} |
|
|
|
QHash<struct ::wl_surface *, int> surfaceApproximated; |
|
Q_SIGNALS: |
|
void frame(quint32 time); |
|
}; |
|
|
|
class TabletSeat : public QObject, public QtWayland::zwp_tablet_seat_v2 |
|
{ |
|
Q_OBJECT |
|
public: |
|
TabletSeat(::zwp_tablet_seat_v2 *seat) |
|
: QtWayland::zwp_tablet_seat_v2(seat) |
|
{ |
|
} |
|
|
|
void zwp_tablet_seat_v2_tablet_added(struct ::zwp_tablet_v2 *id) override |
|
{ |
|
m_tablets << new Tablet(id); |
|
Q_EMIT tabletAdded(); |
|
} |
|
void zwp_tablet_seat_v2_tool_added(struct ::zwp_tablet_tool_v2 *id) override |
|
{ |
|
m_tools << new Tool(id); |
|
Q_EMIT toolAdded(); |
|
} |
|
|
|
void zwp_tablet_seat_v2_pad_added(struct ::zwp_tablet_pad_v2 *id) override |
|
{ |
|
m_pads << new TabletPad(id); |
|
Q_EMIT padAdded(); |
|
} |
|
|
|
QList<Tablet *> m_tablets; |
|
QList<TabletPad *> m_pads; |
|
QList<Tool *> m_tools; |
|
|
|
Q_SIGNALS: |
|
void padAdded(); |
|
void toolAdded(); |
|
void tabletAdded(); |
|
}; |
|
|
|
class TestTabletInterface : public QObject |
|
{ |
|
Q_OBJECT |
|
public: |
|
TestTabletInterface() |
|
{ |
|
} |
|
~TestTabletInterface() override; |
|
|
|
private Q_SLOTS: |
|
void initTestCase(); |
|
void testAdd(); |
|
void testAddPad(); |
|
void testInteractSimple_data(); |
|
void testInteractSimple(); |
|
void testInteractSurfaceChange_data(); |
|
void testInteractSurfaceChange(); |
|
|
|
private: |
|
KWayland::Client::ConnectionThread *m_connection; |
|
KWayland::Client::EventQueue *m_queue; |
|
KWayland::Client::Compositor *m_clientCompositor; |
|
KWayland::Client::Seat *m_clientSeat = nullptr; |
|
|
|
QThread *m_thread; |
|
KWin::Display m_display; |
|
SeatInterface *m_seat; |
|
CompositorInterface *m_serverCompositor; |
|
|
|
TabletSeat *m_tabletSeatClient = nullptr; |
|
TabletSeat *m_tabletSeatClient2 = nullptr; |
|
|
|
TabletManagerV2Interface *m_tabletManager; |
|
QList<KWayland::Client::Surface *> m_surfacesClient; |
|
|
|
TabletV2Interface *m_tablet; |
|
TabletPadV2Interface *m_tabletPad = nullptr; |
|
TabletToolV2Interface *m_tool; |
|
|
|
QList<SurfaceInterface *> m_surfaces; |
|
}; |
|
|
|
static const QString s_socketName = QStringLiteral("kwin-wayland-server-tablet-test-0"); |
|
|
|
void TestTabletInterface::initTestCase() |
|
{ |
|
m_display.addSocketName(s_socketName); |
|
m_display.start(); |
|
QVERIFY(m_display.isRunning()); |
|
|
|
m_seat = new SeatInterface(&m_display, QStringLiteral("seat0"), this); |
|
m_serverCompositor = new CompositorInterface(&m_display, this); |
|
m_tabletManager = new TabletManagerV2Interface(&m_display, this); |
|
|
|
connect(m_serverCompositor, &CompositorInterface::surfaceCreated, this, [this](SurfaceInterface *surface) { |
|
m_surfaces += surface; |
|
}); |
|
|
|
// setup connection |
|
m_connection = new KWayland::Client::ConnectionThread; |
|
QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected); |
|
m_connection->setSocketName(s_socketName); |
|
|
|
m_thread = new QThread(this); |
|
m_connection->moveToThread(m_thread); |
|
m_thread->start(); |
|
|
|
m_connection->initConnection(); |
|
QVERIFY(connectedSpy.wait()); |
|
QVERIFY(!m_connection->connections().isEmpty()); |
|
|
|
m_queue = new KWayland::Client::EventQueue(this); |
|
QVERIFY(!m_queue->isValid()); |
|
m_queue->setup(m_connection); |
|
QVERIFY(m_queue->isValid()); |
|
|
|
auto registry = new KWayland::Client::Registry(this); |
|
connect(registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, registry](const QByteArray &interface, quint32 name, quint32 version) { |
|
if (interface == "zwp_tablet_manager_v2") { |
|
auto tabletClient = new QtWayland::zwp_tablet_manager_v2(registry->registry(), name, version); |
|
auto _seat = tabletClient->get_tablet_seat(*m_clientSeat); |
|
m_tabletSeatClient = new TabletSeat(_seat); |
|
auto _seat2 = tabletClient->get_tablet_seat(*m_clientSeat); |
|
m_tabletSeatClient2 = new TabletSeat(_seat2); |
|
} |
|
}); |
|
connect(registry, &KWayland::Client::Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) { |
|
m_clientSeat = registry->createSeat(name, version); |
|
}); |
|
registry->setEventQueue(m_queue); |
|
QSignalSpy compositorSpy(registry, &KWayland::Client::Registry::compositorAnnounced); |
|
registry->create(m_connection->display()); |
|
QVERIFY(registry->isValid()); |
|
registry->setup(); |
|
wl_display_flush(m_connection->display()); |
|
|
|
QVERIFY(compositorSpy.wait()); |
|
m_clientCompositor = registry->createCompositor(compositorSpy.first().first().value<quint32>(), compositorSpy.first().last().value<quint32>(), this); |
|
QVERIFY(m_clientCompositor->isValid()); |
|
|
|
QSignalSpy surfaceSpy(m_serverCompositor, &CompositorInterface::surfaceCreated); |
|
for (int i = 0; i < 3; ++i) { |
|
m_surfacesClient += m_clientCompositor->createSurface(this); |
|
} |
|
QVERIFY(surfaceSpy.count() < 3 && surfaceSpy.wait(200)); |
|
QVERIFY(m_surfaces.count() == 3); |
|
QVERIFY(m_tabletSeatClient); |
|
} |
|
|
|
TestTabletInterface::~TestTabletInterface() |
|
{ |
|
if (m_queue) { |
|
delete m_queue; |
|
m_queue = nullptr; |
|
} |
|
if (m_thread) { |
|
m_thread->quit(); |
|
m_thread->wait(); |
|
delete m_thread; |
|
m_thread = nullptr; |
|
} |
|
delete m_tabletSeatClient; |
|
delete m_tabletSeatClient2; |
|
m_connection->deleteLater(); |
|
m_connection = nullptr; |
|
} |
|
|
|
void TestTabletInterface::testAdd() |
|
{ |
|
TabletSeatV2Interface *seatInterface = m_tabletManager->seat(m_seat); |
|
QVERIFY(seatInterface); |
|
|
|
QSignalSpy tabletSpy(m_tabletSeatClient, &TabletSeat::tabletAdded); |
|
m_tablet = seatInterface->addTablet(1, 2, QStringLiteral("event33"), QStringLiteral("my tablet"), {QStringLiteral("/test/event33")}); |
|
QVERIFY(m_tablet); |
|
QVERIFY(tabletSpy.wait() || tabletSpy.count() == 1); |
|
QCOMPARE(m_tabletSeatClient->m_tablets.count(), 1); |
|
|
|
QSignalSpy toolSpy(m_tabletSeatClient, &TabletSeat::toolAdded); |
|
m_tool = seatInterface->addTool(KWin::TabletToolV2Interface::Pen, 0, 0, {TabletToolV2Interface::Tilt, TabletToolV2Interface::Pressure}, "my tablet"); |
|
QVERIFY(m_tool); |
|
QVERIFY(toolSpy.wait() || toolSpy.count() == 1); |
|
QCOMPARE(m_tabletSeatClient->m_tools.count(), 1); |
|
|
|
QVERIFY(!m_tool->isClientSupported()); // There's no surface in it yet |
|
m_tool->setCurrentSurface(nullptr); |
|
QVERIFY(!m_tool->isClientSupported()); // There's no surface in it |
|
|
|
QCOMPARE(m_surfaces.count(), 3); |
|
for (SurfaceInterface *surface : m_surfaces) { |
|
m_tool->setCurrentSurface(surface); |
|
} |
|
m_tool->setCurrentSurface(nullptr); |
|
} |
|
|
|
void TestTabletInterface::testAddPad() |
|
{ |
|
TabletSeatV2Interface *seatInterface = m_tabletManager->seat(m_seat); |
|
QVERIFY(seatInterface); |
|
|
|
QSignalSpy tabletPadSpy(m_tabletSeatClient, &TabletSeat::padAdded); |
|
m_tabletPad = seatInterface->addPad(QStringLiteral("my tablet pad"), QStringLiteral("tabletpad"), {QStringLiteral("/test/event33")}, 1, 1, 1, 1, 0); |
|
QVERIFY(m_tabletPad); |
|
QVERIFY(tabletPadSpy.wait() || tabletPadSpy.count() == 1); |
|
QCOMPARE(m_tabletSeatClient->m_pads.count(), 1); |
|
QVERIFY(m_tabletSeatClient->m_pads[0]); |
|
|
|
QVERIFY(m_tabletPad->ring(0)); |
|
QVERIFY(m_tabletPad->strip(0)); |
|
|
|
QCOMPARE(m_surfaces.count(), 3); |
|
QVERIFY(m_tabletSeatClient->m_pads[0]->buttonStates.isEmpty()); |
|
QSignalSpy buttonSpy(m_tabletSeatClient->m_pads[0], &TabletPad::buttonReceived); |
|
m_tabletPad->setCurrentSurface(m_surfaces[0], m_tablet); |
|
m_tabletPad->sendButton(123ms, 0, QtWayland::zwp_tablet_pad_v2::button_state_pressed); |
|
QVERIFY(buttonSpy.count() || buttonSpy.wait(100)); |
|
QCOMPARE(m_tabletSeatClient->m_pads[0]->doneCalled, true); |
|
QCOMPARE(m_tabletSeatClient->m_pads[0]->buttonStates.count(), 1); |
|
QCOMPARE(m_tabletSeatClient->m_pads[0]->buttonStates[*m_surfacesClient[0]][0], QtWayland::zwp_tablet_pad_v2::button_state_pressed); |
|
} |
|
|
|
static uint s_serial = 0; |
|
|
|
void TestTabletInterface::testInteractSimple_data() |
|
{ |
|
QTest::addColumn<TabletSeat *>("tabletSeatClient"); |
|
QTest::newRow("first client") << m_tabletSeatClient; |
|
QTest::newRow("second client") << m_tabletSeatClient2; |
|
} |
|
|
|
void TestTabletInterface::testInteractSimple() |
|
{ |
|
QFETCH(TabletSeat *, tabletSeatClient); |
|
tabletSeatClient->m_tools[0]->surfaceApproximated.clear(); |
|
QSignalSpy frameSpy(tabletSeatClient->m_tools[0], &Tool::frame); |
|
|
|
QVERIFY(!m_tool->isClientSupported()); |
|
m_tool->setCurrentSurface(m_surfaces[0]); |
|
QVERIFY(m_tool->isClientSupported() && m_tablet->isSurfaceSupported(m_surfaces[0])); |
|
m_tool->sendProximityIn(m_tablet); |
|
m_tool->sendPressure(0); |
|
m_tool->sendFrame(s_serial++); |
|
m_tool->sendMotion({3, 3}); |
|
m_tool->sendFrame(s_serial++); |
|
m_tool->sendProximityOut(); |
|
QVERIFY(m_tool->isClientSupported()); |
|
m_tool->sendFrame(s_serial++); |
|
QVERIFY(!m_tool->isClientSupported()); |
|
|
|
QVERIFY(frameSpy.wait(500)); |
|
QCOMPARE(tabletSeatClient->m_tools[0]->surfaceApproximated.count(), 1); |
|
} |
|
|
|
void TestTabletInterface::testInteractSurfaceChange_data() |
|
{ |
|
QTest::addColumn<TabletSeat *>("tabletSeatClient"); |
|
QTest::newRow("first client") << m_tabletSeatClient; |
|
QTest::newRow("second client") << m_tabletSeatClient2; |
|
} |
|
|
|
void TestTabletInterface::testInteractSurfaceChange() |
|
{ |
|
QFETCH(TabletSeat *, tabletSeatClient); |
|
tabletSeatClient->m_tools[0]->surfaceApproximated.clear(); |
|
QSignalSpy frameSpy(tabletSeatClient->m_tools[0], &Tool::frame); |
|
|
|
QVERIFY(!m_tool->isClientSupported()); |
|
m_tool->setCurrentSurface(m_surfaces[0]); |
|
QVERIFY(m_tool->isClientSupported() && m_tablet->isSurfaceSupported(m_surfaces[0])); |
|
m_tool->sendProximityIn(m_tablet); |
|
m_tool->sendPressure(0); |
|
m_tool->sendFrame(s_serial++); |
|
|
|
m_tool->setCurrentSurface(m_surfaces[1]); |
|
QVERIFY(m_tool->isClientSupported()); |
|
|
|
m_tool->sendMotion({3, 3}); |
|
m_tool->sendFrame(s_serial++); |
|
m_tool->sendProximityOut(); |
|
QVERIFY(m_tool->isClientSupported()); |
|
m_tool->sendFrame(s_serial++); |
|
QVERIFY(!m_tool->isClientSupported()); |
|
|
|
QVERIFY(frameSpy.wait(500)); |
|
QCOMPARE(tabletSeatClient->m_tools[0]->surfaceApproximated.count(), 2); |
|
} |
|
|
|
QTEST_GUILESS_MAIN(TestTabletInterface) |
|
#include "test_tablet_interface.moc"
|
|
|