From 2edada763af005367810dda09ca0cdb5adc494b9 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Sat, 17 Dec 2011 21:15:50 +0200 Subject: Update the touchscreen plug-in. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now works with bcm5974: Added manual contact tracking for drivers that do not report a tracking id and fixed abs limit querying to use ABS_MT_* instead of ABS_*. Fixed reported area: The incoming point was the top-left point instead of the center which was incorrect. Added pressure support. Tracking of event type has been removed as handleTouchEvent no longer needs it. Broken debug prints have been removed. Changed udev auto-detection to pick only /dev/input/event*. Fixed multiple released state reports with some drivers. Name and capabilities are now set properly for the QTouchDevice. Change-Id: I8f026c9a14465bfb6d567f4dcf36c5c03f843868 Reviewed-by: Jørgen Lind --- src/plugins/generic/touchscreen/README | 24 +- .../generic/touchscreen/qtoucheventsenderqpa.cpp | 48 ++-- .../generic/touchscreen/qtoucheventsenderqpa.h | 6 +- src/plugins/generic/touchscreen/qtouchscreen.cpp | 255 ++++++++++++--------- src/plugins/generic/touchscreen/qtouchscreen.h | 3 +- 5 files changed, 188 insertions(+), 148 deletions(-) diff --git a/src/plugins/generic/touchscreen/README b/src/plugins/generic/touchscreen/README index bed9016329..6bd244df19 100644 --- a/src/plugins/generic/touchscreen/README +++ b/src/plugins/generic/touchscreen/README @@ -1,6 +1,8 @@ -Generic plug-in for evdev touch events +Generic plug-in for evdev touch events. -(a) Using as a QPA generic plug-in +Tested with the following drivers: bcm5974, hid_magicmouse. + +(1) Using as a QPA generic plug-in 1. set up and connect the touch device 2. install libudev-dev or similar @@ -24,20 +26,22 @@ replacement by default. For embedded systems the code could to be extended to generate also mouse events (by calling handleMouseEvent for the primary touch point for example). -(b) Using in a compositor +(2) Using in a compositor The classes (QTouchScreenHandler, QTouchScreenHandlerThread) are also suitable for direct inclusion into an application, e.g. a Wayland -compositor. The compositor will then usually register its own +compositor. The compositor may then register its own QTouchScreenObserver because relying on the QTouchEvents generated by -the QPA event sender is often not satisfactory, as some low-level -details may get lost, and due to performance reasons. - +the QPA event sender may not always be satisfactory as some low-level +details get lost, and due to performance reasons. -Known issues: +(3) Possible issues and solutions The udev rule matches any touchpad device. If there are multiple ones, specify the device as described above. -If no evdev events are read, remove 50-synaptics.conf from -/usr/share/X11/xorg.conf.d and restart X. +If no evdev events are read, remove 50-synaptics.conf (or similar) +from /usr/share/X11/xorg.conf.d and restart X. Or at least temporarily +disable the device by running xinput set-prop 0. Use xinput list and xinput list-props to figure out the +values. diff --git a/src/plugins/generic/touchscreen/qtoucheventsenderqpa.cpp b/src/plugins/generic/touchscreen/qtoucheventsenderqpa.cpp index a62f9360c7..2a1f3d5ef3 100644 --- a/src/plugins/generic/touchscreen/qtoucheventsenderqpa.cpp +++ b/src/plugins/generic/touchscreen/qtoucheventsenderqpa.cpp @@ -47,8 +47,6 @@ QT_BEGIN_NAMESPACE -//#define POINT_DEBUG - QTouchEventSenderQPA::QTouchEventSenderQPA(const QString &spec) { m_forceToActiveWindow = spec.split(QLatin1Char(':')).contains(QLatin1String("force_window")); @@ -58,12 +56,22 @@ QTouchEventSenderQPA::QTouchEventSenderQPA(const QString &spec) QWindowSystemInterface::registerTouchDevice(m_device); } -void QTouchEventSenderQPA::touch_configure(int x_min, int x_max, int y_min, int y_max) +void QTouchEventSenderQPA::touch_configure(int x_min, int x_max, int y_min, int y_max, + int pressure_min, int pressure_max, + const QString &dev_name) { hw_range_x_min = x_min; hw_range_x_max = x_max; hw_range_y_min = y_min; hw_range_y_max = y_max; + + hw_pressure_min = pressure_min; + hw_pressure_max = pressure_max; + + m_device->setName(dev_name); + + if (hw_pressure_max > hw_pressure_min) + m_device->setCapabilities(m_device->capabilities() | QTouchDevice::Pressure); } void QTouchEventSenderQPA::touch_point(const QList &points) @@ -78,32 +86,28 @@ void QTouchEventSenderQPA::touch_point(const QListgeometry(); } -#ifdef POINT_DEBUG - qDebug() << "QPA: Mapping" << points.size() << "points to" << winRect << state; -#endif + const int hw_w = hw_range_x_max - hw_range_x_min; + const int hw_h = hw_range_y_max - hw_range_y_min; QList touchPoints = points; - // Translate the coordinates and set the normalized position. QPA expects - // 'area' to be in screen coordinates, while the device reports them in its - // own system with (0, 0) being the center point of the device. + // Map the coordinates based on the normalized position. QPA expects 'area' + // to be in screen coordinates. for (int i = 0; i < touchPoints.size(); ++i) { QWindowSystemInterface::TouchPoint &tp(touchPoints[i]); - const int hw_w = hw_range_x_max - hw_range_x_min; - const int hw_h = hw_range_y_max - hw_range_y_min; - - qreal nx = tp.normalPosition.x(); - qreal ny = tp.normalPosition.y(); - - // Generate a screen position that is always inside the active window or the default screen. - const int wx = winRect.left() + int(nx * winRect.width()); - const int wy = winRect.top() + int(ny * winRect.height()); + // Generate a screen position that is always inside the active window + // or the primary screen. + const int wx = winRect.left() + int(tp.normalPosition.x() * winRect.width()); + const int wy = winRect.top() + int(tp.normalPosition.y() * winRect.height()); const qreal sizeRatio = (winRect.width() + winRect.height()) / qreal(hw_w + hw_h); - tp.area = QRect(wx, wy, tp.area.width() * sizeRatio, tp.area.height() * sizeRatio); + tp.area = QRect(0, 0, tp.area.width() * sizeRatio, tp.area.height() * sizeRatio); + tp.area.moveCenter(QPoint(wx, wy)); -#ifdef POINT_DEBUG - qDebug() << " " << i << tp.area << tp.state << tp.id << tp.flags << tp.pressure; -#endif + // Calculate normalized pressure. + if (!hw_pressure_min && !hw_pressure_max) + tp.pressure = tp.state == Qt::TouchPointReleased ? 0 : 1; + else + tp.pressure = (tp.pressure - hw_pressure_min) / qreal(hw_pressure_max - hw_pressure_min); } QWindowSystemInterface::handleTouchEvent(0, m_device, touchPoints); diff --git a/src/plugins/generic/touchscreen/qtoucheventsenderqpa.h b/src/plugins/generic/touchscreen/qtoucheventsenderqpa.h index f0e923b3a0..387df7d3fa 100644 --- a/src/plugins/generic/touchscreen/qtoucheventsenderqpa.h +++ b/src/plugins/generic/touchscreen/qtoucheventsenderqpa.h @@ -54,7 +54,8 @@ class QTouchEventSenderQPA : public QTouchScreenObserver { public: QTouchEventSenderQPA(const QString &spec = QString()); - void touch_configure(int x_min, int x_max, int y_min, int y_max); + void touch_configure(int x_min, int x_max, int y_min, int y_max, + int pressure_min, int pressure_max, const QString &dev_name); void touch_point(const QList &points); private: @@ -63,6 +64,9 @@ private: int hw_range_x_max; int hw_range_y_min; int hw_range_y_max; + int hw_pressure_min; + int hw_pressure_max; + QString hw_dev_name; QTouchDevice *m_device; }; diff --git a/src/plugins/generic/touchscreen/qtouchscreen.cpp b/src/plugins/generic/touchscreen/qtouchscreen.cpp index 1142d0bf0b..b1eac6f359 100644 --- a/src/plugins/generic/touchscreen/qtouchscreen.cpp +++ b/src/plugins/generic/touchscreen/qtouchscreen.cpp @@ -41,28 +41,23 @@ #include "qtouchscreen.h" #include +#include #include -#include #include #include #include QT_BEGIN_NAMESPACE -//#define POINT_DEBUG - class QTouchScreenData { public: QTouchScreenData(QTouchScreenHandler *q_ptr, const QStringList &args); void processInputEvent(input_event *data); - - void dump(); + void assignIds(); QTouchScreenHandler *q; - QEvent::Type m_state; - QEvent::Type m_prevState; int m_lastEventType; QList m_touchPoints; @@ -71,17 +66,24 @@ public: int x; int y; int maj; + int pressure; Qt::TouchPointState state; QTouchEvent::TouchPoint::InfoFlags flags; - Contact() : trackingId(0), x(0), y(0), maj(1), state(Qt::TouchPointPressed), flags(0) { } + Contact() : trackingId(-1), + x(0), y(0), maj(1), pressure(0), + state(Qt::TouchPointPressed), flags(0) { } }; - QMap m_contacts, m_lastContacts; + QHash m_contacts, m_lastContacts; Contact m_currentData; + int findClosestContact(const QHash &contacts, int x, int y, int *dist); + int hw_range_x_min; int hw_range_x_max; int hw_range_y_min; int hw_range_y_max; + int hw_pressure_min; + int hw_pressure_max; QString hw_name; QList m_observers; @@ -89,11 +91,10 @@ public: QTouchScreenData::QTouchScreenData(QTouchScreenHandler *q_ptr, const QStringList &args) : q(q_ptr), - m_state(QEvent::TouchBegin), - m_prevState(m_state), m_lastEventType(-1), hw_range_x_min(0), hw_range_x_max(0), - hw_range_y_min(0), hw_range_y_max(0) + hw_range_y_min(0), hw_range_y_max(0), + hw_pressure_min(0), hw_pressure_max(0) { Q_UNUSED(args); } @@ -101,7 +102,7 @@ QTouchScreenData::QTouchScreenData(QTouchScreenHandler *q_ptr, const QStringList QTouchScreenHandler::QTouchScreenHandler(const QString &spec) : m_notify(0), m_fd(-1), d(0) { - setObjectName(QLatin1String("LinuxInputSubsystem Touch Handler")); + setObjectName(QLatin1String("Linux Touch Handler")); QString dev = QLatin1String("/dev/input/event5"); try_udev(&dev); @@ -126,16 +127,23 @@ QTouchScreenHandler::QTouchScreenHandler(const QString &spec) input_absinfo absInfo; memset(&absInfo, 0, sizeof(input_absinfo)); - if (!ioctl(m_fd, EVIOCGABS(ABS_X), &absInfo) >= 0) { + if (ioctl(m_fd, EVIOCGABS(ABS_MT_POSITION_X), &absInfo) >= 0) { qDebug("min X: %d max X: %d", absInfo.minimum, absInfo.maximum); d->hw_range_x_min = absInfo.minimum; d->hw_range_x_max = absInfo.maximum; } - if (!ioctl(m_fd, EVIOCGABS(ABS_Y), &absInfo) >= 0) { + if (ioctl(m_fd, EVIOCGABS(ABS_MT_POSITION_Y), &absInfo) >= 0) { qDebug("min Y: %d max Y: %d", absInfo.minimum, absInfo.maximum); d->hw_range_y_min = absInfo.minimum; d->hw_range_y_max = absInfo.maximum; } + if (ioctl(m_fd, EVIOCGABS(ABS_PRESSURE), &absInfo) >= 0) { + qDebug("min pressure: %d max pressure: %d", absInfo.minimum, absInfo.maximum); + if (absInfo.maximum > absInfo.minimum) { + d->hw_pressure_min = absInfo.minimum; + d->hw_pressure_max = absInfo.maximum; + } + } char name[1024]; if (ioctl(m_fd, EVIOCGNAME(sizeof(name) - 1), name) >= 0) { d->hw_name = QString::fromLocal8Bit(name); @@ -157,11 +165,14 @@ void QTouchScreenHandler::addObserver(QTouchScreenObserver *observer) return; d->m_observers.append(observer); observer->touch_configure(d->hw_range_x_min, d->hw_range_x_max, - d->hw_range_y_min, d->hw_range_y_max); + d->hw_range_y_min, d->hw_range_y_max, + d->hw_pressure_min, d->hw_pressure_max, + d->hw_name); } void QTouchScreenHandler::try_udev(QString *path) { + *path = QString(); udev *u = udev_new(); udev_enumerate *ue = udev_enumerate_new(u); udev_enumerate_add_match_subsystem(ue, "input"); @@ -171,9 +182,10 @@ void QTouchScreenHandler::try_udev(QString *path) udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(ue)) { const char *syspath = udev_list_entry_get_name(entry); udev_device *udevice = udev_device_new_from_syspath(u, syspath); - *path = QString::fromLocal8Bit(udev_device_get_devnode(udevice)); - qDebug("from udev: %s", qPrintable(*path)); + QString candidate = QString::fromLocal8Bit(udev_device_get_devnode(udevice)); udev_device_unref(udevice); + if (path->isEmpty() && candidate.startsWith("/dev/input/event")) + *path = candidate; } udev_enumerate_unref(ue); udev_unref(u); @@ -213,137 +225,152 @@ void QTouchScreenData::processInputEvent(input_event *data) { if (data->type == EV_ABS) { - if (data->code == ABS_MT_POSITION_X) { - m_currentData.x = data->value; - } else if (data->code == ABS_MT_POSITION_Y) { - m_currentData.y = data->value; - } else if (data->code == ABS_MT_TRACKING_ID) { - m_currentData.trackingId = data->value; - if (m_contacts.isEmpty()) - m_currentData.flags |= QTouchEvent::TouchPoint::Primary; - } else if (data->code == ABS_MT_TOUCH_MAJOR) { - m_currentData.maj = data->value; - if (data->value == 0) - m_currentData.state = Qt::TouchPointReleased; - } + if (data->code == ABS_MT_POSITION_X) { + m_currentData.x = qBound(hw_range_x_min, data->value, hw_range_x_max); + } else if (data->code == ABS_MT_POSITION_Y) { + m_currentData.y = qBound(hw_range_y_min, data->value, hw_range_y_max); + } else if (data->code == ABS_MT_TRACKING_ID) { + m_currentData.trackingId = data->value; + } else if (data->code == ABS_MT_TOUCH_MAJOR) { + m_currentData.maj = data->value; + if (data->value == 0) + m_currentData.state = Qt::TouchPointReleased; + } else if (data->code == ABS_PRESSURE) { + m_currentData.pressure = qBound(hw_pressure_min, data->value, hw_pressure_max); + } } else if (data->type == EV_SYN && data->code == SYN_MT_REPORT && m_lastEventType != EV_SYN) { - m_contacts.insert(m_currentData.trackingId, m_currentData); + // If there is no tracking id, one will be generated later. + // Until that use a temporary key. + int key = m_currentData.trackingId; + if (key == -1) + key = m_contacts.count(); + + // Mark the first point as primary. + if (m_contacts.isEmpty()) + m_currentData.flags |= QTouchEvent::TouchPoint::Primary; + + m_contacts.insert(key, m_currentData); m_currentData = Contact(); } else if (data->type == EV_SYN && data->code == SYN_REPORT) { + // Ensure valid IDs even when the driver does not report ABS_MT_TRACKING_ID. + if (!m_contacts.isEmpty() && m_contacts.constBegin().value().trackingId == -1) + assignIds(); + m_touchPoints.clear(); - for (QMap::iterator it = m_contacts.begin(), ite = m_contacts.end(); - it != ite; ++it) { + Qt::TouchPointStates combinedStates; + QMutableHashIterator it(m_contacts); + while (it.hasNext()) { + it.next(); QWindowSystemInterface::TouchPoint tp; - tp.id = it->trackingId; - tp.flags = it->flags; - tp.pressure = it->state == Qt::TouchPointReleased ? 0 : 1; + Contact &contact(it.value()); + tp.id = contact.trackingId; + tp.flags = contact.flags; - if (m_lastContacts.contains(it->trackingId)) { - const Contact &prev(m_lastContacts.value(it->trackingId)); - if (it->state == Qt::TouchPointReleased) { + if (m_lastContacts.contains(contact.trackingId)) { + const Contact &prev(m_lastContacts.value(contact.trackingId)); + if (contact.state == Qt::TouchPointReleased) { // Copy over the previous values for released points, just in case. - it->x = prev.x; - it->y = prev.y; - it->maj = prev.maj; + contact.x = prev.x; + contact.y = prev.y; + contact.maj = prev.maj; } else { - it->state = (prev.x == it->x && prev.y == it->y) ? Qt::TouchPointStationary : Qt::TouchPointMoved; + contact.state = (prev.x == contact.x && prev.y == contact.y) + ? Qt::TouchPointStationary : Qt::TouchPointMoved; } } - tp.state = it->state; - tp.area = QRectF(it->x, it->y, it->maj, it->maj); + // Avoid reporting a contact in released state more than once. + if (contact.state == Qt::TouchPointReleased + && !m_lastContacts.contains(contact.trackingId)) { + it.remove(); + continue; + } + + tp.state = contact.state; + combinedStates |= tp.state; + + // Store the HW coordinates. Observers can then map it to screen space or something else. + tp.area = QRectF(0, 0, contact.maj, contact.maj); + tp.area.moveCenter(QPoint(contact.x, contact.y)); + tp.pressure = contact.pressure; - // Translate so that (0, 0) is the top-left corner. - const int hw_x = qBound(hw_range_x_min, int(tp.area.left()), hw_range_x_max) - hw_range_x_min; - const int hw_y = qBound(hw_range_y_min, int(tp.area.top()), hw_range_y_max) - hw_range_y_min; // Get a normalized position in range 0..1. - const int hw_w = hw_range_x_max - hw_range_x_min; - const int hw_h = hw_range_y_max - hw_range_y_min; - tp.normalPosition = QPointF(hw_x / qreal(hw_w), - hw_y / qreal(hw_h)); + tp.normalPosition = QPointF((contact.x - hw_range_x_min) / qreal(hw_range_x_max - hw_range_x_min), + (contact.y - hw_range_y_min) / qreal(hw_range_y_max - hw_range_y_min)); m_touchPoints.append(tp); - } - if (m_contacts.isEmpty()) - m_state = QEvent::TouchEnd; + if (contact.state == Qt::TouchPointReleased) + it.remove(); + } m_lastContacts = m_contacts; m_contacts.clear(); - // No need to deliver if all points are stationary. - bool skip = false; - if (m_state == QEvent::TouchUpdate) { - skip = true; - for (int i = 0; i < m_touchPoints.count(); ++i) - if (m_touchPoints.at(i).state != Qt::TouchPointStationary) { - skip = false; - break; - } - } - -#ifdef POINT_DEBUG - dump(); -#endif - - if (!skip && !(m_state == m_prevState && m_state == QEvent::TouchEnd)) + if (!m_touchPoints.isEmpty() && combinedStates != Qt::TouchPointStationary) { for (int i = 0; i < m_observers.count(); ++i) m_observers.at(i)->touch_point(m_touchPoints); - - m_prevState = m_state; - if (m_state == QEvent::TouchBegin) - m_state = QEvent::TouchUpdate; - else if (m_state == QEvent::TouchEnd) - m_state = QEvent::TouchBegin; + } } m_lastEventType = data->type; } -void QTouchScreenData::dump() +int QTouchScreenData::findClosestContact(const QHash &contacts, int x, int y, int *dist) { - const char *eventType; - switch (m_state) { - case QEvent::TouchBegin: - eventType = "TouchBegin"; - break; - case QEvent::TouchUpdate: - eventType = "TouchUpdate"; - break; - case QEvent::TouchEnd: - eventType = "TouchEnd"; - break; - default: - eventType = "unknown"; - break; + int minDist = -1, id = -1; + for (QHash::const_iterator it = contacts.constBegin(), ite = contacts.constEnd(); + it != ite; ++it) { + const Contact &contact(it.value()); + int dx = x - contact.x; + int dy = y - contact.y; + int dist = dx * dx + dy * dy; + if (minDist == -1 || dist < minDist) { + minDist = dist; + id = contact.trackingId; + } } - qDebug() << "touch event" << eventType; - foreach (const QWindowSystemInterface::TouchPoint &tp, m_touchPoints) { - const char *pointState; - switch (tp.state) { - case Qt::TouchPointPressed: - pointState = "pressed"; - break; - case Qt::TouchPointMoved: - pointState = "moved"; - break; - case Qt::TouchPointStationary: - pointState = "stationary"; - break; - case Qt::TouchPointReleased: - pointState = "released"; - break; - default: - pointState = "unknown"; - break; + if (dist) + *dist = minDist; + return id; +} + +void QTouchScreenData::assignIds() +{ + QHash candidates = m_lastContacts, pending = m_contacts, newContacts; + int maxId = -1; + QHash::iterator it, ite, bestMatch; + while (!pending.isEmpty() && !candidates.isEmpty()) { + int bestDist = -1, bestId; + for (it = pending.begin(), ite = pending.end(); it != ite; ++it) { + int dist; + int id = findClosestContact(candidates, it->x, it->y, &dist); + if (id >= 0 && (bestDist == -1 || dist < bestDist)) { + bestDist = dist; + bestId = id; + bestMatch = it; + } + } + if (bestDist >= 0) { + bestMatch->trackingId = bestId; + newContacts.insert(bestId, *bestMatch); + candidates.remove(bestId); + pending.erase(bestMatch); + if (bestId > maxId) + maxId = bestId; + } + } + if (candidates.isEmpty()) { + for (it = pending.begin(), ite = pending.end(); it != ite; ++it) { + it->trackingId = ++maxId; + newContacts.insert(it->trackingId, *it); } - qDebug() << " " << tp.id << tp.area << pointState << tp.normalPosition - << tp.pressure << tp.flags << tp.area.center(); } + m_contacts = newContacts; } diff --git a/src/plugins/generic/touchscreen/qtouchscreen.h b/src/plugins/generic/touchscreen/qtouchscreen.h index 6127cdba32..bcb27d9cdb 100644 --- a/src/plugins/generic/touchscreen/qtouchscreen.h +++ b/src/plugins/generic/touchscreen/qtouchscreen.h @@ -58,7 +58,8 @@ class QTouchScreenData; class QTouchScreenObserver { public: - virtual void touch_configure(int x_min, int x_max, int y_min, int y_max) = 0; + virtual void touch_configure(int x_min, int x_max, int y_min, int y_max, + int pressure_min, int pressure_max, const QString &dev_name) = 0; virtual void touch_point(const QList &points) = 0; }; -- cgit v1.2.3