From 60831935490722202d09dfc7adc9bf1f0708dcdf Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Wed, 4 Jul 2012 15:59:02 +0200 Subject: handle XInput 2.2 multipoint touch events Change-Id: I5c925ae3e191244c7ab9415e4ba2fe49b93dd2af Reviewed-by: Laszlo Agocs --- src/plugins/platforms/xcb/qxcbconnection.h | 12 +- src/plugins/platforms/xcb/qxcbconnection_xi2.cpp | 224 +++++++++++++++++++++-- 2 files changed, 221 insertions(+), 15 deletions(-) (limited to 'src/plugins') diff --git a/src/plugins/platforms/xcb/qxcbconnection.h b/src/plugins/platforms/xcb/qxcbconnection.h index 2c7b11fb10..902ff831f9 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.h +++ b/src/plugins/platforms/xcb/qxcbconnection.h @@ -51,6 +51,7 @@ #include #include #include +#include #ifndef QT_NO_TABLETEVENT #include @@ -58,6 +59,11 @@ #ifdef XCB_USE_XINPUT2_MAEMO struct XInput2MaemoData; +#elif XCB_USE_XINPUT2 +#ifdef XI_TouchBeginMask +#define XCB_USE_XINPUT22 // XI 2.2 adds multi-point touch support +#endif +struct XInput2DeviceData; #endif //#define Q_XCB_DEBUG @@ -397,6 +403,7 @@ private: #ifdef XCB_USE_XINPUT2 void initializeXInput2(); void finalizeXInput2(); + XInput2DeviceData *deviceForId(int id); void xi2HandleEvent(xcb_ge_event_t *event); int m_xiOpCode, m_xiEventBase, m_xiErrorBase; #ifndef QT_NO_TABLETEVENT @@ -417,7 +424,7 @@ private: }; void xi2QueryTabletData(void *dev, TabletData *tabletData); // use no XI stuff in headers void xi2SetupTabletDevices(); - void xi2HandleTabletEvent(void *event, TabletData *tabletData); + bool xi2HandleTabletEvent(void *event, TabletData *tabletData); void xi2ReportTabletEvent(const TabletData &tabletData, void *event); QVector m_tabletData; #endif @@ -459,6 +466,9 @@ private: QXcbEventReader *m_reader; #ifdef XCB_USE_XINPUT2_MAEMO XInput2MaemoData *m_xinputData; +#elif defined(XCB_USE_XINPUT2) + QHash m_touchPoints; + QHash m_touchDevices; #endif #ifdef XCB_USE_DRI2 uint32_t m_dri2_major; diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp index ef4c0526a8..241748d50f 100644 --- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp @@ -40,13 +40,30 @@ ****************************************************************************/ #include "qxcbconnection.h" +#include "qxcbscreen.h" #include "qxcbwindow.h" +#include "qtouchdevice.h" #include +//#define XI2_TOUCH_DEBUG +#ifdef XI2_TOUCH_DEBUG +#include +#endif #ifdef XCB_USE_XINPUT2 #include #include +#define FINGER_MAX_WIDTH_MM 10 + +struct XInput2DeviceData { + XInput2DeviceData() + : xiDeviceInfo(0) + , qtTouchDevice(0) + { + } + XIDeviceInfo *xiDeviceInfo; + QTouchDevice *qtTouchDevice; +}; #ifndef QT_NO_TABLETEVENT static inline bool q_xi2_is_tablet(XIDeviceInfo *dev) @@ -70,15 +87,9 @@ void QXcbConnection::initializeXInput2() } else { m_xi2Enabled = true; } - if (m_xi2Enabled) { #ifndef QT_NO_TABLETEVENT - // Tablet support: Figure out the stylus-related devices. We will - // only select events on this device. Press, motion, etc. events - // must never be selected for _all_ devices as that would render - // the standard XCB_MOTION_NOTIFY and similar handlers useless and - // we have no intention to infect all the pure xcb code with - // Xlib-based XI2. + // Tablet support: Find the stylus-related devices. xi2SetupTabletDevices(); #endif // QT_NO_TABLETEVENT } @@ -94,13 +105,30 @@ void QXcbConnection::xi2Select(xcb_window_t window) if (!m_xi2Enabled) return; + Display *xDisplay = static_cast(m_xlib_display); + unsigned int bitMask = 0; + unsigned char *xiBitMask = reinterpret_cast(&bitMask); + +#ifdef XCB_USE_XINPUT22 + // Select touch events on all master devices indiscriminately. + bitMask |= XI_TouchBeginMask; + bitMask |= XI_TouchUpdateMask; + bitMask |= XI_TouchEndMask; + XIEventMask mask; + mask.deviceid = XIAllMasterDevices; + mask.mask_len = sizeof(bitMask); + mask.mask = xiBitMask; + XISelectEvents(xDisplay, window, &mask, 1); +#endif + #ifndef QT_NO_TABLETEVENT - // Tablets. + // For each tablet, select some additional event types. + // Press, motion, etc. events must never be selected for _all_ devices + // as that would render the standard XCB_MOTION_NOTIFY and + // similar handlers useless and we have no intention to infect + // all the pure xcb code with Xlib-based XI2. if (!m_tabletData.isEmpty()) { - Display *xDisplay = static_cast(m_xlib_display); QVector xiEventMask(m_tabletData.count()); - unsigned int bitMask = 0; - unsigned char *xiBitMask = reinterpret_cast(&bitMask); bitMask |= XI_ButtonPressMask; bitMask |= XI_ButtonReleaseMask; bitMask |= XI_MotionMask; @@ -115,18 +143,180 @@ void QXcbConnection::xi2Select(xcb_window_t window) #endif // QT_NO_TABLETEVENT } +XInput2DeviceData *QXcbConnection::deviceForId(int id) +{ + XInput2DeviceData *dev = m_touchDevices[id]; + if (!dev) { + int unused = 0; + QTouchDevice::Capabilities caps = 0; + dev = new XInput2DeviceData; + dev->xiDeviceInfo = XIQueryDevice(static_cast(m_xlib_display), id, &unused); + dev->qtTouchDevice = new QTouchDevice; + for (int i = 0; i < dev->xiDeviceInfo->num_classes; ++i) { + XIAnyClassInfo *classinfo = dev->xiDeviceInfo->classes[i]; + switch (classinfo->type) { +#ifdef XCB_USE_XINPUT22 + case XITouchClass: { + XITouchClassInfo *tci = reinterpret_cast(classinfo); + switch (tci->mode) { + case XIModeRelative: + dev->qtTouchDevice->setType(QTouchDevice::TouchPad); + break; + case XIModeAbsolute: + dev->qtTouchDevice->setType(QTouchDevice::TouchScreen); + break; + } + } break; +#endif + case XIValuatorClass: { + XIValuatorClassInfo *vci = reinterpret_cast(classinfo); + 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::AbsMTPressure) || vci->label == atom(QXcbAtom::AbsPressure)) + caps |= QTouchDevice::Pressure; + } break; + } + } + dev->qtTouchDevice->setCapabilities(caps); + dev->qtTouchDevice->setName(dev->xiDeviceInfo->name); + if (caps != 0) + QWindowSystemInterface::registerTouchDevice(dev->qtTouchDevice); +#ifdef XI2_TOUCH_DEBUG + qDebug("registered new device %s with %d classes and %d max touch points", + dev->xiDeviceInfo->name, dev->xiDeviceInfo->num_classes, dev->qtTouchDevice->maxTouchPoints()); +#endif + m_touchDevices[id] = dev; + } + return dev; +} + +#ifdef XCB_USE_XINPUT22 +static qreal fixed1616ToReal(FP1616 val) +{ + return (qreal(val >> 16)) + (val & 0xFF) / (qreal)0xFF; +} + +static qreal valuatorNormalized(double value, XIValuatorClassInfo *vci) +{ + if (value > vci->max) + value = vci->max; + if (value < vci->min) + value = vci->min; + return (value - vci->min) / (vci->max - vci->min); +} +#endif + void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event) { if (xi2PrepareXIGenericDeviceEvent(event, m_xiOpCode)) { xXIGenericDeviceEvent *xiEvent = reinterpret_cast(event); + #ifndef QT_NO_TABLETEVENT for (int i = 0; i < m_tabletData.count(); ++i) { if (m_tabletData.at(i).deviceId == xiEvent->deviceid) { - xi2HandleTabletEvent(xiEvent, &m_tabletData[i]); - return; + if (xi2HandleTabletEvent(xiEvent, &m_tabletData[i])) + return; } } #endif // QT_NO_TABLETEVENT + +#ifdef XCB_USE_XINPUT22 + if (xiEvent->evtype == XI_TouchBegin || xiEvent->evtype == XI_TouchUpdate || xiEvent->evtype == XI_TouchEnd) { + xXIDeviceEvent* xiDeviceEvent = reinterpret_cast(event); +#ifdef XI2_TOUCH_DEBUG + qDebug("XI2 event type %d seq %d detail %d pos 0x%X,0x%X %f,%f root pos %f,%f", + event->event_type, xiEvent->sequenceNumber, xiDeviceEvent->detail, + xiDeviceEvent->event_x, xiDeviceEvent->event_y, + fixed1616ToReal(xiDeviceEvent->event_x), fixed1616ToReal(xiDeviceEvent->event_y), + fixed1616ToReal(xiDeviceEvent->root_x), fixed1616ToReal(xiDeviceEvent->root_y) ); +#endif + + if (QXcbWindow *platformWindow = platformWindowFromId(xiDeviceEvent->event)) { + XInput2DeviceData *dev = deviceForId(xiEvent->deviceid); + if (xiEvent->evtype == XI_TouchBegin) { + QWindowSystemInterface::TouchPoint tp; + tp.id = xiDeviceEvent->detail % INT_MAX; + tp.state = Qt::TouchPointPressed; + tp.pressure = -1.0; + m_touchPoints[tp.id] = tp; + } + QWindowSystemInterface::TouchPoint &touchPoint = m_touchPoints[xiDeviceEvent->detail]; + qreal x = fixed1616ToReal(xiDeviceEvent->root_x); + qreal y = fixed1616ToReal(xiDeviceEvent->root_y); + qreal nx = -1.0, ny = -1.0, w = 0.0, h = 0.0; + QXcbScreen* screen = m_screens.at(0); + for (int i = 0; i < dev->xiDeviceInfo->num_classes; ++i) { + XIAnyClassInfo *classinfo = dev->xiDeviceInfo->classes[i]; + if (classinfo->type == XIValuatorClass) { + XIValuatorClassInfo *vci = reinterpret_cast(classinfo); + int n = vci->number; + double value; + if (!xi2GetValuatorValueIfSet(xiDeviceEvent, n, &value)) + continue; +#ifdef XI2_TOUCH_DEBUG + qDebug(" valuator class label %d value %lf from range %lf -> %lf name %s", + vci->label, value, vci->min, vci->max, XGetAtomName(static_cast(m_xlib_display), vci->label) ); +#endif + 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)) { + // Convert the value within its range as a fraction of a finger's max (contact patch) + // width in mm, and from there to pixels depending on screen resolution + w = valuatorNormalized(value, vci) * FINGER_MAX_WIDTH_MM * + screen->geometry().width() / screen->physicalSize().width(); + } else if (vci->label == atom(QXcbAtom::AbsMTTouchMinor)) { + h = valuatorNormalized(value, vci) * FINGER_MAX_WIDTH_MM * + screen->geometry().height() / screen->physicalSize().height(); + } else if (vci->label == atom(QXcbAtom::AbsMTPressure) || + vci->label == atom(QXcbAtom::AbsPressure)) { + touchPoint.pressure = valuatorNormalized(value, vci); + } + } + } + // If any value was not updated, use the last-known value. + if (nx == -1.0) { + x = touchPoint.area.center().x(); + nx = x / screen->geometry().width(); + } + if (ny == -1.0) { + y = touchPoint.area.center().y(); + ny = y / screen->geometry().height(); + } + if (xiEvent->evtype != XI_TouchEnd) { + if (w == 0.0) + w = touchPoint.area.width(); + if (h == 0.0) + h = touchPoint.area.height(); + } + + switch (xiEvent->evtype) { + case XI_TouchUpdate: + if (touchPoint.area.center() != QPoint(x, y)) + touchPoint.state = Qt::TouchPointMoved; + else + touchPoint.state = Qt::TouchPointStationary; + break; + case XI_TouchEnd: + touchPoint.state = Qt::TouchPointReleased; + } + touchPoint.area = QRectF(x - w/2, y - h/2, w, h); + touchPoint.normalPosition = QPointF(nx, ny); + +#ifdef XI2_TOUCH_DEBUG + qDebug() << " tp " << touchPoint.id << " state " << touchPoint.state << " pos norm " << touchPoint.normalPosition << + " area " << touchPoint.area << " pressure " << touchPoint.pressure; +#endif + QWindowSystemInterface::handleTouchEvent(platformWindow->window(), xiEvent->time, dev->qtTouchDevice, m_touchPoints.values()); + // If a touchpoint was released, we can forget it, because the ID won't be reused. + if (touchPoint.state == Qt::TouchPointReleased) + m_touchPoints.remove(touchPoint.id); + } + } +#endif } } @@ -188,6 +378,9 @@ void QXcbConnection::xi2SetupTabletDevices() if (q_xi2_is_tablet(dev)) { TabletData tabletData; xi2QueryTabletData(dev, &tabletData); +#ifdef XI2_TOUCH_DEBUG + qDebug() << "found tablet" << dev->name; +#endif m_tabletData.append(tabletData); } XIFreeDeviceInfo(dev); @@ -197,8 +390,9 @@ void QXcbConnection::xi2SetupTabletDevices() } } -void QXcbConnection::xi2HandleTabletEvent(void *event, TabletData *tabletData) +bool QXcbConnection::xi2HandleTabletEvent(void *event, TabletData *tabletData) { + bool handled = true; Display *xDisplay = static_cast(m_xlib_display); xXIGenericDeviceEvent *xiEvent = static_cast(event); switch (xiEvent->evtype) { @@ -257,8 +451,10 @@ void QXcbConnection::xi2HandleTabletEvent(void *event, TabletData *tabletData) break; } default: + handled = false; break; } + return handled; } void QXcbConnection::xi2ReportTabletEvent(const TabletData &tabletData, void *event) -- cgit v1.2.3