diff options
Diffstat (limited to 'src/plugins/platforms/xcb/qxcbconnection_xi2.cpp')
-rw-r--r-- | src/plugins/platforms/xcb/qxcbconnection_xi2.cpp | 1012 |
1 files changed, 552 insertions, 460 deletions
diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp index 26a9ba8d26..f90f189146 100644 --- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp @@ -50,19 +50,6 @@ #include <X11/extensions/XInput2.h> #include <X11/extensions/XI2proto.h> -struct XInput2TouchDeviceData { - XIDeviceInfo *xiDeviceInfo = nullptr; - QTouchDevice *qtTouchDevice = nullptr; - QHash<int, QWindowSystemInterface::TouchPoint> touchPoints; - QHash<int, QPointF> pointPressedPosition; // in screen coordinates where each point was pressed - - // Stuff that is relevant only for touchpads - QPointF firstPressedPosition; // in screen coordinates where the first point was pressed - QPointF firstPressedNormalPosition; // device coordinates (0 to 1, 0 to 1) where the first point was pressed - QSizeF size; // device size in mm - bool providesTouchOrientation = false; -}; - void QXcbConnection::initializeXInput2() { // TODO Qt 6 (or perhaps earlier): remove these redundant env variables @@ -70,30 +57,37 @@ void QXcbConnection::initializeXInput2() const_cast<QLoggingCategory&>(lcQpaXInput()).setEnabled(QtDebugMsg, true); if (qEnvironmentVariableIsSet("QT_XCB_DEBUG_XINPUT_DEVICES")) const_cast<QLoggingCategory&>(lcQpaXInputDevices()).setEnabled(QtDebugMsg, true); + Display *xDisplay = static_cast<Display *>(m_xlib_display); if (XQueryExtension(xDisplay, "XInputExtension", &m_xiOpCode, &m_xiEventBase, &m_xiErrorBase)) { int xiMajor = 2; - // try 2.2 first, needed for TouchBegin/Update/End - if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) { - m_xi2Minor = 1; // for smooth scrolling 2.1 is enough - if (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) == BadRequest) { - m_xi2Minor = 0; // for tablet support 2.0 is enough - m_xi2Enabled = XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor) != BadRequest; - } else - m_xi2Enabled = true; - } else - m_xi2Enabled = true; - if (m_xi2Enabled) { -#ifdef XCB_USE_XINPUT22 - qCDebug(lcQpaXInputDevices, "XInput version %d.%d is available and Qt supports 2.2 or greater", xiMajor, m_xi2Minor); - m_startSystemResizeInfo.window = XCB_NONE; +#if defined(XCB_USE_XINPUT22) + m_xi2Minor = 2; // for touch support 2.2 is enough +#elif defined(XCB_USE_XINPUT21) + m_xi2Minor = 1; // for smooth scrolling 2.1 is enough #else - qCDebug(lcQpaXInputDevices, "XInput version %d.%d is available and Qt supports 2.0", xiMajor, m_xi2Minor); + m_xi2Minor = 0; // for tablet support 2.0 is enough #endif + qCDebug(lcQpaXInput, "Plugin build with support for XInput 2 version up " + "to %d.%d", xiMajor, m_xi2Minor); + + switch (XIQueryVersion(xDisplay, &xiMajor, &m_xi2Minor)) { + case Success: + // Server's supported version can be lower than the version we have + // announced to support. In this case Qt client will be limited by + // X server's supported version. + qCDebug(lcQpaXInput, "Using XInput version %d.%d", xiMajor, m_xi2Minor); + m_xi2Enabled = true; + xi2SetupDevices(); xi2SelectStateEvents(); + break; + case BadRequest: // Must be an X server with XInput 1 + qCDebug(lcQpaXInput, "X server does not support XInput 2"); + break; + default: // BadValue + qCDebug(lcQpaXInput, "Internal error"); + break; } - - xi2SetupDevices(); } } @@ -113,377 +107,434 @@ void QXcbConnection::xi2SelectStateEvents() XISelectEvents(dpy, DefaultRootWindow(dpy), &xiEventMask, 1); } -void QXcbConnection::xi2SetupDevices() +void QXcbConnection::xi2SelectDeviceEvents(xcb_window_t window) { -#if QT_CONFIG(tabletevent) - m_tabletData.clear(); + if (window == rootWindow()) + return; + + unsigned int bitMask = 0; + unsigned char *xiBitMask = reinterpret_cast<unsigned char *>(&bitMask); + bitMask |= XI_ButtonPressMask; + bitMask |= XI_ButtonReleaseMask; + bitMask |= XI_MotionMask; + // 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 |= XI_EnterMask; + bitMask |= XI_LeaveMask; + bitMask |= XI_PropertyEventMask; +#ifdef XCB_USE_XINPUT22 + if (isAtLeastXI22()) { + bitMask |= XI_TouchBeginMask; + bitMask |= XI_TouchUpdateMask; + bitMask |= XI_TouchEndMask; + } #endif - m_scrollingDevices.clear(); - if (!m_xi2Enabled) - return; + XIEventMask mask; + mask.mask_len = sizeof(bitMask); + mask.mask = xiBitMask; + mask.deviceid = XIAllMasterDevices; + Display *dpy = static_cast<Display *>(m_xlib_display); + Status result = XISelectEvents(dpy, window, &mask, 1); + if (result == Success) + QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); + else + qCDebug(lcQpaXInput, "failed to select events, window %x, result %d", window, result); +} - Display *xDisplay = static_cast<Display *>(m_xlib_display); - int deviceCount = 0; - XIDeviceInfo *devices = XIQueryDevice(xDisplay, XIAllDevices, &deviceCount); - for (int i = 0; i < deviceCount; ++i) { - // Only non-master pointing devices are relevant here. - if (devices[i].use != XISlavePointer) - continue; - qCDebug(lcQpaXInputDevices) << "input device " << devices[i].name << "ID" << devices[i].deviceid; +void QXcbConnection::xi2SetupDevice(void *info, bool removeExisting) +{ + XIDeviceInfo *deviceInfo = reinterpret_cast<XIDeviceInfo *>(info); + if (removeExisting) { #if QT_CONFIG(tabletevent) - TabletData tabletData; + for (int i = 0; i < m_tabletData.count(); ++i) { + if (m_tabletData.at(i).deviceId == deviceInfo->deviceid) { + m_tabletData.remove(i); + break; + } + } #endif - ScrollingDevice scrollingDevice; - for (int c = 0; c < devices[i].num_classes; ++c) { - switch (devices[i].classes[c]->type) { - case XIValuatorClass: { - XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(devices[i].classes[c]); - const int valuatorAtom = qatom(vci->label); - qCDebug(lcQpaXInputDevices) << " has valuator" << atomName(vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms); + m_scrollingDevices.remove(deviceInfo->deviceid); + m_touchDevices.remove(deviceInfo->deviceid); + } + + qCDebug(lcQpaXInputDevices) << "input device " << deviceInfo->name << "ID" << deviceInfo->deviceid; #if QT_CONFIG(tabletevent) - if (valuatorAtom < QXcbAtom::NAtoms) { - TabletData::ValuatorClassInfo info; - info.minVal = vci->min; - info.maxVal = vci->max; - info.number = vci->number; - tabletData.valuatorInfo[valuatorAtom] = info; - } -#endif // QT_CONFIG(tabletevent) - if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel) - scrollingDevice.lastScrollPosition.setX(vci->value); - else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel) - scrollingDevice.lastScrollPosition.setY(vci->value); - break; + TabletData tabletData; +#endif + ScrollingDevice scrollingDevice; + for (int c = 0; c < deviceInfo->num_classes; ++c) { + XIAnyClassInfo *classinfo = deviceInfo->classes[c]; + switch (classinfo->type) { + case XIValuatorClass: { + XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo); + const int valuatorAtom = qatom(vci->label); + qCDebug(lcQpaXInputDevices) << " has valuator" << atomName(vci->label) << "recognized?" << (valuatorAtom < QXcbAtom::NAtoms); +#if QT_CONFIG(tabletevent) + if (valuatorAtom < QXcbAtom::NAtoms) { + TabletData::ValuatorClassInfo info; + info.minVal = vci->min; + info.maxVal = vci->max; + info.number = vci->number; + tabletData.valuatorInfo[valuatorAtom] = info; } +#endif // QT_CONFIG(tabletevent) + if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel) + scrollingDevice.lastScrollPosition.setX(vci->value); + else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel) + scrollingDevice.lastScrollPosition.setY(vci->value); + break; + } #ifdef XCB_USE_XINPUT21 - case XIScrollClass: { - XIScrollClassInfo *sci = reinterpret_cast<XIScrollClassInfo *>(devices[i].classes[c]); - if (sci->scroll_type == XIScrollTypeVertical) { - scrollingDevice.orientations |= Qt::Vertical; - scrollingDevice.verticalIndex = sci->number; - scrollingDevice.verticalIncrement = sci->increment; - } - else if (sci->scroll_type == XIScrollTypeHorizontal) { - scrollingDevice.orientations |= Qt::Horizontal; - scrollingDevice.horizontalIndex = sci->number; - scrollingDevice.horizontalIncrement = sci->increment; - } - break; + case XIScrollClass: { + XIScrollClassInfo *sci = reinterpret_cast<XIScrollClassInfo *>(classinfo); + if (sci->scroll_type == XIScrollTypeVertical) { + scrollingDevice.orientations |= Qt::Vertical; + scrollingDevice.verticalIndex = sci->number; + scrollingDevice.verticalIncrement = sci->increment; } - case XIButtonClass: { - XIButtonClassInfo *bci = reinterpret_cast<XIButtonClassInfo *>(devices[i].classes[c]); - if (bci->num_buttons >= 5) { - Atom label4 = bci->labels[3]; - Atom label5 = bci->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)) - scrollingDevice.legacyOrientations |= Qt::Vertical; - } - if (bci->num_buttons >= 7) { - Atom label6 = bci->labels[5]; - Atom label7 = bci->labels[6]; - if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight)) - scrollingDevice.legacyOrientations |= Qt::Horizontal; - } - qCDebug(lcQpaXInputDevices, " has %d buttons", bci->num_buttons); - break; + else if (sci->scroll_type == XIScrollTypeHorizontal) { + scrollingDevice.orientations |= Qt::Horizontal; + scrollingDevice.horizontalIndex = sci->number; + scrollingDevice.horizontalIncrement = sci->increment; + } + break; + } + case XIButtonClass: { + XIButtonClassInfo *bci = reinterpret_cast<XIButtonClassInfo *>(classinfo); + if (bci->num_buttons >= 5) { + Atom label4 = bci->labels[3]; + Atom label5 = bci->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)) + scrollingDevice.legacyOrientations |= Qt::Vertical; } + if (bci->num_buttons >= 7) { + Atom label6 = bci->labels[5]; + Atom label7 = bci->labels[6]; + if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight)) + scrollingDevice.legacyOrientations |= Qt::Horizontal; + } + qCDebug(lcQpaXInputDevices, " has %d buttons", bci->num_buttons); + break; + } #endif - case XIKeyClass: - qCDebug(lcQpaXInputDevices) << " it's a keyboard"; - break; + case XIKeyClass: + qCDebug(lcQpaXInputDevices) << " it's a keyboard"; + break; #ifdef XCB_USE_XINPUT22 - case XITouchClass: - // will be handled in deviceForId() - break; + case XITouchClass: + // will be handled in populateTouchDevices() + break; #endif - default: - qCDebug(lcQpaXInputDevices) << " has class" << devices[i].classes[c]->type; - break; - } + default: + qCDebug(lcQpaXInputDevices) << " has class" << classinfo->type; + break; } - bool isTablet = false; + } + 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)) - isTablet = true; - - // But we need to be careful not to take the touch and tablet-button devices as tablets. - QByteArray name = QByteArray(devices[i].name).toLower(); - QString dbgType = QLatin1String("UNKNOWN"); - if (name.contains("eraser")) { - isTablet = true; - tabletData.pointerType = QTabletEvent::Eraser; - dbgType = QLatin1String("eraser"); - } else if (name.contains("cursor") && !(name.contains("cursor controls") && name.contains("trackball"))) { - isTablet = true; - tabletData.pointerType = QTabletEvent::Cursor; - dbgType = QLatin1String("cursor"); - } else if (name.contains("wacom") && name.contains("finger touch")) { - isTablet = false; - } else if ((name.contains("pen") || name.contains("stylus")) && isTablet) { - tabletData.pointerType = QTabletEvent::Pen; - dbgType = QLatin1String("pen"); - } else if (name.contains("wacom") && isTablet && !name.contains("touch")) { - // combined device (evdev) rather than separate pen/eraser (wacom driver) - tabletData.pointerType = QTabletEvent::Pen; - dbgType = QLatin1String("pen"); - } else if (name.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) { - // some "Genius" tablets - isTablet = true; - tabletData.pointerType = QTabletEvent::Pen; - dbgType = QLatin1String("pen"); - } else if (name.contains("waltop") && name.contains("tablet")) { - // other "Genius" tablets - // WALTOP International Corp. Slim Tablet - isTablet = true; - tabletData.pointerType = QTabletEvent::Pen; - dbgType = QLatin1String("pen"); - } else if (name.contains("uc-logic") && isTablet) { - tabletData.pointerType = QTabletEvent::Pen; - dbgType = QLatin1String("pen"); - } else { - isTablet = false; - } + // 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)) + isTablet = true; + + // But we need to be careful not to take the touch and tablet-button devices as tablets. + QByteArray name = QByteArray(deviceInfo->name).toLower(); + QString dbgType = QLatin1String("UNKNOWN"); + if (name.contains("eraser")) { + isTablet = true; + tabletData.pointerType = QTabletEvent::Eraser; + dbgType = QLatin1String("eraser"); + } else if (name.contains("cursor") && !(name.contains("cursor controls") && name.contains("trackball"))) { + isTablet = true; + tabletData.pointerType = QTabletEvent::Cursor; + dbgType = QLatin1String("cursor"); + } else if (name.contains("wacom") && name.contains("finger touch")) { + isTablet = false; + } else if ((name.contains("pen") || name.contains("stylus")) && isTablet) { + tabletData.pointerType = QTabletEvent::Pen; + dbgType = QLatin1String("pen"); + } else if (name.contains("wacom") && isTablet && !name.contains("touch")) { + // combined device (evdev) rather than separate pen/eraser (wacom driver) + tabletData.pointerType = QTabletEvent::Pen; + dbgType = QLatin1String("pen"); + } else if (name.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) { + // some "Genius" tablets + isTablet = true; + tabletData.pointerType = QTabletEvent::Pen; + dbgType = QLatin1String("pen"); + } else if (name.contains("waltop") && name.contains("tablet")) { + // other "Genius" tablets + // WALTOP International Corp. Slim Tablet + isTablet = true; + tabletData.pointerType = QTabletEvent::Pen; + dbgType = QLatin1String("pen"); + } else if (name.contains("uc-logic") && isTablet) { + tabletData.pointerType = QTabletEvent::Pen; + dbgType = QLatin1String("pen"); + } else { + isTablet = false; + } - if (isTablet) { - tabletData.deviceId = devices[i].deviceid; - m_tabletData.append(tabletData); - qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType; - } + if (isTablet) { + tabletData.deviceId = deviceInfo->deviceid; + m_tabletData.append(tabletData); + qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType; + } #endif // QT_CONFIG(tabletevent) #ifdef XCB_USE_XINPUT21 - if (scrollingDevice.orientations || scrollingDevice.legacyOrientations) { - scrollingDevice.deviceId = devices[i].deviceid; - // Only use legacy wheel button events when we don't have real scroll valuators. - scrollingDevice.legacyOrientations &= ~scrollingDevice.orientations; - m_scrollingDevices.insert(scrollingDevice.deviceId, scrollingDevice); - qCDebug(lcQpaXInputDevices) << " it's a scrolling device"; - } + if (scrollingDevice.orientations || scrollingDevice.legacyOrientations) { + scrollingDevice.deviceId = deviceInfo->deviceid; + // Only use legacy wheel button events when we don't have real scroll valuators. + scrollingDevice.legacyOrientations &= ~scrollingDevice.orientations; + m_scrollingDevices.insert(scrollingDevice.deviceId, scrollingDevice); + qCDebug(lcQpaXInputDevices) << " it's a scrolling device"; + } #endif - if (!isTablet) { - // touchDeviceForId populates XInput2DeviceData the first time it is called - // with a new deviceId. On subsequent calls it will return the cached object. - XInput2TouchDeviceData *dev = touchDeviceForId(devices[i].deviceid); - if (dev && lcQpaXInputDevices().isDebugEnabled()) { - if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen) - qCDebug(lcQpaXInputDevices, " it's a touchscreen with type %d capabilities 0x%X max touch points %d", - dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(), - dev->qtTouchDevice->maximumTouchPoints()); - else if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad) - qCDebug(lcQpaXInputDevices, " it's a touchpad with type %d capabilities 0x%X max touch points %d size %f x %f", - dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(), - dev->qtTouchDevice->maximumTouchPoints(), - dev->size.width(), dev->size.height()); - } + if (!isTablet) { + TouchDeviceData *dev = populateTouchDevices(deviceInfo); + if (dev && lcQpaXInputDevices().isDebugEnabled()) { + if (dev->qtTouchDevice->type() == QTouchDevice::TouchScreen) + qCDebug(lcQpaXInputDevices, " it's a touchscreen with type %d capabilities 0x%X max touch points %d", + dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(), + dev->qtTouchDevice->maximumTouchPoints()); + else if (dev->qtTouchDevice->type() == QTouchDevice::TouchPad) + qCDebug(lcQpaXInputDevices, " it's a touchpad with type %d capabilities 0x%X max touch points %d size %f x %f", + dev->qtTouchDevice->type(), (unsigned int)dev->qtTouchDevice->capabilities(), + dev->qtTouchDevice->maximumTouchPoints(), + dev->size.width(), dev->size.height()); } } - XIFreeDeviceInfo(devices); + } -void QXcbConnection::finalizeXInput2() +void QXcbConnection::xi2SetupDevices() { - for (XInput2TouchDeviceData *dev : qAsConst(m_touchDevices)) { - if (dev->xiDeviceInfo) - XIFreeDeviceInfo(dev->xiDeviceInfo); - delete dev; +#if QT_CONFIG(tabletevent) + m_tabletData.clear(); +#endif + m_scrollingDevices.clear(); + m_touchDevices.clear(); + + Display *xDisplay = static_cast<Display *>(m_xlib_display); + int deviceCount = 0; + XIDeviceInfo *devices = XIQueryDevice(xDisplay, XIAllDevices, &deviceCount); + for (int i = 0; i < deviceCount; ++i) { + // Only non-master pointing devices are relevant here. + if (devices[i].use != XISlavePointer) + continue; + xi2SetupDevice(&devices[i], false); } + XIFreeDeviceInfo(devices); } -void QXcbConnection::xi2Select(xcb_window_t window) +/*! \internal + + Notes on QT_XCB_NO_XI2_MOUSE Handling: + + Here we don't select pointer button press/release and motion events on master devices, instead + we select these events directly on slave devices. This means that a master device will fallback + to sending core events for every XI_* event that is sent directly by a slave device. For more + details see "Event processing for attached slave devices" in XInput2 specification. To prevent + handling of the same event twice, we have checks for xi2MouseEventsDisabled() in XI2 event + handlers (but this is somewhat inconsistent in some situations). If the purpose for + QT_XCB_NO_XI2_MOUSE was so that an application using QAbstractNativeEventFilter would see core + mouse events before they are handled by Qt then QT_XCB_NO_XI2_MOUSE won't always work as + expected (e.g. we handle scroll event directly from a slave device event, before an application + has seen the fallback core event from a master device). + + The commit introducing QT_XCB_NO_XI2_MOUSE also states that setting this envvar "restores the + old behavior with broken grabbing". It did not elaborate why grabbing was not fixed for this + code path. The issue that this envvar tries to solve seem to be less important than broken + grabbing (broken apparently only for touch events). Thus, if you really want core mouse events + in your application and do not care about broken touch, then use QT_XCB_NO_XI2 (more on this + below) to disable the extension all together. The reason why grabbing might have not been fixed + is that calling XIGrabDevice with this code path for some reason always returns AlreadyGrabbed + (by debugging X server's code it appears that when we call XIGrabDevice, an X server first grabs + pointer via core pointer and then fails to do XI2 grab with AlreadyGrabbed; disclaimer - I did + not debug this in great detail). When we try supporting odd setups like QT_XCB_NO_XI2_MOUSE, we + are asking for trouble anyways. + + In conclusion, introduction of QT_XCB_NO_XI2_MOUSE causes more issues than solves - the above + mentioned inconsistencies, maintenance of this code path and that QT_XCB_NO_XI2_MOUSE replaces + less important issue with somewhat more important issue. It also makes us to use less optimal + code paths in certain situations (see xi2HandleHierarchyEvent). Using of QT_XCB_NO_XI2 has its + drawbacks too - no tablet and touch events. So the only real fix in this case is at an + application side (teach the application about xcb_ge_event_t events). Based on this, + QT_XCB_NO_XI2_MOUSE will be removed in ### Qt 6. It should not have existed in the first place, + native events seen by QAbstractNativeEventFilter is not really a public API, applications should + expect changes at this level and do ifdefs if something changes between Qt version. +*/ +void QXcbConnection::xi2SelectDeviceEventsCompatibility(xcb_window_t window) { - if (!m_xi2Enabled || window == rootWindow()) + if (window == rootWindow()) return; - Display *xDisplay = static_cast<Display *>(m_xlib_display); - unsigned int bitMask = 0; - unsigned char *xiBitMask = reinterpret_cast<unsigned char *>(&bitMask); - + unsigned int mask = 0; + unsigned char *bitMask = reinterpret_cast<unsigned char *>(&mask); + mask |= XI_PropertyEventMask; #ifdef XCB_USE_XINPUT22 if (isAtLeastXI22()) { - bitMask |= XI_TouchBeginMask; - bitMask |= XI_TouchUpdateMask; - bitMask |= XI_TouchEndMask; - bitMask |= XI_PropertyEventMask; // for tablets - if (xi2MouseEvents()) { - // We want both mouse and touch through XI2 if touch is supported (>= 2.2). - // The plain xcb press and motion events will not be delivered after this. - bitMask |= XI_ButtonPressMask; - bitMask |= XI_ButtonReleaseMask; - bitMask |= XI_MotionMask; - - // There is a check for enter/leave events in plain xcb enter/leave event handler - bitMask |= XI_EnterMask; - bitMask |= XI_LeaveMask; - - qCDebug(lcQpaXInput, "XInput 2.2: Selecting press/release/motion events in addition to touch"); - } - XIEventMask mask; - mask.mask_len = sizeof(bitMask); - mask.mask = xiBitMask; - // When xi2MouseEvents() is true (the default), pointer emulation for touch and tablet - // events will get disabled. This is preferable, as Qt Quick handles touch events - // directly, while for other applications QtGui synthesizes mouse events. - mask.deviceid = XIAllMasterDevices; - Status result = XISelectEvents(xDisplay, window, &mask, 1); - if (result == Success) - QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); - else - qCDebug(lcQpaXInput, "XInput 2.2: failed to select pointer/touch events, window %x, result %d", window, result); + mask |= XI_TouchBeginMask; + mask |= XI_TouchUpdateMask; + mask |= XI_TouchEndMask; } +#endif + XIEventMask xiMask; + xiMask.mask_len = sizeof(mask); + xiMask.mask = bitMask; + xiMask.deviceid = XIAllMasterDevices; + Display *dpy = static_cast<Display *>(m_xlib_display); + Status result = XISelectEvents(dpy, window, &xiMask, 1); + if (result == Success) + QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); + else + qCDebug(lcQpaXInput, "failed to select events, window %x, result %d", window, result); - const bool pointerSelected = isAtLeastXI22() && xi2MouseEvents(); -#else - const bool pointerSelected = false; -#endif // XCB_USE_XINPUT22 + mask = XI_ButtonPressMask; + mask |= XI_ButtonReleaseMask; + mask |= XI_MotionMask; - QSet<int> tabletDevices; #if QT_CONFIG(tabletevent) + QSet<int> tabletDevices; if (!m_tabletData.isEmpty()) { - unsigned int tabletBitMask; - unsigned char *xiTabletBitMask = reinterpret_cast<unsigned char *>(&tabletBitMask); - QVector<XIEventMask> xiEventMask(m_tabletData.count()); - tabletBitMask = XI_PropertyEventMask; - if (!pointerSelected) - tabletBitMask |= XI_ButtonPressMask | XI_ButtonReleaseMask | XI_MotionMask; - for (int i = 0; i < m_tabletData.count(); ++i) { + const int nrTablets = m_tabletData.count(); + QVector<XIEventMask> xiEventMask(nrTablets); + for (int i = 0; i < nrTablets; ++i) { int deviceId = m_tabletData.at(i).deviceId; tabletDevices.insert(deviceId); xiEventMask[i].deviceid = deviceId; - xiEventMask[i].mask_len = sizeof(tabletBitMask); - xiEventMask[i].mask = xiTabletBitMask; + xiEventMask[i].mask_len = sizeof(mask); + xiEventMask[i].mask = bitMask; } - XISelectEvents(xDisplay, window, xiEventMask.data(), m_tabletData.count()); + XISelectEvents(dpy, window, xiEventMask.data(), nrTablets); } -#endif // QT_CONFIG(tabletevent) +#endif #ifdef XCB_USE_XINPUT21 - // Enable each scroll device - if (!m_scrollingDevices.isEmpty() && !pointerSelected) { - // Only when XI2 mouse events are not enabled, otherwise motion and release are selected already. + if (!m_scrollingDevices.isEmpty()) { QVector<XIEventMask> xiEventMask(m_scrollingDevices.size()); - unsigned int scrollBitMask; - unsigned char *xiScrollBitMask = reinterpret_cast<unsigned char *>(&scrollBitMask); - - scrollBitMask = XI_MotionMask; - scrollBitMask |= XI_ButtonReleaseMask; - int i=0; + int i = 0; for (const ScrollingDevice& scrollingDevice : qAsConst(m_scrollingDevices)) { +#if QT_CONFIG(tabletevent) if (tabletDevices.contains(scrollingDevice.deviceId)) continue; // All necessary events are already captured. +#endif xiEventMask[i].deviceid = scrollingDevice.deviceId; - xiEventMask[i].mask_len = sizeof(scrollBitMask); - xiEventMask[i].mask = xiScrollBitMask; + xiEventMask[i].mask_len = sizeof(mask); + xiEventMask[i].mask = bitMask; i++; } - XISelectEvents(xDisplay, window, xiEventMask.data(), i); + XISelectEvents(dpy, window, xiEventMask.data(), i); } -#else - Q_UNUSED(xiBitMask); #endif } -XInput2TouchDeviceData *QXcbConnection::touchDeviceForId(int id) +QXcbConnection::TouchDeviceData *QXcbConnection::touchDeviceForId(int id) { - XInput2TouchDeviceData *dev = Q_NULLPTR; - QHash<int, XInput2TouchDeviceData*>::const_iterator devIt = m_touchDevices.constFind(id); - if (devIt != m_touchDevices.cend()) { - dev = devIt.value(); - } else { - int nrDevices = 0; - QTouchDevice::Capabilities caps = 0; - dev = new XInput2TouchDeviceData; - dev->xiDeviceInfo = XIQueryDevice(static_cast<Display *>(m_xlib_display), id, &nrDevices); - if (nrDevices <= 0) { - delete dev; - return 0; - } - int type = -1; - int maxTouchPoints = 1; - bool hasRelativeCoords = false; - for (int i = 0; i < dev->xiDeviceInfo->num_classes; ++i) { - XIAnyClassInfo *classinfo = dev->xiDeviceInfo->classes[i]; - switch (classinfo->type) { + TouchDeviceData *dev = nullptr; + if (m_touchDevices.contains(id)) + dev = &m_touchDevices[id]; + return dev; +} + +QXcbConnection::TouchDeviceData *QXcbConnection::populateTouchDevices(void *info) +{ + XIDeviceInfo *deviceinfo = reinterpret_cast<XIDeviceInfo *>(info); + QTouchDevice::Capabilities caps = 0; + int type = -1; + int maxTouchPoints = 1; + bool isTouchDevice = false; + bool hasRelativeCoords = false; + TouchDeviceData dev; + for (int i = 0; i < deviceinfo->num_classes; ++i) { + XIAnyClassInfo *classinfo = deviceinfo->classes[i]; + switch (classinfo->type) { #ifdef XCB_USE_XINPUT22 - case XITouchClass: { - XITouchClassInfo *tci = reinterpret_cast<XITouchClassInfo *>(classinfo); - maxTouchPoints = tci->num_touches; - qCDebug(lcQpaXInputDevices, " has touch class with mode %d", tci->mode); - switch (tci->mode) { - case XIDependentTouch: - type = QTouchDevice::TouchPad; - break; - case XIDirectTouch: - type = QTouchDevice::TouchScreen; - break; - } + case XITouchClass: { + XITouchClassInfo *tci = reinterpret_cast<XITouchClassInfo *>(classinfo); + maxTouchPoints = tci->num_touches; + qCDebug(lcQpaXInputDevices, " has touch class with mode %d", tci->mode); + switch (tci->mode) { + case XIDependentTouch: + type = QTouchDevice::TouchPad; + break; + case XIDirectTouch: + type = QTouchDevice::TouchScreen; break; } + break; + } #endif // XCB_USE_XINPUT22 - case XIValuatorClass: { - XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo); - // 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 (vci->label == atom(QXcbAtom::AbsMTPositionX)) - caps |= QTouchDevice::Position | QTouchDevice::NormalizedPosition; - else if (vci->label == atom(QXcbAtom::AbsMTTouchMajor)) - caps |= QTouchDevice::Area; - else if (vci->label == atom(QXcbAtom::AbsMTOrientation)) - dev->providesTouchOrientation = true; - else if (vci->label == atom(QXcbAtom::AbsMTPressure) || vci->label == atom(QXcbAtom::AbsPressure)) - caps |= QTouchDevice::Pressure; - else if (vci->label == atom(QXcbAtom::RelX)) { - hasRelativeCoords = true; - dev->size.setWidth((vci->max - vci->min) * 1000.0 / vciResolution); - } else if (vci->label == atom(QXcbAtom::RelY)) { - hasRelativeCoords = true; - dev->size.setHeight((vci->max - vci->min) * 1000.0 / vciResolution); - } else if (vci->label == atom(QXcbAtom::AbsX)) { - caps |= QTouchDevice::Position; - dev->size.setWidth((vci->max - vci->min) * 1000.0 / vciResolution); - } else if (vci->label == atom(QXcbAtom::AbsY)) { - caps |= QTouchDevice::Position; - dev->size.setHeight((vci->max - vci->min) * 1000.0 / vciResolution); - } - break; + case XIValuatorClass: { + XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo); + const QXcbAtom::Atom valuatorAtom = qatom(vci->label); + if (valuatorAtom < QXcbAtom::NAtoms) { + TouchDeviceData::ValuatorClassInfo info; + info.min = vci->min; + info.max = vci->max; + info.number = vci->number; + info.label = valuatorAtom; + dev.valuatorInfo.append(info); } - default: - break; + // 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) + caps |= QTouchDevice::Position | QTouchDevice::NormalizedPosition; + else if (valuatorAtom == QXcbAtom::AbsMTTouchMajor) + caps |= QTouchDevice::Area; + else if (valuatorAtom == QXcbAtom::AbsMTOrientation) + dev.providesTouchOrientation = true; + else if (valuatorAtom == QXcbAtom::AbsMTPressure || valuatorAtom == QXcbAtom::AbsPressure) + caps |= QTouchDevice::Pressure; + else if (valuatorAtom == QXcbAtom::RelX) { + hasRelativeCoords = true; + dev.size.setWidth((vci->max - vci->min) * 1000.0 / vciResolution); + } else if (valuatorAtom == QXcbAtom::RelY) { + hasRelativeCoords = true; + dev.size.setHeight((vci->max - vci->min) * 1000.0 / vciResolution); + } else if (valuatorAtom == QXcbAtom::AbsX) { + caps |= QTouchDevice::Position; + dev.size.setWidth((vci->max - vci->min) * 1000.0 / vciResolution); + } else if (valuatorAtom == QXcbAtom::AbsY) { + caps |= QTouchDevice::Position; + dev.size.setHeight((vci->max - vci->min) * 1000.0 / vciResolution); } + break; } - if (type < 0 && caps && hasRelativeCoords) { - type = QTouchDevice::TouchPad; - if (dev->size.width() < 10 || dev->size.height() < 10 || - dev->size.width() > 10000 || dev->size.height() > 10000) - dev->size = QSizeF(130, 110); - } - if (!isAtLeastXI22() || type == QTouchDevice::TouchPad) - caps |= QTouchDevice::MouseEmulation; - - if (type >= QTouchDevice::TouchScreen && type <= QTouchDevice::TouchPad) { - dev->qtTouchDevice = new QTouchDevice; - dev->qtTouchDevice->setName(QString::fromUtf8(dev->xiDeviceInfo->name)); - dev->qtTouchDevice->setType((QTouchDevice::DeviceType)type); - dev->qtTouchDevice->setCapabilities(caps); - dev->qtTouchDevice->setMaximumTouchPoints(maxTouchPoints); - if (caps != 0) - QWindowSystemInterface::registerTouchDevice(dev->qtTouchDevice); - m_touchDevices[id] = dev; - } else { - XIFreeDeviceInfo(dev->xiDeviceInfo); - delete dev; - dev = 0; + default: + break; } } - return dev; + if (type < 0 && caps && hasRelativeCoords) { + type = QTouchDevice::TouchPad; + if (dev.size.width() < 10 || dev.size.height() < 10 || + dev.size.width() > 10000 || dev.size.height() > 10000) + dev.size = QSizeF(130, 110); + } + if (!isAtLeastXI22() || type == QTouchDevice::TouchPad) + caps |= QTouchDevice::MouseEmulation; + + if (type >= QTouchDevice::TouchScreen && type <= QTouchDevice::TouchPad) { + dev.qtTouchDevice = new QTouchDevice; + dev.qtTouchDevice->setName(QString::fromUtf8(deviceinfo->name)); + dev.qtTouchDevice->setType((QTouchDevice::DeviceType)type); + dev.qtTouchDevice->setCapabilities(caps); + dev.qtTouchDevice->setMaximumTouchPoints(maxTouchPoints); + if (caps != 0) + QWindowSystemInterface::registerTouchDevice(dev.qtTouchDevice); + m_touchDevices[deviceinfo->deviceid] = dev; + isTouchDevice = true; + } + + return isTouchDevice ? &m_touchDevices[deviceinfo->deviceid] : nullptr; } #if defined(XCB_USE_XINPUT21) || QT_CONFIG(tabletevent) @@ -525,7 +576,7 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) break; } case XI_HierarchyChanged: - xi2HandleHierachyEvent(xiEvent); + xi2HandleHierarchyEvent(xiEvent); return; case XI_DeviceChanged: xi2HandleDeviceChangedEvent(xiEvent); @@ -549,9 +600,8 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) #endif // QT_CONFIG(tabletevent) #ifdef XCB_USE_XINPUT21 - QHash<int, ScrollingDevice>::iterator device = m_scrollingDevices.find(sourceDeviceId); - if (device != m_scrollingDevices.end()) - xi2HandleScrollEvent(xiEvent, device.value()); + if (ScrollingDevice *device = scrollingDeviceForId(sourceDeviceId)) + xi2HandleScrollEvent(xiEvent, *device); #endif // XCB_USE_XINPUT21 #ifdef XCB_USE_XINPUT22 @@ -560,7 +610,7 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) case XI_ButtonPress: case XI_ButtonRelease: case XI_Motion: - if (xi2MouseEvents() && eventListener && !(xiDeviceEvent->flags & XIPointerEmulated)) + if (!xi2MouseEventsDisabled() && eventListener && !(xiDeviceEvent->flags & XIPointerEmulated)) eventListener->handleXIMouseEvent(event); break; @@ -576,7 +626,7 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) xi2ProcessTouch(xiDeviceEvent, platformWindow); break; } - } else if (xiEnterEvent && xi2MouseEvents() && eventListener) { + } else if (xiEnterEvent && !xi2MouseEventsDisabled() && eventListener) { switch (xiEnterEvent->evtype) { case XI_Enter: case XI_Leave: @@ -587,20 +637,25 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) #endif // XCB_USE_XINPUT22 } +bool QXcbConnection::xi2MouseEventsDisabled() const +{ + static bool xi2MouseDisabled = qEnvironmentVariableIsSet("QT_XCB_NO_XI2_MOUSE"); + // FIXME: Don't use XInput2 mouse events when Xinerama extension + // is enabled, because it causes problems with multi-monitor setup. + return xi2MouseDisabled || has_xinerama_extension; +} + #ifdef XCB_USE_XINPUT22 -static qreal valuatorNormalized(double value, XIValuatorClassInfo *vci) +bool QXcbConnection::isTouchScreen(int id) { - if (value > vci->max) - value = vci->max; - if (value < vci->min) - value = vci->min; - return (value - vci->min) / (vci->max - vci->min); + auto device = touchDeviceForId(id); + return device && device->qtTouchDevice->type() == QTouchDevice::TouchScreen; } void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindow) { xXIDeviceEvent *xiDeviceEvent = static_cast<xXIDeviceEvent *>(xiDevEvent); - XInput2TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid); + TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid); Q_ASSERT(dev); const bool firstTouch = dev->touchPoints.isEmpty(); if (xiDeviceEvent->evtype == XI_TouchBegin) { @@ -617,53 +672,53 @@ 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 (int i = 0; i < dev->xiDeviceInfo->num_classes; ++i) { - XIAnyClassInfo *classinfo = dev->xiDeviceInfo->classes[i]; - if (classinfo->type == XIValuatorClass) { - XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classinfo); - int n = vci->number; - double value; - if (!xi2GetValuatorValueIfSet(xiDeviceEvent, n, &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 ); - if (vci->label == atom(QXcbAtom::RelX)) { - nx = valuatorNormalized(value, vci); - } else if (vci->label == atom(QXcbAtom::RelY)) { - ny = valuatorNormalized(value, vci); - } else if (vci->label == atom(QXcbAtom::AbsX)) { - nx = valuatorNormalized(value, vci); - } else if (vci->label == atom(QXcbAtom::AbsY)) { - ny = valuatorNormalized(value, vci); - } else if (vci->label == atom(QXcbAtom::AbsMTPositionX)) { - nx = valuatorNormalized(value, vci); - } else if (vci->label == atom(QXcbAtom::AbsMTPositionY)) { - ny = valuatorNormalized(value, vci); - } else if (vci->label == atom(QXcbAtom::AbsMTTouchMajor)) { - const qreal sw = screen->geometry().width(); - const qreal sh = screen->geometry().height(); - w = valuatorNormalized(value, vci) * std::sqrt(sw * sw + sh * sh); - } else if (vci->label == atom(QXcbAtom::AbsMTTouchMinor)) { - const qreal sw = screen->geometry().width(); - const qreal sh = screen->geometry().height(); - h = valuatorNormalized(value, vci) * std::sqrt(sw * sw + sh * sh); - } else if (vci->label == atom(QXcbAtom::AbsMTOrientation)) { - // 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 - // don't change the result, so normalize value to range - // [0, vci->max] first. - value = qAbs(value); - while (value > vci->max) - value -= 2 * vci->max; - value = qAbs(value); - majorAxisIsY = value < vci->max - value; - } else if (vci->label == atom(QXcbAtom::AbsMTPressure) || - vci->label == atom(QXcbAtom::AbsPressure)) { - touchPoint.pressure = valuatorNormalized(value, vci); - } + for (const TouchDeviceData::ValuatorClassInfo vci : 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); + 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) { + nx = valuatorNormalized; + } else if (vci.label == QXcbAtom::RelY) { + ny = valuatorNormalized; + } else if (vci.label == QXcbAtom::AbsX) { + nx = valuatorNormalized; + } else if (vci.label == QXcbAtom::AbsY) { + ny = valuatorNormalized; + } else if (vci.label == QXcbAtom::AbsMTPositionX) { + nx = valuatorNormalized; + } else if (vci.label == QXcbAtom::AbsMTPositionY) { + ny = valuatorNormalized; + } else if (vci.label == QXcbAtom::AbsMTTouchMajor) { + const qreal sw = screen->geometry().width(); + const qreal sh = screen->geometry().height(); + w = valuatorNormalized * std::sqrt(sw * sw + sh * sh); + } else if (vci.label == QXcbAtom::AbsMTTouchMinor) { + const qreal sw = screen->geometry().width(); + const qreal sh = screen->geometry().height(); + h = valuatorNormalized * std::sqrt(sw * sw + sh * sh); + } else if (vci.label == QXcbAtom::AbsMTOrientation) { + // 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 + // don't change the result, so normalize value to range + // [0, vci.max] first. + value = qAbs(value); + while (value > vci.max) + value -= 2 * vci.max; + value = qAbs(value); + majorAxisIsY = value < vci.max - value; + } else if (vci.label == QXcbAtom::AbsMTPressure || vci.label == QXcbAtom::AbsPressure) { + touchPoint.pressure = valuatorNormalized; } + } // If any value was not updated, use the last-known value. if (nx == -1.0) { @@ -765,12 +820,12 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo bool QXcbConnection::startSystemResizeForTouchBegin(xcb_window_t window, const QPoint &point, Qt::Corner corner) { - QHash<int, XInput2TouchDeviceData*>::const_iterator devIt = m_touchDevices.constBegin(); + QHash<int, TouchDeviceData>::const_iterator devIt = m_touchDevices.constBegin(); for (; devIt != m_touchDevices.constEnd(); ++devIt) { - XInput2TouchDeviceData *deviceData = devIt.value(); - if (deviceData->qtTouchDevice->type() == QTouchDevice::TouchScreen) { - QHash<int, QPointF>::const_iterator pointIt = deviceData->pointPressedPosition.constBegin(); - for (; pointIt != deviceData->pointPressedPosition.constEnd(); ++pointIt) { + TouchDeviceData deviceData = devIt.value(); + if (deviceData.qtTouchDevice->type() == QTouchDevice::TouchScreen) { + QHash<int, QPointF>::const_iterator pointIt = deviceData.pointPressedPosition.constBegin(); + for (; pointIt != deviceData.pointPressedPosition.constEnd(); ++pointIt) { if (pointIt.value().toPoint() == point) { m_startSystemResizeInfo.window = window; m_startSystemResizeInfo.deviceid = devIt.key(); @@ -783,6 +838,7 @@ bool QXcbConnection::startSystemResizeForTouchBegin(xcb_window_t window, const Q } return false; } +#endif // XCB_USE_XINPUT22 bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab) { @@ -843,59 +899,70 @@ bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab) return grabbed; } -#endif // XCB_USE_XINPUT22 -void QXcbConnection::xi2HandleHierachyEvent(void *event) +void QXcbConnection::xi2HandleHierarchyEvent(void *event) { xXIHierarchyEvent *xiEvent = reinterpret_cast<xXIHierarchyEvent *>(event); // We only care about hotplugged devices if (!(xiEvent->flags & (XISlaveRemoved | XISlaveAdded))) return; + xi2SetupDevices(); - // Reselect events for all event-listening windows. - for (auto it = m_mapper.cbegin(), end = m_mapper.cend(); it != end; ++it) - xi2Select(it.key()); + + if (xi2MouseEventsDisabled()) { + // In compatibility mode (a.k.a xi2MouseEventsDisabled() mode) we select events for + // each device separately. When a new device appears, we have to select events from + // this device on all event-listening windows. This is not needed when events are + // selected via XIAllDevices/XIAllMasterDevices (as in xi2SelectDeviceEvents()). + for (auto it = m_mapper.cbegin(), end = m_mapper.cend(); it != end; ++it) + xi2SelectDeviceEventsCompatibility(it.key()); + } } void QXcbConnection::xi2HandleDeviceChangedEvent(void *event) { xXIDeviceChangedEvent *xiEvent = reinterpret_cast<xXIDeviceChangedEvent *>(event); - - // ### If a slave device changes (XIDeviceChange), we should probably run setup on it again. - if (xiEvent->reason != XISlaveSwitch) - return; - + switch (xiEvent->reason) { + case XIDeviceChange: { + int nrDevices = 0; + Display *dpy = static_cast<Display *>(m_xlib_display); + XIDeviceInfo* deviceInfo = XIQueryDevice(dpy, xiEvent->sourceid, &nrDevices); + if (nrDevices <= 0) + return; + xi2SetupDevice(deviceInfo); + XIFreeDeviceInfo(deviceInfo); + break; + } + case XISlaveSwitch: { #ifdef XCB_USE_XINPUT21 - // This code handles broken scrolling device drivers that reset absolute positions - // when they are made active. Whenever a new slave device is made active the - // primary pointer sends a DeviceChanged event with XISlaveSwitch, and the new - // active slave in sourceid. - - QHash<int, ScrollingDevice>::iterator device = m_scrollingDevices.find(xiEvent->sourceid); - if (device == m_scrollingDevices.end()) - return; + if (ScrollingDevice *scrollingDevice = scrollingDeviceForId(xiEvent->sourceid)) + xi2UpdateScrollingDevice(*scrollingDevice); +#endif + break; + } + default: + qCDebug(lcQpaXInputEvents, "unknown device-changed-event (device %d)", xiEvent->sourceid); + break; + } +} +#ifdef XCB_USE_XINPUT21 +void QXcbConnection::xi2UpdateScrollingDevice(ScrollingDevice &scrollingDevice) +{ int nrDevices = 0; - XIDeviceInfo* xiDeviceInfo = XIQueryDevice(static_cast<Display *>(m_xlib_display), xiEvent->sourceid, &nrDevices); + Display *dpy = static_cast<Display *>(m_xlib_display); + XIDeviceInfo* deviceInfo = XIQueryDevice(dpy, scrollingDevice.deviceId, &nrDevices); if (nrDevices <= 0) { - qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", xiEvent->sourceid); + qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", scrollingDevice.deviceId); return; } - updateScrollingDevice(*device, xiDeviceInfo->num_classes, xiDeviceInfo->classes); - XIFreeDeviceInfo(xiDeviceInfo); -#endif -} - -void QXcbConnection::updateScrollingDevice(ScrollingDevice &scrollingDevice, int num_classes, void *classInfo) -{ -#ifdef XCB_USE_XINPUT21 - XIAnyClassInfo **classes = reinterpret_cast<XIAnyClassInfo**>(classInfo); QPointF lastScrollPosition; if (lcQpaXInput().isDebugEnabled()) lastScrollPosition = scrollingDevice.lastScrollPosition; - for (int c = 0; c < num_classes; ++c) { - if (classes[c]->type == XIValuatorClass) { - XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classes[c]); + for (int c = 0; c < deviceInfo->num_classes; ++c) { + XIAnyClassInfo *classInfo = deviceInfo->classes[c]; + if (classInfo->type == XIValuatorClass) { + XIValuatorClassInfo *vci = reinterpret_cast<XIValuatorClassInfo *>(classInfo); const int valuatorAtom = qatom(vci->label); if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel) scrollingDevice.lastScrollPosition.setX(vci->value); @@ -908,37 +975,30 @@ void QXcbConnection::updateScrollingDevice(ScrollingDevice &scrollingDevice, int lastScrollPosition.x(), lastScrollPosition.y(), scrollingDevice.lastScrollPosition.x(), scrollingDevice.lastScrollPosition.y()); -#else - Q_UNUSED(scrollingDevice); - Q_UNUSED(num_classes); - Q_UNUSED(classInfo); -#endif + + XIFreeDeviceInfo(deviceInfo); } -#ifdef XCB_USE_XINPUT21 -void QXcbConnection::handleEnterEvent() +void QXcbConnection::xi2UpdateScrollingDevices() { QHash<int, ScrollingDevice>::iterator it = m_scrollingDevices.begin(); const QHash<int, ScrollingDevice>::iterator end = m_scrollingDevices.end(); while (it != end) { - ScrollingDevice& scrollingDevice = it.value(); - int nrDevices = 0; - XIDeviceInfo* xiDeviceInfo = XIQueryDevice(static_cast<Display *>(m_xlib_display), scrollingDevice.deviceId, &nrDevices); - if (nrDevices <= 0) { - qCDebug(lcQpaXInputDevices, "scrolling device %d no longer present", scrollingDevice.deviceId); - it = m_scrollingDevices.erase(it); - continue; - } - updateScrollingDevice(scrollingDevice, xiDeviceInfo->num_classes, xiDeviceInfo->classes); - XIFreeDeviceInfo(xiDeviceInfo); + xi2UpdateScrollingDevice(it.value()); ++it; } } -#endif + +QXcbConnection::ScrollingDevice *QXcbConnection::scrollingDeviceForId(int id) +{ + ScrollingDevice *dev = nullptr; + if (m_scrollingDevices.contains(id)) + dev = &m_scrollingDevices[id]; + return dev; +} void QXcbConnection::xi2HandleScrollEvent(void *event, ScrollingDevice &scrollingDevice) { -#ifdef XCB_USE_XINPUT21 xXIGenericDeviceEvent *xiEvent = reinterpret_cast<xXIGenericDeviceEvent *>(event); if (xiEvent->evtype == XI_Motion && scrollingDevice.orientations) { @@ -1008,10 +1068,51 @@ void QXcbConnection::xi2HandleScrollEvent(void *event, ScrollingDevice &scrollin } } } -#else - Q_UNUSED(event); - Q_UNUSED(scrollingDevice); +} #endif // XCB_USE_XINPUT21 + +static int xi2ValuatorOffset(const unsigned char *maskPtr, int maskLen, int number) +{ + int offset = 0; + for (int i = 0; i < maskLen; i++) { + if (number < 8) { + if ((maskPtr[i] & (1 << number)) == 0) + return -1; + } + for (int j = 0; j < 8; j++) { + if (j == number) + return offset; + if (maskPtr[i] & (1 << j)) + offset++; + } + number -= 8; + } + return -1; +} + +bool QXcbConnection::xi2GetValuatorValueIfSet(const void *event, int valuatorNum, double *value) +{ + const xXIDeviceEvent *xideviceevent = static_cast<const xXIDeviceEvent *>(event); + const unsigned char *buttonsMaskAddr = (const unsigned char*)&xideviceevent[1]; + const unsigned char *valuatorsMaskAddr = buttonsMaskAddr + xideviceevent->buttons_len * 4; + FP3232 *valuatorsValuesAddr = (FP3232*)(valuatorsMaskAddr + xideviceevent->valuators_len * 4); + + int valuatorOffset = xi2ValuatorOffset(valuatorsMaskAddr, xideviceevent->valuators_len, valuatorNum); + if (valuatorOffset < 0) + return false; + + *value = valuatorsValuesAddr[valuatorOffset].integral; + *value += ((double)valuatorsValuesAddr[valuatorOffset].frac / (1 << 16) / (1 << 16)); + return true; +} + +void QXcbConnection::xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *event) +{ + // xcb event structs contain stuff that wasn't on the wire, the full_sequence field + // adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes. + // Move this data back to have the same layout in memory as it was on the wire + // and allow casting, overwriting the full_sequence field. + memmove((char*) event + 32, (char*) event + 36, event->length * 4); } Qt::MouseButton QXcbConnection::xiToQtMouseButton(uint32_t b) @@ -1028,15 +1129,6 @@ Qt::MouseButton QXcbConnection::xiToQtMouseButton(uint32_t b) return Qt::NoButton; } -#ifdef XCB_USE_XINPUT22 -bool QXcbConnection::isTouchScreen(int id) const -{ - auto device = m_touchDevices.value(id); - return device && device->qtTouchDevice - && device->qtTouchDevice->type() == QTouchDevice::TouchScreen; -} -#endif - #if QT_CONFIG(tabletevent) static QTabletEvent::TabletDevice toolIdToTabletDevice(quint32 toolId) { // keep in sync with wacom_intuos_inout() in Linux kernel driver wacom_wac.c |