summaryrefslogtreecommitdiffstats
path: root/tests/auto/client/tabletv2/tst_tabletv2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/client/tabletv2/tst_tabletv2.cpp')
-rw-r--r--tests/auto/client/tabletv2/tst_tabletv2.cpp918
1 files changed, 918 insertions, 0 deletions
diff --git a/tests/auto/client/tabletv2/tst_tabletv2.cpp b/tests/auto/client/tabletv2/tst_tabletv2.cpp
new file mode 100644
index 000000000..2fe2ff420
--- /dev/null
+++ b/tests/auto/client/tabletv2/tst_tabletv2.cpp
@@ -0,0 +1,918 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mockcompositor.h"
+
+#include <qwayland-server-tablet-unstable-v2.h>
+
+#include <QtGui/QRasterWindow>
+
+using namespace MockCompositor;
+
+constexpr int tabletVersion = 1; // protocol VERSION, not the name suffix (_v2)
+
+class TabletManagerV2;
+class TabletSeatV2;
+
+class TabletV2 : public QObject, public QtWaylandServer::zwp_tablet_v2
+{
+ Q_OBJECT
+public:
+ explicit TabletV2(TabletSeatV2 *tabletSeat)
+ : m_tabletSeat(tabletSeat)
+ {
+ }
+
+ void send_removed() = delete;
+ void send_removed(struct ::wl_resource *resource) = delete;
+ void sendRemoved();
+
+ QPointer<TabletSeatV2> m_tabletSeat; // destroy order is not guaranteed
+protected:
+ void zwp_tablet_v2_destroy(Resource *resource) override;
+};
+
+class TabletToolV2 : public QObject, public QtWaylandServer::zwp_tablet_tool_v2
+{
+ Q_OBJECT
+public:
+ using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type;
+ explicit TabletToolV2(TabletSeatV2 *tabletSeat, ToolType toolType, quint64 hardwareSerial)
+ : m_tabletSeat(tabletSeat)
+ , m_toolType(toolType)
+ , m_hardwareSerial(hardwareSerial)
+ {
+ }
+
+ wl_resource *toolResource() // for convenience
+ {
+ Q_ASSERT(resourceMap().size() == 1);
+ // Strictly speaking, there may be more than one resource for the tool, for intsance if
+ // if there are multiple clients, or a client has called get_tablet_seat multiple times.
+ // For now we'll pretend there can only be one resource.
+ return resourceMap().first()->handle;
+ }
+
+ void send_removed() = delete;
+ void send_removed(struct ::wl_resource *resource) = delete;
+ void sendRemoved();
+
+ uint sendProximityIn(TabletV2 *tablet, Surface *surface);
+ void sendProximityOut();
+ void sendMotion(QPointF position)
+ {
+ Q_ASSERT(m_proximitySurface);
+ for (auto *resource : resourceMap())
+ send_motion(resource->handle, wl_fixed_from_double(position.x()), wl_fixed_from_double(position.y()));
+ }
+ uint sendDown();
+ void sendUp() { send_up(toolResource()); }
+ void sendPressure(uint pressure);
+ void sendTilt(qreal tiltX, qreal tiltY) { send_tilt(toolResource(), wl_fixed_from_double(tiltX), wl_fixed_from_double(tiltY)); }
+ void sendRotation(qreal rotation) { send_rotation(toolResource(), wl_fixed_from_double(rotation)); }
+ uint sendButton(uint button, bool pressed);
+ uint sendFrame();
+
+ QPointer<TabletSeatV2> m_tabletSeat; // destruction order is not guaranteed
+ ToolType m_toolType = ToolType::type_pen;
+ quint64 m_hardwareSerial = 0;
+ QPointer<Surface> m_proximitySurface;
+protected:
+ void zwp_tablet_tool_v2_destroy(Resource *resource) override;
+};
+
+class TabletPadV2 : public QObject, public QtWaylandServer::zwp_tablet_pad_v2
+{
+ Q_OBJECT
+public:
+ explicit TabletPadV2(TabletSeatV2 *tabletSeat)
+ : m_tabletSeat(tabletSeat)
+ {
+ }
+
+ void send_removed() = delete;
+ void send_removed(struct ::wl_resource *resource) = delete;
+ void sendRemoved();
+
+ QPointer<TabletSeatV2> m_tabletSeat; // destroy order is not guaranteed
+protected:
+ void zwp_tablet_pad_v2_destroy(Resource *resource) override;
+};
+
+class TabletSeatV2 : public QObject, public QtWaylandServer::zwp_tablet_seat_v2
+{
+ Q_OBJECT
+public:
+ explicit TabletSeatV2(TabletManagerV2 *manager, Seat *seat)
+ : m_manager(manager)
+ , m_seat(seat)
+ {}
+ TabletV2 *addTablet()
+ {
+ auto *tablet = new TabletV2(this);
+ m_tablets.append(tablet);
+ for (auto *resource : resourceMap())
+ sendTabletAdded(resource, tablet);
+ return tablet;
+ }
+
+ void sendTabletAdded(Resource *resource, TabletV2 *tablet)
+ {
+ // Although, not necessarily correct, assuming just one tablet_seat per client
+ auto *tabletResource = tablet->add(resource->client(), resource->version());
+ zwp_tablet_seat_v2::send_tablet_added(resource->handle, tabletResource->handle);
+ // TODO: send extra stuff before done?
+ tablet->send_done(tabletResource->handle);
+ }
+
+ using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type;
+ TabletToolV2 *addTool(ToolType toolType = ToolType::type_pen, quint64 hardwareSerial = 0)
+ {
+ auto *tool = new TabletToolV2(this, toolType, hardwareSerial);
+ m_tools.append(tool);
+ for (auto *resource : resourceMap())
+ sendToolAdded(resource, tool);
+ return tool;
+ }
+
+ void sendToolAdded(Resource *resource, TabletToolV2 *tool)
+ {
+ // Although, not necessarily correct, assuming just one tablet_seat per client
+ auto *toolResource = tool->add(resource->client(), resource->version())->handle;
+ zwp_tablet_seat_v2::send_tool_added(resource->handle, toolResource);
+ tool->send_type(toolResource, tool->m_toolType);
+ if (tool->m_hardwareSerial) {
+ const uint hi = tool->m_hardwareSerial >> 32;
+ const uint lo = tool->m_hardwareSerial & 0xffffffff;
+ tool->send_hardware_serial(toolResource, hi, lo);
+ }
+ tool->send_done(toolResource);
+ }
+
+ TabletPadV2 *addPad()
+ {
+ auto *pad = new TabletPadV2(this);
+ m_pads.append(pad);
+ for (auto *resource : resourceMap())
+ sendPadAdded(resource, pad);
+ return pad;
+ }
+
+ void sendPadAdded(Resource *resource, TabletPadV2 *pad)
+ {
+ // Although, not necessarily correct, assuming just one tablet_seat per client
+ auto *padResource = pad->add(resource->client(), resource->version())->handle;
+ zwp_tablet_seat_v2::send_pad_added(resource->handle, padResource);
+ pad->send_done(padResource);
+ }
+
+ void removeAll()
+ {
+ const auto tools = m_tools;
+ for (auto *tool : tools)
+ tool->sendRemoved();
+
+ const auto tablets = m_tablets;
+ for (auto *tablet : tablets)
+ tablet->sendRemoved();
+
+ const auto pads = m_pads;
+ for (auto *pad : pads)
+ pad->sendRemoved();
+ }
+
+ TabletManagerV2 *m_manager = nullptr;
+ Seat *m_seat = nullptr;
+ QVector<TabletV2 *> m_tablets;
+ QVector<TabletV2 *> m_tabletsWaitingForDestroy;
+ QVector<TabletToolV2 *> m_tools;
+ QVector<TabletToolV2 *> m_toolsWaitingForDestroy;
+ QVector<TabletPadV2 *> m_pads;
+ QVector<TabletPadV2 *> m_padsWaitingForDestroy;
+
+protected:
+ void zwp_tablet_seat_v2_bind_resource(Resource *resource)
+ {
+ for (auto *tablet : m_tablets)
+ sendTabletAdded(resource, tablet);
+ for (auto *tool : m_tools)
+ sendToolAdded(resource, tool);
+ for (auto *pad : m_pads)
+ sendPadAdded(resource, pad);
+ }
+};
+
+class TabletManagerV2 : public Global, public QtWaylandServer::zwp_tablet_manager_v2
+{
+ Q_OBJECT
+public:
+ explicit TabletManagerV2(CoreCompositor *compositor, int version = 1)
+ : QtWaylandServer::zwp_tablet_manager_v2(compositor->m_display, version)
+ , m_version(version)
+ {}
+ bool isClean() override
+ {
+ for (auto *seat : m_tabletSeats) {
+ if (!seat->m_tabletsWaitingForDestroy.empty())
+ return false;
+ if (!seat->m_toolsWaitingForDestroy.empty())
+ return false;
+ if (!seat->m_padsWaitingForDestroy.empty())
+ return false;
+ }
+ return true;
+ }
+
+ TabletSeatV2 *tabletSeatFor(Seat *seat)
+ {
+ Q_ASSERT(seat);
+ if (auto *tabletSeat = m_tabletSeats.value(seat, nullptr))
+ return tabletSeat;
+
+ auto *tabletSeat = new TabletSeatV2(this, seat);
+ m_tabletSeats[seat] = tabletSeat;
+ return tabletSeat;
+ }
+
+ int m_version = 1; // TODO: Remove on libwayland upgrade
+ QMap<Seat *, TabletSeatV2 *> m_tabletSeats;
+
+protected:
+ void zwp_tablet_manager_v2_destroy(Resource *resource) override
+ {
+ // tablet_seats created from this object are unaffected and should be destroyed separately.
+ wl_resource_destroy(resource->handle);
+ }
+
+ void zwp_tablet_manager_v2_get_tablet_seat(Resource *resource, uint32_t id, ::wl_resource *seatResource) override
+ {
+ auto *seat = fromResource<Seat>(seatResource);
+ QVERIFY(seat);
+ auto *tabletSeat = tabletSeatFor(seat);
+ tabletSeat->add(resource->client(), id, resource->version());
+ }
+};
+
+void TabletV2::sendRemoved()
+{
+ for (auto *resource : resourceMap())
+ zwp_tablet_v2_send_removed(resource->handle);
+ bool removed = m_tabletSeat->m_tablets.removeOne(this);
+ QVERIFY(removed);
+ m_tabletSeat->m_tabletsWaitingForDestroy.append(this);
+}
+
+void TabletV2::zwp_tablet_v2_destroy(QtWaylandServer::zwp_tablet_v2::Resource *resource)
+{
+ Q_UNUSED(resource)
+ if (m_tabletSeat) {
+ bool removed = m_tabletSeat->m_tabletsWaitingForDestroy.removeOne(this);
+ QVERIFY(removed);
+ }
+ wl_resource_destroy(resource->handle);
+}
+
+void TabletToolV2::sendRemoved()
+{
+ for (auto *resource : resourceMap())
+ zwp_tablet_tool_v2_send_removed(resource->handle);
+ bool removed = m_tabletSeat->m_tools.removeOne(this);
+ QVERIFY(removed);
+ m_tabletSeat->m_toolsWaitingForDestroy.append(this);
+}
+
+uint TabletToolV2::sendProximityIn(TabletV2 *tablet, Surface *surface)
+{
+ Q_ASSERT(!m_proximitySurface);
+ m_proximitySurface = surface;
+ uint serial = m_tabletSeat->m_seat->m_compositor->nextSerial();
+ auto *client = surface->resource()->client();
+ auto tabletResource = tablet->resourceMap().value(client)->handle;
+ send_proximity_in(toolResource(), serial, tabletResource, surface->resource()->handle);
+ return serial;
+}
+
+void TabletToolV2::sendProximityOut()
+{
+ Q_ASSERT(m_proximitySurface);
+ send_proximity_out(toolResource());
+ m_proximitySurface = nullptr;
+}
+
+uint TabletToolV2::sendDown()
+{
+ uint serial = m_tabletSeat->m_seat->m_compositor->nextSerial();
+ send_down(toolResource(), serial);
+ return serial;
+}
+
+void TabletToolV2::sendPressure(uint pressure)
+{
+ Q_ASSERT(m_proximitySurface);
+ auto *client = m_proximitySurface->resource()->client();
+ auto toolResource = resourceMap().value(client)->handle;
+ send_pressure(toolResource, pressure);
+}
+
+uint TabletToolV2::sendButton(uint button, bool pressed)
+{
+ button_state state = pressed ? button_state_pressed : button_state_released;
+ uint serial = m_tabletSeat->m_seat->m_compositor->nextSerial();
+ send_button(toolResource(), serial, button, state);
+ return serial;
+}
+
+uint TabletToolV2::sendFrame()
+{
+ uint time = m_tabletSeat->m_seat->m_compositor->currentTimeMilliseconds();
+ for (auto *resource : resourceMap())
+ send_frame(resource->handle, time);
+ return time;
+}
+
+void TabletToolV2::zwp_tablet_tool_v2_destroy(QtWaylandServer::zwp_tablet_tool_v2::Resource *resource)
+{
+ if (m_tabletSeat) {
+ bool removed = m_tabletSeat->m_toolsWaitingForDestroy.removeOne(this);
+ QVERIFY(removed);
+ }
+ wl_resource_destroy(resource->handle);
+}
+
+void TabletPadV2::sendRemoved()
+{
+ for (auto *resource : resourceMap())
+ zwp_tablet_pad_v2_send_removed(resource->handle);
+ bool removed = m_tabletSeat->m_pads.removeOne(this);
+ QVERIFY(removed);
+ m_tabletSeat->m_padsWaitingForDestroy.append(this);
+}
+
+void TabletPadV2::zwp_tablet_pad_v2_destroy(QtWaylandServer::zwp_tablet_pad_v2::Resource *resource)
+{
+ if (m_tabletSeat) {
+ bool removed = m_tabletSeat->m_padsWaitingForDestroy.removeOne(this);
+ QVERIFY(removed);
+ }
+ wl_resource_destroy(resource->handle);
+}
+
+class TabletCompositor : public DefaultCompositor {
+public:
+ explicit TabletCompositor()
+ {
+ exec([this] {
+ m_config.autoConfigure = true;
+ add<TabletManagerV2>(tabletVersion);
+ });
+ }
+ TabletSeatV2 *tabletSeat(int i = 0)
+ {
+ return get<TabletManagerV2>()->tabletSeatFor(get<Seat>(i));
+ }
+ TabletV2 *tablet(int i = 0, int iSeat = 0)
+ {
+ if (auto *ts = tabletSeat(iSeat))
+ return ts->m_tablets.value(i, nullptr);
+ return nullptr;
+ }
+ TabletToolV2 *tabletTool(int i = 0, int iSeat = 0)
+ {
+ if (auto *ts = tabletSeat(iSeat))
+ return ts->m_tools.value(i, nullptr);
+ return nullptr;
+ }
+ TabletPadV2 *tabletPad(int i = 0, int iSeat = 0)
+ {
+ if (auto *ts = tabletSeat(iSeat))
+ return ts->m_pads.value(i, nullptr);
+ return nullptr;
+ }
+};
+
+Q_DECLARE_METATYPE(QtWaylandServer::zwp_tablet_tool_v2::type);
+Q_DECLARE_METATYPE(QTabletEvent::PointerType);
+Q_DECLARE_METATYPE(Qt::MouseButton);
+
+class tst_tabletv2 : public QObject, private TabletCompositor
+{
+ using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type;
+ Q_OBJECT
+private slots:
+ void cleanup();
+ void bindsToManager();
+ void createsTabletSeat();
+ void destroysTablet();
+ void destroysTool();
+ void destroysPad();
+ void proximityEvents();
+ void moveEvent();
+ void pointerType_data();
+ void pointerType();
+ void hardwareSerial();
+ void buttons_data();
+ void buttons();
+ void tabletEvents();
+};
+
+class ProximityFilter : public QObject {
+ Q_OBJECT
+public:
+ ProximityFilter() { qApp->installEventFilter(this); }
+ ~ProximityFilter() override { qDeleteAll(m_events); }
+ QVector<QTabletEvent *> m_events;
+
+ int nextEventIndex = 0;
+ int numEvents() const { return m_events.size() - nextEventIndex; }
+ QTabletEvent *popEvent()
+ {
+ auto *event = m_events.value(nextEventIndex, nullptr);
+ if (event)
+ ++nextEventIndex;
+ return event;
+ }
+
+protected:
+ bool eventFilter(QObject *object, QEvent *event) override
+ {
+ Q_UNUSED(object);
+ switch (event->type()) {
+ case QEvent::TabletEnterProximity:
+ case QEvent::TabletLeaveProximity: {
+ auto *e = static_cast<QTabletEvent *>(event);
+ auto *ev = new QTabletEvent(e->type(), e->posF(), e->globalPosF(), e->device(),
+ e->pointerType(), e->pressure(), e->xTilt(), e->yTilt(),
+ e->tangentialPressure(), e->rotation(), e->z(),
+ Qt::KeyboardModifier::NoModifier, e->uniqueId(),
+ e->button(), e->buttons());
+ m_events << ev;
+ break;
+ }
+ default:
+ break;
+ }
+ return false;
+ }
+};
+
+void tst_tabletv2::cleanup()
+{
+ exec([&] {
+ tabletSeat()->removeAll();
+ });
+ QCOMPOSITOR_COMPARE(get<TabletManagerV2>()->m_tabletSeats.size(), 1);
+ QCOMPOSITOR_COMPARE(tabletSeat()->m_tablets.size(), 0);
+ QCOMPOSITOR_COMPARE(tabletSeat()->m_tools.size(), 0);
+ QCOMPOSITOR_COMPARE(tabletSeat()->m_pads.size(), 0);
+
+ QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage()));
+}
+
+void tst_tabletv2::bindsToManager()
+{
+ QCOMPOSITOR_TRY_COMPARE(get<TabletManagerV2>()->resourceMap().size(), 1);
+ QCOMPOSITOR_TRY_COMPARE(get<TabletManagerV2>()->resourceMap().first()->version(), tabletVersion);
+}
+
+void tst_tabletv2::createsTabletSeat()
+{
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat()->resourceMap().contains(client()));
+ QCOMPOSITOR_TRY_COMPARE(tabletSeat()->resourceMap().value(client())->version(), tabletVersion);
+ //TODO: Maybe also assert some capability reported though qt APIs?
+}
+
+void tst_tabletv2::destroysTablet()
+{
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ });
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+
+ exec([&] {
+ tablet()->sendRemoved();
+ });
+
+ QCOMPOSITOR_TRY_VERIFY(!tablet());
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_tabletsWaitingForDestroy.empty());
+}
+
+void tst_tabletv2::destroysTool()
+{
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTool();
+ });
+ QCOMPOSITOR_TRY_VERIFY(tabletTool());
+
+ exec([&] {
+ tabletTool()->sendRemoved();
+ });
+
+ QCOMPOSITOR_TRY_VERIFY(!tabletTool());
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_toolsWaitingForDestroy.empty());
+}
+
+void tst_tabletv2::destroysPad()
+{
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addPad();
+ });
+ QCOMPOSITOR_TRY_VERIFY(tabletPad());
+
+ exec([&] {
+ tabletPad()->sendRemoved();
+ });
+
+ QCOMPOSITOR_TRY_VERIFY(!tabletPad());
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat()->m_padsWaitingForDestroy.empty());
+}
+
+void tst_tabletv2::proximityEvents()
+{
+ ProximityFilter filter;
+
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ tabletSeat()->addTool();
+ });
+
+ QRasterWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ auto *surface = xdgSurface()->m_surface;
+ auto *tool = tabletTool();
+ tool->sendProximityIn(tablet(), surface);
+ tool->sendFrame();
+ });
+
+ QTRY_COMPARE(filter.numEvents(), 1);
+ QTabletEvent *enterEvent = filter.popEvent();
+ QCOMPARE(enterEvent->type(), QEvent::TabletEnterProximity);
+
+ exec([&] {
+ auto *tool = tabletTool();
+ tool->sendProximityOut();
+ tool->sendFrame();
+ });
+
+ QTRY_COMPARE(filter.numEvents(), 1);
+ QTabletEvent *leaveEvent = filter.popEvent();
+ QCOMPARE(leaveEvent->type(), QEvent::TabletLeaveProximity);
+}
+
+class TabletWindow : public QRasterWindow {
+ Q_OBJECT
+public:
+ ~TabletWindow() override { qDeleteAll(m_events); }
+
+ void tabletEvent(QTabletEvent *e) override
+ {
+ m_events << new QTabletEvent(e->type(), e->posF(), e->globalPosF(), e->device(),
+ e->pointerType(), e->pressure(), e->xTilt(), e->yTilt(),
+ e->tangentialPressure(), e->rotation(), e->z(),
+ Qt::KeyboardModifier::NoModifier, e->uniqueId(), e->button(),
+ e->buttons());
+ emit tabletEventReceived(m_events.last());
+ }
+ int nextEventIndex = 0;
+ int numEvents() const { return m_events.size() - nextEventIndex; }
+ QTabletEvent *popEvent()
+ {
+ auto *event = m_events.value(nextEventIndex, nullptr);
+ if (event)
+ ++nextEventIndex;
+ return event;
+ }
+
+signals:
+ void tabletEventReceived(QTabletEvent *event);
+
+private:
+ QVector<QTabletEvent *> m_events;
+};
+
+void tst_tabletv2::moveEvent()
+{
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ tabletSeat()->addTool();
+ });
+
+ TabletWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ auto *surface = xdgSurface()->m_surface;
+ auto *tool = tabletTool();
+ tool->sendProximityIn(tablet(), surface);
+ QMargins margins = window.frameMargins();
+ tool->sendMotion(QPointF(12 + margins.left(), 34 + margins.top()));
+ tool->sendFrame();
+ });
+ QTRY_VERIFY(window.numEvents());
+ QTabletEvent *event = window.popEvent();
+ QCOMPARE(event->type(), QEvent::TabletMove);
+ QCOMPARE(event->pressure(), 0);
+ QCOMPARE(event->posF(), QPointF(12, 34));
+}
+
+void tst_tabletv2::pointerType_data()
+{
+ QTest::addColumn<ToolType>("toolType");
+ QTest::addColumn<QTabletEvent::PointerType>("pointerType");
+ QTest::addColumn<QTabletEvent::TabletDevice>("tabletDevice");
+
+ QTest::newRow("pen") << ToolType::type_pen << QTabletEvent::PointerType::Pen << QTabletEvent::TabletDevice::Stylus;
+ QTest::newRow("eraser") << ToolType::type_eraser << QTabletEvent::PointerType::Eraser << QTabletEvent::TabletDevice::Stylus;
+ QTest::newRow("pencil") << ToolType::type_pencil << QTabletEvent::PointerType::Pen << QTabletEvent::TabletDevice::Stylus;
+ QTest::newRow("airbrush") << ToolType::type_airbrush << QTabletEvent::PointerType::Pen << QTabletEvent::TabletDevice::Airbrush;
+ QTest::newRow("brush") << ToolType::type_brush << QTabletEvent::PointerType::Pen << QTabletEvent::TabletDevice::Stylus; // TODO: is TabletDevice::Stylus the right thing?
+ QTest::newRow("lens") << ToolType::type_lens << QTabletEvent::PointerType::Cursor << QTabletEvent::TabletDevice::Puck;
+ // TODO: also add tests for FourDMouse and RotationStylus (also need to send capabilities)
+
+ // TODO: should these rather be mapped to touch/mouse events?
+ QTest::newRow("finger") << ToolType::type_finger << QTabletEvent::PointerType::UnknownPointer << QTabletEvent::TabletDevice::NoDevice;
+ QTest::newRow("mouse") << ToolType::type_mouse << QTabletEvent::PointerType::Cursor << QTabletEvent::TabletDevice::NoDevice;
+}
+
+void tst_tabletv2::pointerType()
+{
+ using ToolType = QtWaylandServer::zwp_tablet_tool_v2::type;
+ QFETCH(ToolType, toolType);
+ QFETCH(QTabletEvent::PointerType, pointerType);
+ QFETCH(QTabletEvent::TabletDevice, tabletDevice);
+
+ ProximityFilter filter;
+
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ tabletSeat()->addTool(toolType);
+ });
+
+ TabletWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ auto *surface = xdgSurface()->m_surface;
+ auto *tool = tabletTool();
+ tool->sendProximityIn(tablet(), surface);
+ QMargins margins = window.frameMargins();
+ tool->sendMotion(QPointF(12 + margins.left(), 34 + margins.top()));
+ tool->sendFrame();
+ });
+
+ QTRY_COMPARE(filter.numEvents(), 1);
+ QTabletEvent *event = filter.popEvent();
+ QCOMPARE(event->pointerType(), pointerType);
+ QCOMPARE(event->device(), tabletDevice);
+
+ QTRY_VERIFY(window.numEvents());
+ event = window.popEvent();
+ QCOMPARE(event->pointerType(), pointerType);
+ QCOMPARE(event->device(), tabletDevice);
+
+ exec([&] {
+ tabletTool()->sendProximityOut();
+ tabletTool()->sendFrame();
+ });
+
+ QTRY_VERIFY(filter.numEvents());
+ event = filter.popEvent();
+ QCOMPARE(event->pointerType(), pointerType);
+ QCOMPARE(event->device(), tabletDevice);
+}
+
+void tst_tabletv2::hardwareSerial()
+{
+ ProximityFilter filter;
+ const quint64 uid = 0xbaba15dead15f00d;
+
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ tabletSeat()->addTool(ToolType::type_pen, uid);
+ });
+
+ TabletWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ auto *surface = xdgSurface()->m_surface;
+ auto *tool = tabletTool();
+ tool->sendProximityIn(tablet(), surface);
+ QMargins margins = window.frameMargins();
+ tool->sendMotion(QPointF(12 + margins.left(), 34 + margins.top()));
+ tool->sendFrame();
+ });
+
+ QTRY_COMPARE(filter.numEvents(), 1);
+ QTabletEvent *event = filter.popEvent();
+ QCOMPARE(event->uniqueId(), uid);
+
+ QTRY_VERIFY(window.numEvents());
+ event = window.popEvent();
+ QCOMPARE(event->uniqueId(), uid);
+
+ exec([&] {
+ tabletTool()->sendProximityOut();
+ tabletTool()->sendFrame();
+ });
+
+ QTRY_VERIFY(filter.numEvents());
+ event = filter.popEvent();
+ QCOMPARE(event->uniqueId(), uid);
+}
+
+// As defined in linux/input-event-codes.h
+#ifndef BTN_STYLUS
+#define BTN_STYLUS 0x14b
+#endif
+#ifndef BTN_STYLUS2
+#define BTN_STYLUS2 0x14c
+#endif
+
+void tst_tabletv2::buttons_data()
+{
+ QTest::addColumn<uint>("tabletButton");
+ QTest::addColumn<Qt::MouseButton>("mouseButton");
+
+ QTest::newRow("BTN_STYLUS2") << uint(BTN_STYLUS2) << Qt::MouseButton::RightButton;
+ QTest::newRow("BTN_STYLUS") << uint(BTN_STYLUS) << Qt::MouseButton::MiddleButton;
+}
+
+void tst_tabletv2::buttons()
+{
+ QFETCH(uint, tabletButton);
+ QFETCH(Qt::MouseButton, mouseButton);
+
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ tabletSeat()->addTool();
+ });
+
+ TabletWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ tabletTool()->sendProximityIn(tablet(), xdgSurface()->m_surface);
+ QMargins margins = window.frameMargins();
+ tabletTool()->sendMotion(QPointF(12 + margins.left(), 34 + margins.top()));
+ tabletTool()->sendFrame();
+ });
+
+ QTRY_VERIFY(window.numEvents());
+ window.popEvent();
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ tabletTool()->sendButton(tabletButton, true);
+ tabletTool()->sendFrame();
+ tabletTool()->sendButton(tabletButton, false);
+ tabletTool()->sendFrame();
+ });
+
+ QTRY_VERIFY(window.numEvents());
+ QTabletEvent *event = window.popEvent();
+ QCOMPARE(event->buttons(), mouseButton);
+
+ exec([&] {
+ tabletTool()->sendProximityOut();
+ tabletTool()->sendFrame();
+ });
+}
+
+void tst_tabletv2::tabletEvents()
+{
+ QCOMPOSITOR_TRY_VERIFY(tabletSeat());
+ exec([&] {
+ tabletSeat()->addTablet();
+ tabletSeat()->addTool();
+ });
+
+ TabletWindow window;
+ window.resize(64, 64);
+ window.show();
+ QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
+
+ const QPointF insideDecorations(window.frameMargins().left(), window.frameMargins().top());
+
+ QCOMPOSITOR_TRY_VERIFY(tablet());
+ exec([&] {
+ auto *surface = xdgSurface()->m_surface;
+ auto *tool = tabletTool();
+ // TODO: encapsulate this into a helper function?
+ tool->sendProximityIn(tablet(), surface);
+ tool->sendMotion(QPointF(12, 34) + insideDecorations);
+ tool->sendDown();
+ tool->sendPressure(65535);
+ tool->sendFrame();
+ });
+
+ QTRY_VERIFY(window.numEvents());
+ QTabletEvent *event = window.popEvent();
+ QCOMPARE(event->type(), QEvent::TabletPress);
+ QCOMPARE(event->pressure(), 1.0);
+ QCOMPARE(event->posF(), QPointF(12, 34));
+
+ // Values we didn't send should be 0
+ QCOMPARE(event->rotation(), 0);
+ QCOMPARE(event->xTilt(), 0);
+ QCOMPARE(event->yTilt(), 0);
+
+ exec([&] {
+ tabletTool()->sendMotion(QPointF(45, 56) + insideDecorations);
+ tabletTool()->sendPressure(65535/2);
+ tabletTool()->sendRotation(90);
+ tabletTool()->sendTilt(13, 37);
+ tabletTool()->sendFrame();
+ });
+
+ QTRY_VERIFY(window.numEvents());
+ event = window.popEvent();
+ QCOMPARE(event->type(), QEvent::TabletMove);
+ QVERIFY(qAbs(event->pressure() - 0.5) < 0.01);
+ QVERIFY(qAbs(event->rotation() - 90) < 0.01);
+ QVERIFY(qAbs(event->xTilt() - 13) < 0.01);
+ QVERIFY(qAbs(event->yTilt() - 37) < 0.01);
+ QCOMPARE(event->posF(), QPointF(45, 56));
+
+ // Verify that the values stay the same if we don't update them
+ exec([&] {
+ tabletTool()->sendMotion(QPointF(10, 11) + insideDecorations); // Change position only
+ tabletTool()->sendFrame();
+ });
+ QTRY_VERIFY(window.numEvents());
+ event = window.popEvent();
+ QCOMPARE(event->type(), QEvent::TabletMove);
+ QVERIFY(qAbs(event->pressure() - 0.5) < 0.01);
+ QVERIFY(qAbs(event->rotation() - 90) < 0.01);
+ QVERIFY(qAbs(event->xTilt() - 13) < 0.01);
+ QVERIFY(qAbs(event->yTilt() - 37) < 0.01);
+ QCOMPARE(event->posF(), QPointF(10, 11));
+
+ exec([&] {
+ tabletTool()->sendPressure(0);
+ tabletTool()->sendUp();
+ tabletTool()->sendFrame();
+
+ tabletTool()->sendProximityOut();
+ tabletTool()->sendFrame();
+ });
+
+ QTRY_VERIFY(window.numEvents());
+ event = window.popEvent();
+ QCOMPARE(event->type(), QEvent::TabletRelease);
+ QCOMPARE(event->pressure(), 0);
+ QCOMPARE(event->posF(), QPointF(10, 11));
+}
+
+QCOMPOSITOR_TEST_MAIN(tst_tabletv2)
+#include "tst_tabletv2.moc"