File indexing completed on 2024-05-05 05:47:41
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"