diff options
Diffstat (limited to 'src/plugins/platforms/xcb/qxcbconnection_xi2.cpp')
-rw-r--r-- | src/plugins/platforms/xcb/qxcbconnection_xi2.cpp | 573 |
1 files changed, 411 insertions, 162 deletions
diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp index 4657c2ac89..4f62a1880b 100644 --- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** 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 Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** 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-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qxcbconnection.h" #include "qxcbkeyboard.h" @@ -51,13 +15,29 @@ #include <xcb/xinput.h> +#if QT_CONFIG(gestures) +#define QT_XCB_HAS_TOUCHPAD_GESTURES (XCB_INPUT_MINOR_VERSION >= 4) +#endif + +using namespace Qt::StringLiterals; + using qt_xcb_input_device_event_t = xcb_input_button_press_event_t; +#if QT_XCB_HAS_TOUCHPAD_GESTURES +using qt_xcb_input_pinch_event_t = xcb_input_gesture_pinch_begin_event_t; +using qt_xcb_input_swipe_event_t = xcb_input_gesture_swipe_begin_event_t; +#endif struct qt_xcb_input_event_mask_t { xcb_input_event_mask_t header; - uint32_t mask; + alignas(4) uint8_t mask[8] = {}; // up to 2 units of 4 bytes }; +static inline void setXcbMask(uint8_t* mask, int bit) +{ + // note that XI protocol always uses little endian for masks over the wire + mask[bit >> 3] |= 1 << (bit & 7); +} + void QXcbConnection::xi2SelectStateEvents() { // These state events do not depend on a specific X window, but are global @@ -65,9 +45,9 @@ void QXcbConnection::xi2SelectStateEvents() qt_xcb_input_event_mask_t xiEventMask; xiEventMask.header.deviceid = XCB_INPUT_DEVICE_ALL; xiEventMask.header.mask_len = 1; - xiEventMask.mask = XCB_INPUT_XI_EVENT_MASK_HIERARCHY; - xiEventMask.mask |= XCB_INPUT_XI_EVENT_MASK_DEVICE_CHANGED; - xiEventMask.mask |= XCB_INPUT_XI_EVENT_MASK_PROPERTY; + setXcbMask(xiEventMask.mask, XCB_INPUT_HIERARCHY); + setXcbMask(xiEventMask.mask, XCB_INPUT_DEVICE_CHANGED); + setXcbMask(xiEventMask.mask, XCB_INPUT_PROPERTY); xcb_input_xi_select_events(xcb_connection(), rootWindow(), 1, &xiEventMask.header); } @@ -76,23 +56,33 @@ void QXcbConnection::xi2SelectDeviceEvents(xcb_window_t window) if (window == rootWindow()) return; - uint32_t bitMask = XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS; - bitMask |= XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE; - bitMask |= XCB_INPUT_XI_EVENT_MASK_MOTION; + qt_xcb_input_event_mask_t mask; + + setXcbMask(mask.mask, XCB_INPUT_BUTTON_PRESS); + setXcbMask(mask.mask, XCB_INPUT_BUTTON_RELEASE); + setXcbMask(mask.mask, XCB_INPUT_MOTION); // There is a check for enter/leave events in plain xcb enter/leave event handler, // core enter/leave events will be ignored in this case. - bitMask |= XCB_INPUT_XI_EVENT_MASK_ENTER; - bitMask |= XCB_INPUT_XI_EVENT_MASK_LEAVE; + setXcbMask(mask.mask, XCB_INPUT_ENTER); + setXcbMask(mask.mask, XCB_INPUT_LEAVE); if (isAtLeastXI22()) { - bitMask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN; - bitMask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE; - bitMask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_END; + setXcbMask(mask.mask, XCB_INPUT_TOUCH_BEGIN); + setXcbMask(mask.mask, XCB_INPUT_TOUCH_UPDATE); + setXcbMask(mask.mask, XCB_INPUT_TOUCH_END); + } +#if QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES + if (isAtLeastXI24()) { + setXcbMask(mask.mask, XCB_INPUT_GESTURE_PINCH_BEGIN); + setXcbMask(mask.mask, XCB_INPUT_GESTURE_PINCH_UPDATE); + setXcbMask(mask.mask, XCB_INPUT_GESTURE_PINCH_END); + setXcbMask(mask.mask, XCB_INPUT_GESTURE_SWIPE_BEGIN); + setXcbMask(mask.mask, XCB_INPUT_GESTURE_SWIPE_UPDATE); + setXcbMask(mask.mask, XCB_INPUT_GESTURE_SWIPE_END); } +#endif // QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES - qt_xcb_input_event_mask_t mask; mask.header.deviceid = XCB_INPUT_DEVICE_ALL; - mask.header.mask_len = 1; - mask.mask = bitMask; + mask.header.mask_len = 2; xcb_void_cookie_t cookie = xcb_input_xi_select_events_checked(xcb_connection(), window, 1, &mask.header); xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie); @@ -238,7 +228,7 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting, auto *deviceInfo = reinterpret_cast<xcb_input_xi_device_info_t *>(info); if (removeExisting) { #if QT_CONFIG(tabletevent) - for (int i = 0; i < m_tabletData.count(); ++i) { + for (int i = 0; i < m_tabletData.size(); ++i) { if (m_tabletData.at(i).deviceId == deviceInfo->deviceid) { m_tabletData.remove(i); break; @@ -251,11 +241,13 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting, const QByteArray nameRaw = QByteArray(xcb_input_xi_device_info_name(deviceInfo), xcb_input_xi_device_info_name_length(deviceInfo)); const QString name = QString::fromUtf8(nameRaw); + m_xiSlavePointerIds.append(deviceInfo->deviceid); qCDebug(lcQpaXInputDevices) << "input device " << name << "ID" << deviceInfo->deviceid; #if QT_CONFIG(tabletevent) TabletData tabletData; #endif QXcbScrollingDevicePrivate *scrollingDeviceP = nullptr; + bool used = false; auto scrollingDevice = [&]() { if (!scrollingDeviceP) scrollingDeviceP = new QXcbScrollingDevicePrivate(name, deviceInfo->deviceid, @@ -281,9 +273,9 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting, tabletData.valuatorInfo[valuatorAtom] = info; } #endif // QT_CONFIG(tabletevent) - if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel) + if (valuatorAtom == QXcbAtom::AtomRelHorizScroll || valuatorAtom == QXcbAtom::AtomRelHorizWheel) scrollingDevice()->lastScrollPosition.setX(fixed3232ToReal(vci->value)); - else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel) + else if (valuatorAtom == QXcbAtom::AtomRelVertScroll || valuatorAtom == QXcbAtom::AtomRelVertWheel) scrollingDevice()->lastScrollPosition.setY(fixed3232ToReal(vci->value)); break; } @@ -311,14 +303,14 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting, xcb_atom_t label5 = labels[4]; // Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on // button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons. - if ((!label4 || qatom(label4) == QXcbAtom::ButtonWheelUp || qatom(label4) == QXcbAtom::ButtonWheelDown) && - (!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(label5) == QXcbAtom::ButtonWheelDown)) + if ((!label4 || qatom(label4) == QXcbAtom::AtomButtonWheelUp || qatom(label4) == QXcbAtom::AtomButtonWheelDown) && + (!label5 || qatom(label5) == QXcbAtom::AtomButtonWheelUp || qatom(label5) == QXcbAtom::AtomButtonWheelDown)) scrollingDevice()->legacyOrientations |= Qt::Vertical; } if (bci->num_buttons >= 7) { xcb_atom_t label6 = labels[5]; xcb_atom_t label7 = labels[6]; - if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight)) + if ((!label6 || qatom(label6) == QXcbAtom::AtomButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::AtomButtonHorizWheelRight)) scrollingDevice()->legacyOrientations |= Qt::Horizontal; } buttonCount = bci->num_buttons; @@ -329,6 +321,9 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting, qCDebug(lcQpaXInputDevices) << " it's a keyboard"; break; case XCB_INPUT_DEVICE_CLASS_TYPE_TOUCH: +#if QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES + case XCB_INPUT_DEVICE_CLASS_TYPE_GESTURE: +#endif // QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES // will be handled in populateTouchDevices() break; default: @@ -339,49 +334,49 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting, bool isTablet = false; #if QT_CONFIG(tabletevent) // If we have found the valuators which we expect a tablet to have, it might be a tablet. - if (tabletData.valuatorInfo.contains(QXcbAtom::AbsX) && - tabletData.valuatorInfo.contains(QXcbAtom::AbsY) && - tabletData.valuatorInfo.contains(QXcbAtom::AbsPressure)) + if (tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsX) && + tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsY) && + tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsPressure)) isTablet = true; // But we need to be careful not to take the touch and tablet-button devices as tablets. QByteArray nameLower = nameRaw.toLower(); - QString dbgType = QLatin1String("UNKNOWN"); + QString dbgType = "UNKNOWN"_L1; if (nameLower.contains("eraser")) { isTablet = true; tabletData.pointerType = QPointingDevice::PointerType::Eraser; - dbgType = QLatin1String("eraser"); + dbgType = "eraser"_L1; } else if (nameLower.contains("cursor") && !(nameLower.contains("cursor controls") && nameLower.contains("trackball"))) { isTablet = true; tabletData.pointerType = QPointingDevice::PointerType::Cursor; - dbgType = QLatin1String("cursor"); + dbgType = "cursor"_L1; } else if (nameLower.contains("wacom") && nameLower.contains("finger touch")) { isTablet = false; } else if ((nameLower.contains("pen") || nameLower.contains("stylus")) && isTablet) { tabletData.pointerType = QPointingDevice::PointerType::Pen; - dbgType = QLatin1String("pen"); + dbgType = "pen"_L1; } else if (nameLower.contains("wacom") && isTablet && !nameLower.contains("touch")) { // combined device (evdev) rather than separate pen/eraser (wacom driver) tabletData.pointerType = QPointingDevice::PointerType::Pen; - dbgType = QLatin1String("pen"); - } else if (nameLower.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) { + dbgType = "pen"_L1; + } else if (nameLower.contains("aiptek") /* && device == QXcbAtom::AtomKEYBOARD */) { // some "Genius" tablets isTablet = true; tabletData.pointerType = QPointingDevice::PointerType::Pen; - dbgType = QLatin1String("pen"); + dbgType = "pen"_L1; } else if (nameLower.contains("waltop") && nameLower.contains("tablet")) { // other "Genius" tablets // WALTOP International Corp. Slim Tablet isTablet = true; tabletData.pointerType = QPointingDevice::PointerType::Pen; - dbgType = QLatin1String("pen"); + dbgType = "pen"_L1; } else if (nameLower.contains("uc-logic") && isTablet) { tabletData.pointerType = QPointingDevice::PointerType::Pen; - dbgType = QLatin1String("pen"); + dbgType = "pen"_L1; } else if (nameLower.contains("ugee")) { isTablet = true; tabletData.pointerType = QPointingDevice::PointerType::Pen; - dbgType = QLatin1String("pen"); + dbgType = "pen"_L1; } else { isTablet = false; } @@ -392,9 +387,9 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting, m_tabletData.append(tabletData); qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType; QPointingDevice::Capabilities capsOverride = QInputDevice::Capability::None; - if (tabletData.valuatorInfo.contains(QXcbAtom::AbsTiltX)) + if (tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsTiltX)) capsOverride.setFlag(QInputDevice::Capability::XTilt); - if (tabletData.valuatorInfo.contains(QXcbAtom::AbsTiltY)) + if (tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsTiltY)) capsOverride.setFlag(QInputDevice::Capability::YTilt); // TODO can we get USB ID? Q_ASSERT(deviceInfo->deviceid == tabletData.deviceId); @@ -412,7 +407,7 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting, } if (!isTablet) { - TouchDeviceData *dev = populateTouchDevices(deviceInfo, scrollingDeviceP); + TouchDeviceData *dev = populateTouchDevices(deviceInfo, scrollingDeviceP, &used); if (dev && lcQpaXInputDevices().isDebugEnabled()) { if (dev->qtTouchDevice->type() == QInputDevice::DeviceType::TouchScreen) qCDebug(lcQpaXInputDevices, " it's a touchscreen with type %d capabilities 0x%X max touch points %d", @@ -435,6 +430,7 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting, if (master) scrollingDeviceP->seatName = master->seatName(); QWindowSystemInterface::registerInputDevice(new QXcbScrollingDevice(*scrollingDeviceP, master)); + used = true; } else { QWindowSystemInterface::registerInputDevice(new QPointingDevice( name, deviceInfo->deviceid, @@ -442,14 +438,22 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting, caps, 1, buttonCount, (master ? master->seatName() : QString()), QPointingDeviceUniqueId(), master)); } } + + if (!used && scrollingDeviceP) { + QXcbScrollingDevice *holder = new QXcbScrollingDevice(*scrollingDeviceP, master); + holder->deleteLater(); + } } +/*! + Find all X11 input devices at startup, or react to a device hierarchy event, + and create/delete the corresponding QInputDevice instances as necessary. + Afterwards, we expect QInputDevice::devices() to contain only the + Qt-relevant devices that \c {xinput list} reports. The parent of each master + device is this QXcbConnection object; the parent of each slave is its master. +*/ void QXcbConnection::xi2SetupDevices() { -#if QT_CONFIG(tabletevent) - m_tabletData.clear(); -#endif - m_touchDevices.clear(); m_xiMasterPointerIds.clear(); auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), XCB_INPUT_DEVICE_ALL); @@ -458,6 +462,18 @@ void QXcbConnection::xi2SetupDevices() return; } + // Start with all known devices; remove the ones that still exist. + // Afterwards, previousDevices will be the list of those that we should delete. + QList<const QInputDevice *> previousDevices = QInputDevice::devices(); + // Return true if the device with the given systemId is new; + // otherwise remove it from previousDevices and return false. + auto newOrKeep = [&previousDevices](qint64 systemId) { + // if nothing is removed from previousDevices, it's a new device + return !previousDevices.removeIf([systemId](const QInputDevice *dev) { + return dev->systemId() == systemId; + }); + }; + // XInput doesn't provide a way to identify "seats"; but each device has an attachment to another device. // So we make up a seatId: master-keyboard-id << 16 | master-pointer-id. @@ -466,17 +482,21 @@ void QXcbConnection::xi2SetupDevices() xcb_input_xi_device_info_t *deviceInfo = it.data; switch (deviceInfo->type) { case XCB_INPUT_DEVICE_TYPE_MASTER_KEYBOARD: { - auto dev = new QInputDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), - deviceInfo->deviceid, QInputDevice::DeviceType::Keyboard, - QString::number(deviceInfo->deviceid << 16 | deviceInfo->attachment, 16), this); - QWindowSystemInterface::registerInputDevice(dev); + if (newOrKeep(deviceInfo->deviceid)) { + auto dev = new QInputDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), + deviceInfo->deviceid, QInputDevice::DeviceType::Keyboard, + QString::number(deviceInfo->deviceid << 16 | deviceInfo->attachment, 16), this); + QWindowSystemInterface::registerInputDevice(dev); + } } break; case XCB_INPUT_DEVICE_TYPE_MASTER_POINTER: { m_xiMasterPointerIds.append(deviceInfo->deviceid); - auto dev = new QXcbScrollingDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid, - QInputDevice::Capability::Position | QInputDevice::Capability::Scroll | QInputDevice::Capability::Hover, - 32, QString::number(deviceInfo->attachment << 16 | deviceInfo->deviceid, 16), this); - QWindowSystemInterface::registerInputDevice(dev); + if (newOrKeep(deviceInfo->deviceid)) { + auto dev = new QXcbScrollingDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid, + QInputDevice::Capability::Position | QInputDevice::Capability::Scroll | QInputDevice::Capability::Hover, + 32, QString::number(deviceInfo->attachment << 16 | deviceInfo->deviceid, 16), this); + QWindowSystemInterface::registerInputDevice(dev); + } continue; } break; default: @@ -493,23 +513,36 @@ void QXcbConnection::xi2SetupDevices() // already registered break; case XCB_INPUT_DEVICE_TYPE_SLAVE_POINTER: { - m_xiSlavePointerIds.append(deviceInfo->deviceid); - QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment)); - Q_ASSERT(master); - xi2SetupSlavePointerDevice(deviceInfo, false, qobject_cast<QPointingDevice *>(master)); + if (newOrKeep(deviceInfo->deviceid)) { + m_xiSlavePointerIds.append(deviceInfo->deviceid); + QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment)); + Q_ASSERT(master); + xi2SetupSlavePointerDevice(deviceInfo, false, qobject_cast<QPointingDevice *>(master)); + } } break; case XCB_INPUT_DEVICE_TYPE_SLAVE_KEYBOARD: { - QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment)); - Q_ASSERT(master); - QWindowSystemInterface::registerInputDevice(new QInputDevice( - QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid, - QInputDevice::DeviceType::Keyboard, master->seatName(), master)); + if (newOrKeep(deviceInfo->deviceid)) { + QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment)); + Q_ASSERT(master); + QWindowSystemInterface::registerInputDevice(new QInputDevice( + QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid, + QInputDevice::DeviceType::Keyboard, master->seatName(), master)); + } } break; case XCB_INPUT_DEVICE_TYPE_FLOATING_SLAVE: break; } } + // previousDevices is now the list of those that are no longer found + qCDebug(lcQpaXInputDevices) << "removed" << previousDevices; + for (auto it = previousDevices.constBegin(); it != previousDevices.constEnd(); ++it) { + const auto id = (*it)->systemId(); + m_xiSlavePointerIds.removeAll(id); + m_touchDevices.remove(id); + } + qDeleteAll(previousDevices); + if (m_xiMasterPointerIds.size() > 1) qCDebug(lcQpaXInputDevices) << "multi-pointer X detected"; } @@ -522,7 +555,7 @@ QXcbConnection::TouchDeviceData *QXcbConnection::touchDeviceForId(int id) return dev; } -QXcbConnection::TouchDeviceData *QXcbConnection::populateTouchDevices(void *info, QXcbScrollingDevicePrivate *scrollingDeviceP) +QXcbConnection::TouchDeviceData *QXcbConnection::populateTouchDevices(void *info, QXcbScrollingDevicePrivate *scrollingDeviceP, bool *used) { auto *deviceInfo = reinterpret_cast<xcb_input_xi_device_info_t *>(info); QPointingDevice::Capabilities caps; @@ -549,6 +582,18 @@ QXcbConnection::TouchDeviceData *QXcbConnection::populateTouchDevices(void *info } break; } +#if QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES + case XCB_INPUT_DEVICE_CLASS_TYPE_GESTURE: { + // Note that gesture devices can only be touchpads (i.e. dependent devices in XInput + // naming convention). According to XI 2.4, the same device can't have touch and + // gesture device classes. + auto *gci = reinterpret_cast<xcb_input_gesture_class_t *>(classinfo); + maxTouchPoints = gci->num_touches; + qCDebug(lcQpaXInputDevices, " has gesture class"); + type = QInputDevice::DeviceType::TouchPad; + break; + } +#endif // QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES case XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR: { auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classinfo); const QXcbAtom::Atom valuatorAtom = qatom(vci->label); @@ -563,27 +608,27 @@ QXcbConnection::TouchDeviceData *QXcbConnection::populateTouchDevices(void *info // Some devices (mice) report a resolution of 0; they will be excluded later, // for now just prevent a division by zero const int vciResolution = vci->resolution ? vci->resolution : 1; - if (valuatorAtom == QXcbAtom::AbsMTPositionX) + if (valuatorAtom == QXcbAtom::AtomAbsMTPositionX) caps |= QInputDevice::Capability::Position | QInputDevice::Capability::NormalizedPosition; - else if (valuatorAtom == QXcbAtom::AbsMTTouchMajor) + else if (valuatorAtom == QXcbAtom::AtomAbsMTTouchMajor) caps |= QInputDevice::Capability::Area; - else if (valuatorAtom == QXcbAtom::AbsMTOrientation) + else if (valuatorAtom == QXcbAtom::AtomAbsMTOrientation) dev.providesTouchOrientation = true; - else if (valuatorAtom == QXcbAtom::AbsMTPressure || valuatorAtom == QXcbAtom::AbsPressure) + else if (valuatorAtom == QXcbAtom::AtomAbsMTPressure || valuatorAtom == QXcbAtom::AtomAbsPressure) caps |= QInputDevice::Capability::Pressure; - else if (valuatorAtom == QXcbAtom::RelX) { + else if (valuatorAtom == QXcbAtom::AtomRelX) { hasRelativeCoords = true; dev.size.setWidth((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution); - } else if (valuatorAtom == QXcbAtom::RelY) { + } else if (valuatorAtom == QXcbAtom::AtomRelY) { hasRelativeCoords = true; dev.size.setHeight((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution); - } else if (valuatorAtom == QXcbAtom::AbsX) { + } else if (valuatorAtom == QXcbAtom::AtomAbsX) { caps |= QInputDevice::Capability::Position; dev.size.setWidth((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution); - } else if (valuatorAtom == QXcbAtom::AbsY) { + } else if (valuatorAtom == QXcbAtom::AtomAbsY) { caps |= QInputDevice::Capability::Position; dev.size.setHeight((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution); - } else if (valuatorAtom == QXcbAtom::RelVertWheel || valuatorAtom == QXcbAtom::RelHorizWheel) { + } else if (valuatorAtom == QXcbAtom::AtomRelVertWheel || valuatorAtom == QXcbAtom::AtomRelHorizWheel) { caps |= QInputDevice::Capability::Scroll; } break; @@ -615,6 +660,7 @@ QXcbConnection::TouchDeviceData *QXcbConnection::populateTouchDevices(void *info dev.qtTouchDevice = new QXcbScrollingDevice(*scrollingDeviceP, master); if (Q_UNLIKELY(!caps.testFlag(QInputDevice::Capability::Scroll))) qCDebug(lcQpaXInputDevices) << "unexpectedly missing RelVert/HorizWheel atoms for touchpad with scroll capability" << dev.qtTouchDevice; + *used = true; } else { dev.qtTouchDevice = new QPointingDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo), xcb_input_xi_device_info_name_length(deviceInfo)), @@ -639,7 +685,8 @@ static inline qreal fixed1616ToReal(xcb_input_fp1616_t val) void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) { auto *xiEvent = reinterpret_cast<qt_xcb_input_device_event_t *>(event); - if (m_xiSlavePointerIds.contains(xiEvent->deviceid)) { + setTime(xiEvent->time); + if (m_xiSlavePointerIds.contains(xiEvent->deviceid) && xiEvent->event_type != XCB_INPUT_PROPERTY) { if (!m_duringSystemMoveResize) return; if (xiEvent->event == XCB_NONE) @@ -673,6 +720,18 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) sourceDeviceId = xiDeviceEvent->sourceid; // use the actual device id instead of the master break; } +#if QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES + case XCB_INPUT_GESTURE_PINCH_BEGIN: + case XCB_INPUT_GESTURE_PINCH_UPDATE: + case XCB_INPUT_GESTURE_PINCH_END: + xi2HandleGesturePinchEvent(event); + return; + case XCB_INPUT_GESTURE_SWIPE_BEGIN: + case XCB_INPUT_GESTURE_SWIPE_UPDATE: + case XCB_INPUT_GESTURE_SWIPE_END: + xi2HandleGestureSwipeEvent(event); + return; +#endif // QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES case XCB_INPUT_ENTER: case XCB_INPUT_LEAVE: { xiEnterEvent = reinterpret_cast<xcb_input_enter_event_t *>(event); @@ -706,6 +765,8 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) if (auto device = QPointingDevicePrivate::pointingDeviceById(sourceDeviceId)) xi2HandleScrollEvent(event, device); + else + qCDebug(lcQpaXInputEvents) << "scroll event from unregistered device" << sourceDeviceId; if (xiDeviceEvent) { switch (xiDeviceEvent->event_type) { @@ -724,8 +785,12 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) event->event_type, xiDeviceEvent->sequence, xiDeviceEvent->detail, fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y), fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y),xiDeviceEvent->event); - if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) + if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) { xi2ProcessTouch(xiDeviceEvent, platformWindow); + } else { // When the window cannot be matched, delete it from touchPoints + if (TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid)) + dev->touchPoints.remove((xiDeviceEvent->detail % INT_MAX)); + } break; } } else if (xiEnterEvent && eventListener) { @@ -748,7 +813,16 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo { auto *xiDeviceEvent = reinterpret_cast<xcb_input_touch_begin_event_t *>(xiDevEvent); TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid); - Q_ASSERT(dev); + if (!dev) { + qCDebug(lcQpaXInputEvents) << "didn't find the dev for given sourceid - " << xiDeviceEvent->sourceid + << ", try to repopulate xi2 devices"; + xi2SetupDevices(); + dev = touchDeviceForId(xiDeviceEvent->sourceid); + if (!dev) { + qCDebug(lcQpaXInputEvents) << "still can't find the dev for it, give up."; + return; + } + } const bool firstTouch = dev->touchPoints.isEmpty(); if (xiDeviceEvent->event_type == XCB_INPUT_TOUCH_BEGIN) { QWindowSystemInterface::TouchPoint tp; @@ -764,39 +838,39 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo qreal nx = -1.0, ny = -1.0; qreal w = 0.0, h = 0.0; bool majorAxisIsY = touchPoint.area.height() > touchPoint.area.width(); - for (const TouchDeviceData::ValuatorClassInfo &vci : qAsConst(dev->valuatorInfo)) { + for (const TouchDeviceData::ValuatorClassInfo &vci : std::as_const(dev->valuatorInfo)) { double value; if (!xi2GetValuatorValueIfSet(xiDeviceEvent, vci.number, &value)) continue; if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) qCDebug(lcQpaXInputEvents, " valuator %20s value %lf from range %lf -> %lf", - atomName(vci.label).constData(), value, vci.min, vci.max); + atomName(atom(vci.label)).constData(), value, vci.min, vci.max); if (value > vci.max) value = vci.max; if (value < vci.min) value = vci.min; qreal valuatorNormalized = (value - vci.min) / (vci.max - vci.min); - if (vci.label == QXcbAtom::RelX) { + if (vci.label == QXcbAtom::AtomRelX) { nx = valuatorNormalized; - } else if (vci.label == QXcbAtom::RelY) { + } else if (vci.label == QXcbAtom::AtomRelY) { ny = valuatorNormalized; - } else if (vci.label == QXcbAtom::AbsX) { + } else if (vci.label == QXcbAtom::AtomAbsX) { nx = valuatorNormalized; - } else if (vci.label == QXcbAtom::AbsY) { + } else if (vci.label == QXcbAtom::AtomAbsY) { ny = valuatorNormalized; - } else if (vci.label == QXcbAtom::AbsMTPositionX) { + } else if (vci.label == QXcbAtom::AtomAbsMTPositionX) { nx = valuatorNormalized; - } else if (vci.label == QXcbAtom::AbsMTPositionY) { + } else if (vci.label == QXcbAtom::AtomAbsMTPositionY) { ny = valuatorNormalized; - } else if (vci.label == QXcbAtom::AbsMTTouchMajor) { + } else if (vci.label == QXcbAtom::AtomAbsMTTouchMajor) { const qreal sw = screen->geometry().width(); const qreal sh = screen->geometry().height(); w = valuatorNormalized * qHypot(sw, sh); - } else if (vci.label == QXcbAtom::AbsMTTouchMinor) { + } else if (vci.label == QXcbAtom::AtomAbsMTTouchMinor) { const qreal sw = screen->geometry().width(); const qreal sh = screen->geometry().height(); h = valuatorNormalized * qHypot(sw, sh); - } else if (vci.label == QXcbAtom::AbsMTOrientation) { + } else if (vci.label == QXcbAtom::AtomAbsMTOrientation) { // Find the closest axis. // 0 corresponds to the Y axis, vci.max to the X axis. // Flipping over the Y axis and rotating by 180 degrees @@ -807,7 +881,7 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo value -= 2 * vci.max; value = qAbs(value); majorAxisIsY = value < vci.max - value; - } else if (vci.label == QXcbAtom::AbsMTPressure || vci.label == QXcbAtom::AbsPressure) { + } else if (vci.label == QXcbAtom::AtomAbsMTPressure || vci.label == QXcbAtom::AtomAbsPressure) { touchPoint.pressure = valuatorNormalized; } @@ -939,7 +1013,7 @@ void QXcbConnection::abortSystemMoveResize(xcb_window_t window) qCDebug(lcQpaXInputDevices) << "sending client message NET_WM_MOVERESIZE_CANCEL to window: " << window; m_startSystemMoveResizeInfo.window = XCB_NONE; - const xcb_atom_t moveResize = connection()->atom(QXcbAtom::_NET_WM_MOVERESIZE); + const xcb_atom_t moveResize = connection()->atom(QXcbAtom::Atom_NET_WM_MOVERESIZE); xcb_client_message_event_t xev; xev.response_type = XCB_CLIENT_MESSAGE; xev.type = moveResize; @@ -973,22 +1047,33 @@ bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab) bool ok = false; if (grab) { // grab - uint32_t mask = XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS - | XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE - | XCB_INPUT_XI_EVENT_MASK_MOTION - | XCB_INPUT_XI_EVENT_MASK_ENTER - | XCB_INPUT_XI_EVENT_MASK_LEAVE; + uint8_t mask[8] = {}; + setXcbMask(mask, XCB_INPUT_BUTTON_PRESS); + setXcbMask(mask, XCB_INPUT_BUTTON_RELEASE); + setXcbMask(mask, XCB_INPUT_MOTION); + setXcbMask(mask, XCB_INPUT_ENTER); + setXcbMask(mask, XCB_INPUT_LEAVE); if (isAtLeastXI22()) { - mask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN; - mask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE; - mask |= XCB_INPUT_XI_EVENT_MASK_TOUCH_END; + setXcbMask(mask, XCB_INPUT_TOUCH_BEGIN); + setXcbMask(mask, XCB_INPUT_TOUCH_UPDATE); + setXcbMask(mask, XCB_INPUT_TOUCH_END); } +#if QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES + if (isAtLeastXI24()) { + setXcbMask(mask, XCB_INPUT_GESTURE_PINCH_BEGIN); + setXcbMask(mask, XCB_INPUT_GESTURE_PINCH_UPDATE); + setXcbMask(mask, XCB_INPUT_GESTURE_PINCH_END); + setXcbMask(mask, XCB_INPUT_GESTURE_SWIPE_BEGIN); + setXcbMask(mask, XCB_INPUT_GESTURE_SWIPE_UPDATE); + setXcbMask(mask, XCB_INPUT_GESTURE_SWIPE_END); + } +#endif // QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES - for (int id : qAsConst(m_xiMasterPointerIds)) { + for (int id : std::as_const(m_xiMasterPointerIds)) { xcb_generic_error_t *error = nullptr; auto cookie = xcb_input_xi_grab_device(xcb_connection(), w, XCB_CURRENT_TIME, XCB_CURSOR_NONE, id, XCB_INPUT_GRAB_MODE_22_ASYNC, XCB_INPUT_GRAB_MODE_22_ASYNC, - false, 1, &mask); + false, 2, reinterpret_cast<uint32_t *>(mask)); auto *reply = xcb_input_xi_grab_device_reply(xcb_connection(), cookie, &error); if (error) { qCDebug(lcQpaXInput, "failed to grab events for device %d on window %x" @@ -1002,7 +1087,7 @@ bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab) free(reply); } } else { // ungrab - for (int id : qAsConst(m_xiMasterPointerIds)) { + for (int id : std::as_const(m_xiMasterPointerIds)) { auto cookie = xcb_input_xi_ungrab_device_checked(xcb_connection(), XCB_CURRENT_TIME, id); xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie); if (error) { @@ -1026,18 +1111,182 @@ bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab) void QXcbConnection::xi2HandleHierarchyEvent(void *event) { auto *xiEvent = reinterpret_cast<xcb_input_hierarchy_event_t *>(event); - // We only care about hotplugged devices - if (!(xiEvent->flags & (XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED | XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED))) + // We care about hotplugged devices (slaves) and master devices. + // We don't report anything for DEVICE_ENABLED or DEVICE_DISABLED + // (but often that goes with adding or removal anyway). + // We don't react to SLAVE_ATTACHED or SLAVE_DETACHED either. + if (xiEvent->flags & (XCB_INPUT_HIERARCHY_MASK_MASTER_ADDED | + XCB_INPUT_HIERARCHY_MASK_MASTER_REMOVED | + XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED | + XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED)) + xi2SetupDevices(); +} + +#if QT_XCB_HAS_TOUCHPAD_GESTURES +void QXcbConnection::xi2HandleGesturePinchEvent(void *event) +{ + auto *xiEvent = reinterpret_cast<qt_xcb_input_pinch_event_t *>(event); + + if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) { + qCDebug(lcQpaXInputEvents, "XI2 gesture event type %d seq %d fingers %d pos %6.1f, " + "%6.1f root pos %6.1f, %6.1f delta_angle %6.1f scale %6.1f on window %x", + xiEvent->event_type, xiEvent->sequence, xiEvent->detail, + fixed1616ToReal(xiEvent->event_x), fixed1616ToReal(xiEvent->event_y), + fixed1616ToReal(xiEvent->root_x), fixed1616ToReal(xiEvent->root_y), + fixed1616ToReal(xiEvent->delta_angle), fixed1616ToReal(xiEvent->scale), + xiEvent->event); + } + QXcbWindow *platformWindow = platformWindowFromId(xiEvent->event); + if (!platformWindow) return; - xi2SetupDevices(); + setTime(xiEvent->time); + + TouchDeviceData *dev = touchDeviceForId(xiEvent->sourceid); + Q_ASSERT(dev); + + uint32_t fingerCount = xiEvent->detail; + + switch (xiEvent->event_type) { + case XCB_INPUT_GESTURE_PINCH_BEGIN: + // Gestures must be accepted when we are grabbing gesture events. Otherwise the entire + // sequence will get replayed when the grab ends. + if (m_xiGrab) { + xcb_input_xi_allow_events(xcb_connection(), XCB_CURRENT_TIME, xiEvent->deviceid, + XCB_INPUT_EVENT_MODE_ASYNC_DEVICE, 0, xiEvent->event); + } + m_lastPinchScale = 1.0; + QWindowSystemInterface::handleGestureEvent(platformWindow->window(), xiEvent->time, + dev->qtTouchDevice, + Qt::BeginNativeGesture, + platformWindow->lastPointerPosition(), + platformWindow->lastPointerGlobalPosition(), + fingerCount); + break; + + case XCB_INPUT_GESTURE_PINCH_UPDATE: { + qreal rotationDelta = fixed1616ToReal(xiEvent->delta_angle); + qreal scale = fixed1616ToReal(xiEvent->scale); + qreal scaleDelta = scale - m_lastPinchScale; + m_lastPinchScale = scale; + + QPointF delta = QPointF(fixed1616ToReal(xiEvent->delta_x), + fixed1616ToReal(xiEvent->delta_y)); + + if (!delta.isNull()) { + QWindowSystemInterface::handleGestureEventWithValueAndDelta( + platformWindow->window(), xiEvent->time, dev->qtTouchDevice, + Qt::PanNativeGesture, 0, delta, + platformWindow->lastPointerPosition(), + platformWindow->lastPointerGlobalPosition(), + fingerCount); + } + if (rotationDelta != 0) { + QWindowSystemInterface::handleGestureEventWithRealValue( + platformWindow->window(), xiEvent->time, dev->qtTouchDevice, + Qt::RotateNativeGesture, + rotationDelta, + platformWindow->lastPointerPosition(), + platformWindow->lastPointerGlobalPosition(), + fingerCount); + } + if (scaleDelta != 0) { + QWindowSystemInterface::handleGestureEventWithRealValue( + platformWindow->window(), xiEvent->time, dev->qtTouchDevice, + Qt::ZoomNativeGesture, + scaleDelta, + platformWindow->lastPointerPosition(), + platformWindow->lastPointerGlobalPosition(), + fingerCount); + } + break; + } + case XCB_INPUT_GESTURE_PINCH_END: + QWindowSystemInterface::handleGestureEvent(platformWindow->window(), xiEvent->time, + dev->qtTouchDevice, + Qt::EndNativeGesture, + platformWindow->lastPointerPosition(), + platformWindow->lastPointerGlobalPosition(), + fingerCount); + break; + } } +void QXcbConnection::xi2HandleGestureSwipeEvent(void *event) +{ + auto *xiEvent = reinterpret_cast<qt_xcb_input_swipe_event_t *>(event); + + if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) { + qCDebug(lcQpaXInputEvents, "XI2 gesture event type %d seq %d detail %d pos %6.1f, %6.1f root pos %6.1f, %6.1f on window %x", + xiEvent->event_type, xiEvent->sequence, xiEvent->detail, + fixed1616ToReal(xiEvent->event_x), fixed1616ToReal(xiEvent->event_y), + fixed1616ToReal(xiEvent->root_x), fixed1616ToReal(xiEvent->root_y), + xiEvent->event); + } + QXcbWindow *platformWindow = platformWindowFromId(xiEvent->event); + if (!platformWindow) + return; + + setTime(xiEvent->time); + + TouchDeviceData *dev = touchDeviceForId(xiEvent->sourceid); + Q_ASSERT(dev); + + uint32_t fingerCount = xiEvent->detail; + + switch (xiEvent->event_type) { + case XCB_INPUT_GESTURE_SWIPE_BEGIN: + // Gestures must be accepted when we are grabbing gesture events. Otherwise the entire + // sequence will get replayed when the grab ends. + if (m_xiGrab) { + xcb_input_xi_allow_events(xcb_connection(), XCB_CURRENT_TIME, xiEvent->deviceid, + XCB_INPUT_EVENT_MODE_ASYNC_DEVICE, 0, xiEvent->event); + } + QWindowSystemInterface::handleGestureEvent(platformWindow->window(), xiEvent->time, + dev->qtTouchDevice, + Qt::BeginNativeGesture, + platformWindow->lastPointerPosition(), + platformWindow->lastPointerGlobalPosition(), + fingerCount); + break; + case XCB_INPUT_GESTURE_SWIPE_UPDATE: { + QPointF delta = QPointF(fixed1616ToReal(xiEvent->delta_x), + fixed1616ToReal(xiEvent->delta_y)); + + if (xiEvent->delta_x != 0 || xiEvent->delta_y != 0) { + QWindowSystemInterface::handleGestureEventWithValueAndDelta( + platformWindow->window(), xiEvent->time, dev->qtTouchDevice, + Qt::PanNativeGesture, 0, delta, + platformWindow->lastPointerPosition(), + platformWindow->lastPointerGlobalPosition(), + fingerCount); + } + break; + } + case XCB_INPUT_GESTURE_SWIPE_END: + QWindowSystemInterface::handleGestureEvent(platformWindow->window(), xiEvent->time, + dev->qtTouchDevice, + Qt::EndNativeGesture, + platformWindow->lastPointerPosition(), + platformWindow->lastPointerGlobalPosition(), + fingerCount); + break; + } +} + +#else // QT_XCB_HAS_TOUCHPAD_GESTURES +void QXcbConnection::xi2HandleGesturePinchEvent(void*) {} +void QXcbConnection::xi2HandleGestureSwipeEvent(void*) {} +#endif + void QXcbConnection::xi2HandleDeviceChangedEvent(void *event) { auto *xiEvent = reinterpret_cast<xcb_input_device_changed_event_t *>(event); switch (xiEvent->reason) { case XCB_INPUT_CHANGE_REASON_DEVICE_CHANGE: { + // Don't call xi2SetupSlavePointerDevice() again for an already-known device, and never for a master. + if (m_xiMasterPointerIds.contains(xiEvent->deviceid) || m_xiSlavePointerIds.contains(xiEvent->deviceid)) + return; auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), xiEvent->sourceid); if (!reply || reply->num_infos <= 0) return; @@ -1079,9 +1328,9 @@ void QXcbConnection::xi2UpdateScrollingDevice(QInputDevice *dev) if (classInfo->type == XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR) { auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classInfo); const int valuatorAtom = qatom(vci->label); - if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel) + if (valuatorAtom == QXcbAtom::AtomRelHorizScroll || valuatorAtom == QXcbAtom::AtomRelHorizWheel) scrollingDevice->lastScrollPosition.setX(fixed3232ToReal(vci->value)); - else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel) + else if (valuatorAtom == QXcbAtom::AtomRelVertScroll || valuatorAtom == QXcbAtom::AtomRelVertWheel) scrollingDevice->lastScrollPosition.setY(fixed3232ToReal(vci->value)); } } @@ -1268,7 +1517,7 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD // The evdev driver doesn't do it this way. const auto *ev = reinterpret_cast<const xcb_input_property_event_t *>(event); if (ev->what == XCB_INPUT_PROPERTY_FLAG_MODIFIED) { - if (ev->property == atom(QXcbAtom::WacomSerialIDs)) { + if (ev->property == atom(QXcbAtom::AtomWacomSerialIDs)) { enum WacomSerialIndex { _WACSER_USB_ID = 0, _WACSER_LAST_TOOL_SERIAL, @@ -1281,7 +1530,7 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD auto reply = Q_XCB_REPLY(xcb_input_xi_get_property, xcb_connection(), tabletData->deviceId, 0, ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, 100); if (reply) { - if (reply->type == atom(QXcbAtom::INTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) { + if (reply->type == atom(QXcbAtom::AtomINTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) { quint32 *ptr = reinterpret_cast<quint32 *>(xcb_input_xi_get_property_items(reply.get())); quint32 tool = ptr[_WACSER_TOOL_ID]; // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/ @@ -1289,6 +1538,7 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD if (!tool && ptr[_WACSER_TOOL_SERIAL]) tool = ptr[_WACSER_TOOL_SERIAL]; + QWindow *win = nullptr; // TODO QTBUG-111400 get the position somehow, then the window // The property change event informs us which tool is in proximity or which one left proximity. if (tool) { const QPointingDevice *dev = tabletToolInstance(nullptr, tabletData->name, @@ -1297,30 +1547,26 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD tabletData->inProximity = true; tabletData->tool = dev->type(); tabletData->serialId = qint64(ptr[_WACSER_TOOL_SERIAL]); - QWindowSystemInterface::handleTabletEnterProximityEvent(ev->time, - int(tabletData->tool), int(tabletData->pointerType), tabletData->serialId); + QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(win, ev->time, dev, true); // enter } else { tool = ptr[_WACSER_LAST_TOOL_ID]; // Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/ // e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1 if (!tool) tool = ptr[_WACSER_LAST_TOOL_SERIAL]; - const QInputDevice *dev = QInputDevicePrivate::fromId(tabletData->deviceId); + auto *dev = qobject_cast<const QPointingDevice *>(QInputDevicePrivate::fromId(tabletData->deviceId)); Q_ASSERT(dev); tabletData->tool = dev->type(); tabletData->inProximity = false; tabletData->serialId = qint64(ptr[_WACSER_LAST_TOOL_SERIAL]); - // TODO why doesn't it just take QPointingDevice* - QWindowSystemInterface::handleTabletLeaveProximityEvent(ev->time, - int(tabletData->tool), int(tabletData->pointerType), tabletData->serialId); + QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(win, ev->time, dev, false); // leave } // TODO maybe have a hash of tabletData->deviceId to device data so we can // look up the tablet name here, and distinguish multiple tablets - if (Q_UNLIKELY(lcQpaXInputEvents().isDebugEnabled())) - qCDebug(lcQpaXInputDevices, "XI2 proximity change on tablet %d %s (USB %x): last tool: %x id %x current tool: %x id %x %s", - tabletData->deviceId, qPrintable(tabletData->name), ptr[_WACSER_USB_ID], - ptr[_WACSER_LAST_TOOL_SERIAL], ptr[_WACSER_LAST_TOOL_ID], - ptr[_WACSER_TOOL_SERIAL], ptr[_WACSER_TOOL_ID], toolName(tabletData->tool)); + qCDebug(lcQpaXInputDevices, "XI2 proximity change on tablet %d %s (USB %x): last tool: %x id %x current tool: %x id %x %s", + tabletData->deviceId, qPrintable(tabletData->name), ptr[_WACSER_USB_ID], + ptr[_WACSER_LAST_TOOL_SERIAL], ptr[_WACSER_LAST_TOOL_ID], + ptr[_WACSER_TOOL_SERIAL], ptr[_WACSER_TOOL_ID], toolName(tabletData->tool)); } } } @@ -1354,6 +1600,9 @@ void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletD double pressure = 0, rotation = 0, tangentialPressure = 0; int xTilt = 0, yTilt = 0; static const bool useValuators = !qEnvironmentVariableIsSet("QT_XCB_TABLET_LEGACY_COORDINATES"); + const QPointingDevice *dev = QPointingDevicePrivate::tabletDevice(QInputDevice::DeviceType(tabletData->tool), + QPointingDevice::PointerType(tabletData->pointerType), + QPointingDeviceUniqueId::fromNumericId(tabletData->serialId)); // Valuators' values are relative to the physical size of the current virtual // screen. Therefore we cannot use QScreen/QWindow geometry and should use @@ -1372,36 +1621,37 @@ void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletD xi2GetValuatorValueIfSet(event, classInfo.number, &classInfo.curVal); double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal); switch (valuator) { - case QXcbAtom::AbsX: + case QXcbAtom::AtomAbsX: if (Q_LIKELY(useValuators)) { const qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.x(), physicalScreenArea.width()); global.setX(value); local.setX(xcbWindow->mapFromGlobalF(global).x()); } break; - case QXcbAtom::AbsY: + case QXcbAtom::AtomAbsY: if (Q_LIKELY(useValuators)) { qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.y(), physicalScreenArea.height()); global.setY(value); local.setY(xcbWindow->mapFromGlobalF(global).y()); } break; - case QXcbAtom::AbsPressure: + case QXcbAtom::AtomAbsPressure: pressure = normalizedValue; break; - case QXcbAtom::AbsTiltX: + case QXcbAtom::AtomAbsTiltX: xTilt = classInfo.curVal; break; - case QXcbAtom::AbsTiltY: + case QXcbAtom::AtomAbsTiltY: yTilt = classInfo.curVal; break; - case QXcbAtom::AbsWheel: + case QXcbAtom::AtomAbsWheel: switch (tabletData->tool) { case QInputDevice::DeviceType::Airbrush: tangentialPressure = normalizedValue * 2.0 - 1.0; // Convert 0..1 range to -1..+1 range break; case QInputDevice::DeviceType::Stylus: - rotation = normalizedValue * 360.0 - 180.0; // Convert 0..1 range to -180..+180 degrees + if (dev->capabilities().testFlag(QInputDevice::Capability::Rotation)) + rotation = normalizedValue * 360.0 - 180.0; // Convert 0..1 range to -180..+180 degrees break; default: // Other types of styli do not use this valuator break; @@ -1420,16 +1670,15 @@ void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletD local.x(), local.y(), global.x(), global.y(), (int)tabletData->buttons, pressure, xTilt, yTilt, rotation, (int)modifiers); - QWindowSystemInterface::handleTabletEvent(window, ev->time, local, global, - int(tabletData->tool), int(tabletData->pointerType), + QWindowSystemInterface::handleTabletEvent(window, ev->time, dev, local, global, tabletData->buttons, pressure, xTilt, yTilt, tangentialPressure, - rotation, 0, tabletData->serialId, modifiers); + rotation, 0, modifiers); } QXcbConnection::TabletData *QXcbConnection::tabletDataForDevice(int id) { - for (int i = 0; i < m_tabletData.count(); ++i) { + for (int i = 0; i < m_tabletData.size(); ++i) { if (m_tabletData.at(i).deviceId == id) return &m_tabletData[i]; } |