File indexing completed on 2024-04-28 17:02:39

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0003     SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
0004     SPDX-FileCopyrightText: 2024 Fushan Wen <qydwhotmail@gmail.com>
0005  */
0006 
0007 #include "interaction.h"
0008 
0009 #include <ranges>
0010 #include <span>
0011 
0012 #include <linux/input-event-codes.h>
0013 
0014 #include <QDBusConnection>
0015 #include <QDBusInterface>
0016 #include <QDBusMessage>
0017 #include <QDBusMetaType>
0018 #include <QDBusReply>
0019 #include <QDebug>
0020 #include <QGuiApplication>
0021 #include <QScopeGuard>
0022 #include <QThread>
0023 
0024 FakeInputInterface *s_interface;
0025 
0026 QHash<unsigned /* unique id */, QPoint> PointerAction::s_positions = {};
0027 QSet<unsigned /*unique id*/> PointerAction::s_touchPoints = {};
0028 QSet<int /* pressed button */> PointerAction::s_mouseButtons = {};
0029 
0030 namespace
0031 {
0032 // Magic offset stolen from kwin.
0033 constexpr auto EVDEV_OFFSET = 8U;
0034 
0035 struct LayoutNames {
0036     QString shortName;
0037     QString displayName;
0038     QString longName;
0039 };
0040 
0041 QDBusArgument &operator<<(QDBusArgument &argument, const LayoutNames &layoutNames)
0042 {
0043     argument.beginStructure();
0044     argument << layoutNames.shortName << layoutNames.displayName << layoutNames.longName;
0045     argument.endStructure();
0046     return argument;
0047 }
0048 
0049 const QDBusArgument &operator>>(const QDBusArgument &argument, LayoutNames &layoutNames)
0050 {
0051     argument.beginStructure();
0052     argument >> layoutNames.shortName >> layoutNames.displayName >> layoutNames.longName;
0053     argument.endStructure();
0054     return argument;
0055 }
0056 
0057 [[nodiscard]] unsigned getUniqueId(const QString &idStr)
0058 {
0059     static unsigned lastId = 0;
0060     static QHash<QString, unsigned> table;
0061     if (auto it = table.find(idStr); it != table.end()) {
0062         return *it;
0063     }
0064     return *table.insert(idStr, lastId++);
0065 }
0066 
0067 } // namespace
0068 
0069 Q_DECLARE_METATYPE(LayoutNames)
0070 
0071 FakeInputInterface::FakeInputInterface()
0072     : QWaylandClientExtensionTemplate<FakeInputInterface>(ORG_KDE_KWIN_FAKE_INPUT_DESTROY_SINCE_VERSION)
0073 {
0074     auto startAuth = [this]() {
0075         authenticate(QStringLiteral("inputsynth"), QStringLiteral("hello"));
0076 #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
0077         m_display = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>()->display();
0078 #else
0079         m_display = static_cast<struct wl_display *>(qGuiApp->platformNativeInterface()->nativeResourceForIntegration("wl_display"));
0080 #endif
0081         wl_display_roundtrip(m_display);
0082 
0083         Q_EMIT readyChanged();
0084     };
0085 
0086 #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
0087     initialize();
0088     QMetaObject::invokeMethod(this, startAuth, Qt::QueuedConnection);
0089 #else
0090     connect(this, &FakeInputInterface::activeChanged, this, startAuth);
0091 #endif
0092 }
0093 
0094 FakeInputInterface::~FakeInputInterface()
0095 {
0096 }
0097 
0098 void FakeInputInterface::roundtrip(bool touch)
0099 {
0100     if (touch) {
0101         touch_frame();
0102     }
0103     wl_display_roundtrip(s_interface->m_display);
0104 }
0105 
0106 void FakeInputInterface::sendKey(const std::vector<quint32> &linuxModifiers, quint32 linuxKeyCode, wl_keyboard_key_state keyState)
0107 {
0108     for (const auto &modifier : linuxModifiers) {
0109         qDebug() << "  pressing modifier" << modifier;
0110         keyboard_key(modifier, WL_KEYBOARD_KEY_STATE_PRESSED);
0111         wl_display_roundtrip(m_display);
0112     }
0113 
0114     qDebug() << "    key (state)" << linuxKeyCode << keyState;
0115     keyboard_key(linuxKeyCode, keyState);
0116     wl_display_roundtrip(m_display);
0117 
0118     for (const auto &modifier : linuxModifiers) {
0119         qDebug() << "  releasing modifier" << modifier;
0120         keyboard_key(modifier, WL_KEYBOARD_KEY_STATE_RELEASED);
0121         wl_display_roundtrip(m_display);
0122     }
0123 }
0124 
0125 BaseAction::BaseAction()
0126 {
0127 }
0128 
0129 BaseAction::~BaseAction()
0130 {
0131 }
0132 
0133 KeyboardAction::KeyboardAction(const QChar &key, wl_keyboard_key_state keyState)
0134     : BaseAction()
0135     , m_keysym(charToKeysym(key))
0136     , m_context(xkb_context_new(XKB_CONTEXT_NO_FLAGS))
0137     , m_ruleNames({.rules = nullptr, .model = nullptr, .layout = defaultLayout().constData(), .variant = nullptr, .options = nullptr})
0138     , m_keymap(xkb_keymap_new_from_names(m_context.get(), &m_ruleNames, XKB_KEYMAP_COMPILE_NO_FLAGS))
0139     , m_state(xkb_state_new(m_keymap.get()))
0140     , m_layout(xkb_state_serialize_layout(m_state.get(), XKB_STATE_LAYOUT_EFFECTIVE))
0141     , m_modCount(xkb_keymap_num_mods(m_keymap.get()))
0142     , m_keyState(keyState)
0143 {
0144     Q_ASSERT(!defaultLayout().isEmpty());
0145     Q_ASSERT(m_keysym != XKB_KEY_NoSymbol);
0146 
0147     qDebug() << "looking for keysym" << m_keysym << "for char" << key;
0148 
0149     // Load the modifier keycodes. This walks all modifiers and maps them to keycodes. Effectively just resolving
0150     // that Alt is 123 and Ctrl is 456 etc.
0151     loadModifiers();
0152 
0153     // Once we know our modifiers we can resolve the actual key by iterating the keysyms.
0154     for (const auto &keycode : std::views::iota(xkb_keymap_min_keycode(m_keymap.get()), xkb_keymap_max_keycode(m_keymap.get()))) {
0155         for (const auto &level : std::views::iota(0U, xkb_keymap_num_levels_for_key(m_keymap.get(), keycode, m_layout))) {
0156             const xkb_keysym_t *syms = nullptr;
0157             uint num_syms = xkb_keymap_key_get_syms_by_level(m_keymap.get(), keycode, m_layout, level, &syms);
0158             for (const auto &sym : std::span{syms, num_syms}) {
0159                 if (sym != m_keysym) {
0160                     continue;
0161                 }
0162                 qWarning() << "found keysym";
0163                 m_keycode = keycode - EVDEV_OFFSET;
0164                 m_level = level;
0165                 // We found the key. As a last step we'll need to resolve the modifiers required to trigger this
0166                 // key. e.g. to produce 'A' we need to press the 'Shift' modifier before the 'a' key.
0167                 resolveModifiersForKey(keycode);
0168             }
0169         }
0170     }
0171     Q_ASSERT(m_keycode != XKB_KEYCODE_INVALID);
0172 }
0173 
0174 KeyboardAction::~KeyboardAction()
0175 {
0176 }
0177 
0178 void KeyboardAction::perform()
0179 {
0180     s_interface->sendKey(linuxModifiers(), m_keycode, m_keyState);
0181 }
0182 
0183 [[nodiscard]] std::vector<quint32> KeyboardAction::linuxModifiers() const
0184 {
0185     if (m_level == 0) {
0186         return {};
0187     }
0188 
0189     qDebug() << m_modifiers;
0190     std::vector<quint32> ret;
0191     for (const auto &modifier : m_modifiers) {
0192         if (m_modifierNameToSym.contains(modifier)) {
0193             const auto modifierSym = m_modifierNameToSym.value(modifier);
0194             const auto modifierCodes = m_modifierSymToCodes.value(modifierSym);
0195             // Returning the first possible code only is a bit meh but seems to work fine so far.
0196             ret.push_back(modifierCodes.at(0));
0197         }
0198     }
0199     if (ret.empty()) {
0200         qCritical("Unknown level!");
0201         return {};
0202     }
0203     return ret;
0204 }
0205 
0206 QByteArray KeyboardAction::defaultLayout() const
0207 {
0208     static const auto layout = [] {
0209         if (qEnvironmentVariableIsSet("KWIN_XKB_DEFAULT_KEYMAP")) {
0210             auto layout = qgetenv("XKB_DEFAULT_LAYOUT");
0211             qDebug() << "synthesizing environment-influenced layout:" << layout;
0212             return layout;
0213         }
0214 
0215         // When running outside a nested kwin we'll need to follow whatever kwin has defined as layout.
0216 
0217         qDBusRegisterMetaType<LayoutNames>();
0218         qDBusRegisterMetaType<QList<LayoutNames>>();
0219 
0220         QDBusMessage layoutMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.keyboard"),
0221                                                                     QStringLiteral("/Layouts"),
0222                                                                     QStringLiteral("org.kde.KeyboardLayouts"),
0223                                                                     QStringLiteral("getLayout"));
0224         QDBusReply<int> layoutReply = QDBusConnection::sessionBus().call(layoutMessage);
0225         if (!layoutReply.isValid()) {
0226             qWarning() << "Failed to get layout index" << layoutReply.error().message() << "defaulting to us";
0227             return QByteArrayLiteral("us");
0228         }
0229         const auto layoutIndex = layoutReply.value();
0230 
0231         QDBusMessage listMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.keyboard"),
0232                                                                   QStringLiteral("/Layouts"),
0233                                                                   QStringLiteral("org.kde.KeyboardLayouts"),
0234                                                                   QStringLiteral("getLayoutsList"));
0235         QDBusReply<QList<LayoutNames>> listReply = QDBusConnection::sessionBus().call(listMessage);
0236         if (!listReply.isValid()) {
0237             qWarning() << "Failed to get layout list" << listReply.error().message() << "defaulting to us";
0238             return QByteArrayLiteral("us");
0239         }
0240 
0241         auto layout = listReply.value().at(layoutIndex).shortName.toUtf8();
0242         qDebug() << "synthesizing layout:" << layout;
0243         return layout;
0244     }();
0245     Q_ASSERT(!layout.isEmpty());
0246     return layout;
0247 }
0248 
0249 void KeyboardAction::loadModifiers()
0250 {
0251     static constexpr auto modifierKeys = {XKB_KEY_Shift_L,
0252                                           XKB_KEY_Alt_L,
0253                                           XKB_KEY_Meta_L,
0254                                           XKB_KEY_Mode_switch,
0255                                           XKB_KEY_Super_L,
0256                                           XKB_KEY_Super_R,
0257                                           XKB_KEY_Hyper_L,
0258                                           XKB_KEY_Hyper_R,
0259                                           XKB_KEY_ISO_Level3_Shift,
0260                                           XKB_KEY_ISO_Level5_Shift};
0261 
0262     for (const auto &keycode : std::views::iota(xkb_keymap_min_keycode(m_keymap.get()), xkb_keymap_max_keycode(m_keymap.get()))) {
0263         for (const auto &level : std::views::iota(0U, xkb_keymap_num_levels_for_key(m_keymap.get(), keycode, m_layout))) {
0264             const xkb_keysym_t *syms = nullptr;
0265             uint num_syms = xkb_keymap_key_get_syms_by_level(m_keymap.get(), keycode, m_layout, level, &syms);
0266             for (const auto &sym : std::span{syms, num_syms}) {
0267                 if (const auto it = std::ranges::find(modifierKeys, sym); it == modifierKeys.end()) {
0268                     continue;
0269                 }
0270 
0271                 m_modifierSymToCodes[sym].push_back(keycode - EVDEV_OFFSET);
0272 
0273                 // The sym is a modifier. Find out which by pressing the key and checking which modifiers activate.
0274                 xkb_state_update_key(m_state.get(), keycode, XKB_KEY_DOWN);
0275                 auto up = qScopeGuard([this, &keycode] {
0276                     xkb_state_update_key(m_state.get(), keycode, XKB_KEY_UP);
0277                 });
0278 
0279                 for (const auto &mod : std::views::iota(0U, m_modCount)) {
0280                     if (xkb_state_mod_index_is_active(m_state.get(), mod, XKB_STATE_MODS_EFFECTIVE) <= 0) {
0281                         continue;
0282                     }
0283                     m_modifierNameToSym[QString::fromUtf8(xkb_keymap_mod_get_name(m_keymap.get(), mod))] = sym;
0284                     break;
0285                 }
0286             }
0287         }
0288     }
0289 }
0290 
0291 void KeyboardAction::resolveModifiersForKey(xkb_keycode_t keycode)
0292 {
0293     static constexpr auto maxMasks = 1; // we only care about a single mask because we need only one way to access the key
0294     std::array<xkb_mod_mask_t, maxMasks> mask{};
0295     const auto maskSize = xkb_keymap_key_get_mods_for_level(m_keymap.get(), keycode, m_layout, m_level, mask.data(), mask.size());
0296     for (const auto &mask : std::span{mask.data(), maskSize}) {
0297         for (const auto &mod : std::views::iota(0U, m_modCount)) {
0298             if ((mask & (1 << mod)) == 0) {
0299                 continue;
0300             }
0301             const auto name = xkb_keymap_mod_get_name(m_keymap.get(), mod);
0302             const auto qName = QString::fromUtf8(name);
0303             if (!m_modifiers.contains(qName)) {
0304                 m_modifiers.push_back(qName);
0305             }
0306         }
0307     }
0308 }
0309 
0310 xkb_keysym_t KeyboardAction::charToKeysym(const QChar &key)
0311 {
0312     // A bit awkward but not all keys manage to map via xkb_utf32_to_keysym so we augment the lookup.
0313     // https://www.selenium.dev/selenium/docs/api/py/webdriver/selenium.webdriver.common.keys.html#selenium.webdriver.common.keys.Keys.ARROW_LEFT
0314     static const QHash<QChar, xkb_keysym_t> charToKeyMap{
0315         {QChar(u'\ue025'), XKB_KEY_plus},      {QChar(u'\ue00a'), XKB_KEY_Alt_L},
0316         {QChar(u'\ue015'), XKB_KEY_Down},      {QChar(u'\ue012'), XKB_KEY_Left},
0317         {QChar(u'\ue014'), XKB_KEY_Right},     {QChar(u'\ue013'), XKB_KEY_Up},
0318         {QChar(u'\ue003'), XKB_KEY_BackSpace}, {QChar(u'\ue001'), XKB_KEY_Cancel},
0319         {QChar(u'\ue005'), XKB_KEY_Clear},     {QChar(u'\ue009'), XKB_KEY_Control_L},
0320         {QChar(u'\ue028'), XKB_KEY_period},    {QChar(u'\ue017'), XKB_KEY_Delete},
0321         {QChar(u'\ue029'), XKB_KEY_slash},     {QChar(u'\ue010'), XKB_KEY_End},
0322         {QChar(u'\ue007'), XKB_KEY_KP_Enter},  {QChar(u'\ue019'), XKB_KEY_equal},
0323         {QChar(u'\ue00c'), XKB_KEY_Escape},    {QChar(u'\ue031'), XKB_KEY_F1},
0324         {QChar(u'\ue03a'), XKB_KEY_F10},       {QChar(u'\ue03b'), XKB_KEY_F11},
0325         {QChar(u'\ue03c'), XKB_KEY_F12},       {QChar(u'\ue032'), XKB_KEY_F2},
0326         {QChar(u'\ue033'), XKB_KEY_F3},        {QChar(u'\ue034'), XKB_KEY_F4},
0327         {QChar(u'\ue035'), XKB_KEY_F5},        {QChar(u'\ue036'), XKB_KEY_F6},
0328         {QChar(u'\ue037'), XKB_KEY_F7},        {QChar(u'\ue038'), XKB_KEY_F8},
0329         {QChar(u'\ue039'), XKB_KEY_F9},        {QChar(u'\ue002'), XKB_KEY_Help},
0330         {QChar(u'\ue011'), XKB_KEY_Home},      {QChar(u'\ue016'), XKB_KEY_Insert},
0331         {QChar(u'\ue008'), XKB_KEY_Shift_L},   {QChar(u'\ue03d'), XKB_KEY_Meta_L},
0332         {QChar(u'\ue024'), XKB_KEY_multiply},  {QChar(u'\ue000'), XKB_KEY_NoSymbol},
0333         {QChar(u'\ue01a'), XKB_KEY_KP_0},      {QChar(u'\ue01b'), XKB_KEY_KP_1},
0334         {QChar(u'\ue01c'), XKB_KEY_KP_2},      {QChar(u'\ue01d'), XKB_KEY_KP_3},
0335         {QChar(u'\ue01e'), XKB_KEY_KP_4},      {QChar(u'\ue01f'), XKB_KEY_KP_5},
0336         {QChar(u'\ue020'), XKB_KEY_KP_6},      {QChar(u'\ue021'), XKB_KEY_KP_7},
0337         {QChar(u'\ue022'), XKB_KEY_KP_8},      {QChar(u'\ue023'), XKB_KEY_KP_9},
0338         {QChar(u'\ue00f'), XKB_KEY_Page_Down}, {QChar(u'\ue00e'), XKB_KEY_Page_Up},
0339         {QChar(u'\ue00b'), XKB_KEY_Pause},     {QChar(u'\ue006'), XKB_KEY_Return},
0340         {QChar(u'\ue018'), XKB_KEY_semicolon}, {QChar(u'\ue026'), XKB_KEY_comma},
0341         {QChar(u'\ue00d'), XKB_KEY_space},     {QChar(u'\ue027'), XKB_KEY_minus},
0342         {QChar(u'\ue004'), XKB_KEY_Tab},       {QChar(u'\ue040'), XKB_KEY_Zenkaku_Hankaku},
0343     };
0344 
0345     if (auto it = charToKeyMap.constFind(key); it != charToKeyMap.cend()) {
0346         return it.value();
0347     }
0348 
0349     return xkb_utf32_to_keysym(key.unicode());
0350 }
0351 
0352 PauseAction::PauseAction(unsigned long duration)
0353     : BaseAction()
0354     , m_duration(duration)
0355 {
0356 }
0357 
0358 PauseAction::~PauseAction()
0359 {
0360 }
0361 
0362 void PauseAction::perform()
0363 {
0364     QThread::msleep(m_duration);
0365 }
0366 
0367 WheelAction::WheelAction(const QString &id, const QPoint &pos, const QPoint &deltaPos, unsigned long duration)
0368     : m_uniqueId(getUniqueId(id))
0369     , m_pos(pos)
0370     , m_deltaPos(deltaPos)
0371     , m_duration(duration)
0372 {
0373 }
0374 
0375 WheelAction::~WheelAction()
0376 {
0377 }
0378 
0379 void WheelAction::perform()
0380 {
0381     PointerAction::s_positions[m_uniqueId] = m_pos;
0382     s_interface->pointer_motion_absolute(wl_fixed_from_int(m_pos.x()), wl_fixed_from_int(m_pos.y()));
0383     s_interface->roundtrip();
0384 
0385     if (m_deltaPos.x() != 0) {
0386         s_interface->axis(WL_POINTER_AXIS_HORIZONTAL_SCROLL, wl_fixed_from_int(m_deltaPos.x()));
0387         s_interface->roundtrip();
0388     }
0389     if (m_deltaPos.y() != 0) {
0390         s_interface->axis(WL_POINTER_AXIS_VERTICAL_SCROLL, wl_fixed_from_int(m_deltaPos.y()));
0391         s_interface->roundtrip();
0392     }
0393 
0394     QThread::msleep(m_duration);
0395 }
0396 
0397 PointerAction::PointerAction(PointerKind pointerType, const QString &id, ActionType actionType, Button button, unsigned long duration)
0398     : m_uniqueId(getUniqueId(id))
0399     , m_pointerType(pointerType)
0400     , m_actionType(actionType)
0401     , m_button(button)
0402     , m_duration(duration)
0403 {
0404 }
0405 
0406 PointerAction::~PointerAction()
0407 {
0408 }
0409 
0410 void PointerAction::setPosition(const QPoint &pos, Origin origin)
0411 {
0412     m_pos = pos;
0413     m_origin = origin;
0414 }
0415 
0416 void PointerAction::perform()
0417 {
0418     static const QHash<int, uint32_t> s_buttonMap = {
0419         {static_cast<int>(Button::Left), BTN_LEFT},
0420         {static_cast<int>(Button::Middle), BTN_MIDDLE},
0421         {static_cast<int>(Button::Right), BTN_RIGHT},
0422         {static_cast<int>(Button::Forward), BTN_FORWARD},
0423         {static_cast<int>(Button::Back), BTN_BACK},
0424     };
0425 
0426     switch (m_actionType) {
0427     case ActionType::Move: {
0428         auto lastPosIt = s_positions.find(m_uniqueId);
0429         if (m_pointerType == PointerKind::Mouse) {
0430             if (lastPosIt == s_positions.end()) {
0431                 lastPosIt = s_positions.insert(m_uniqueId, QPoint(0, 0));
0432             }
0433         } else if (lastPosIt == s_positions.end() || !s_touchPoints.contains(m_uniqueId)) {
0434             // Save the initial position
0435             s_positions[m_uniqueId] = m_pos;
0436             return;
0437         }
0438         // Interpolate the trail based on the total duration
0439         constexpr double stepDurationMs = 50.0; // Can't be too short otherwise Qt will ignore some events
0440         int steps = 1;
0441         if (m_duration > stepDurationMs) {
0442             int xDiff = 0;
0443             int yDiff = 0;
0444             if (m_origin == Origin::Pointer) {
0445                 xDiff = m_pos.x();
0446                 yDiff = m_pos.y();
0447             } else {
0448                 xDiff = m_pos.x() - lastPosIt->x();
0449                 yDiff = m_pos.y() - lastPosIt->y();
0450             }
0451 
0452             // Calculate how many steps are going to be performed
0453             steps = std::ceil(m_duration / stepDurationMs);
0454             // Distance that advances in each step
0455             const int stepXDiff = std::lround(xDiff / double(steps));
0456             const int stepYDiff = std::lround(yDiff / double(steps));
0457 
0458             for (int i : std::views::iota(1, steps)) {
0459                 const wl_fixed_t newX = wl_fixed_from_int(lastPosIt->x() + stepXDiff * i);
0460                 const wl_fixed_t newY = wl_fixed_from_int(lastPosIt->y() + stepYDiff * i);
0461                 if (m_pointerType == PointerKind::Touch) {
0462                     s_interface->touch_motion(m_uniqueId, newX, newY);
0463                 } else {
0464                     s_interface->pointer_motion_absolute(newX, newY);
0465                 }
0466                 s_interface->roundtrip(m_pointerType == PointerKind::Touch);
0467                 QThread::msleep(stepDurationMs);
0468             }
0469         }
0470         // Final round of move
0471         const wl_fixed_t lastX = wl_fixed_from_int(m_pos.x());
0472         const wl_fixed_t lastY = wl_fixed_from_int(m_pos.y());
0473         if (m_pointerType == PointerKind::Touch) {
0474             s_interface->touch_motion(m_uniqueId, lastX, lastY);
0475         } else {
0476             s_interface->pointer_motion_absolute(lastX, lastY);
0477         }
0478         s_interface->roundtrip(m_pointerType == PointerKind::Touch);
0479         // Sleep to the total duration
0480         QThread::msleep(m_duration - (steps - 1) * stepDurationMs);
0481         // Update the last position
0482         *lastPosIt = m_pos;
0483 
0484         return;
0485     }
0486 
0487     case ActionType::Down: {
0488         QPoint lastPos;
0489         if (auto lastPosIt = s_positions.find(m_uniqueId); lastPosIt != s_positions.end()) {
0490             lastPos = *lastPosIt;
0491         } else {
0492             lastPos = {0, 0};
0493             s_positions[m_uniqueId] = lastPos;
0494         }
0495 
0496         if (m_pointerType == PointerKind::Touch) {
0497             if (s_touchPoints.contains(m_uniqueId)) {
0498                 return;
0499             }
0500             s_touchPoints.insert(m_uniqueId);
0501             qDebug() << "sending touch_down at" << lastPos;
0502             s_interface->touch_down(m_uniqueId, wl_fixed_from_int(lastPos.x()), wl_fixed_from_int(lastPos.y()));
0503         } else {
0504             if (s_mouseButtons.contains(static_cast<int>(m_button))) {
0505                 return;
0506             }
0507             s_mouseButtons.insert(static_cast<int>(m_button));
0508             qDebug() << "clicking at" << lastPos;
0509             s_interface->button(s_buttonMap[static_cast<int>(m_button)], WL_POINTER_BUTTON_STATE_PRESSED);
0510         }
0511         s_interface->roundtrip(m_pointerType == PointerKind::Touch);
0512         return;
0513     }
0514     case ActionType::Up: {
0515         if (m_pointerType == PointerKind::Touch) {
0516             if (s_touchPoints.remove(m_uniqueId)) {
0517                 qDebug() << "sending touch_up";
0518                 s_interface->touch_up(m_uniqueId);
0519             }
0520         } else {
0521             if (s_mouseButtons.remove(static_cast<int>(m_button))) {
0522                 qDebug() << "releasing mouse button" << static_cast<int>(m_button);
0523                 s_interface->button(s_buttonMap[static_cast<int>(m_button)], WL_POINTER_BUTTON_STATE_RELEASED);
0524             }
0525         }
0526         s_interface->roundtrip(m_pointerType == PointerKind::Touch);
0527         return;
0528     }
0529     case ActionType::Cancel: {
0530         if (m_pointerType == PointerKind::Touch) {
0531             if (!s_touchPoints.empty()) {
0532                 s_interface->touch_cancel();
0533                 s_touchPoints.clear();
0534             }
0535         } else {
0536             if (!s_mouseButtons.empty()) {
0537                 for (auto button : s_mouseButtons) {
0538                     s_interface->button(s_buttonMap[button], WL_POINTER_BUTTON_STATE_RELEASED);
0539                 }
0540                 s_mouseButtons.clear();
0541             }
0542         }
0543         s_interface->roundtrip(m_pointerType == PointerKind::Touch);
0544         return;
0545     }
0546     }
0547 
0548     qWarning() << "Ignored an unknown action type" << static_cast<int>(m_actionType);
0549 }
0550 
0551 #include "moc_interaction.cpp"