/**************************************************************************** ** ** 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 #include 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 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 m_tabletSeat; // destruction order is not guaranteed ToolType m_toolType = ToolType::type_pen; quint64 m_hardwareSerial = 0; QPointer 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 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 m_tablets; QVector m_tabletsWaitingForDestroy; QVector m_tools; QVector m_toolsWaitingForDestroy; QVector m_pads; QVector 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 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(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(tabletVersion); }); } TabletSeatV2 *tabletSeat(int i = 0) { return get()->tabletSeatFor(get(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 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(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()->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()->resourceMap().size(), 1); QCOMPOSITOR_TRY_COMPARE(get()->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 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"); QTest::addColumn("pointerType"); QTest::addColumn("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("tabletButton"); QTest::addColumn("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"