| /**************************************************************************** |
| ** |
| ** 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" |