Update the touchscreen plug-in.
authorLaszlo Agocs <laszlo.p.agocs@nokia.com>
Sat, 17 Dec 2011 19:15:50 +0000 (21:15 +0200)
committerQt by Nokia <qt-info@nokia.com>
Mon, 19 Dec 2011 16:29:10 +0000 (17:29 +0100)
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 <jorgen.lind@nokia.com>
src/plugins/generic/touchscreen/README
src/plugins/generic/touchscreen/qtoucheventsenderqpa.cpp
src/plugins/generic/touchscreen/qtoucheventsenderqpa.h
src/plugins/generic/touchscreen/qtouchscreen.cpp
src/plugins/generic/touchscreen/qtouchscreen.h

index bed9016..6bd244d 100644 (file)
@@ -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 <device> <device enabled
+property> 0. Use xinput list and xinput list-props to figure out the
+values.
index a62f936..2a1f3d5 100644 (file)
@@ -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<QWindowSystemInterface::TouchPoint> &points)
@@ -78,32 +86,28 @@ void QTouchEventSenderQPA::touch_point(const QList<QWindowSystemInterface::Touch
         winRect = QGuiApplication::primaryScreen()->geometry();
     }
 
-#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<QWindowSystemInterface::TouchPoint> 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);
index f0e923b..387df7d 100644 (file)
@@ -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<QWindowSystemInterface::TouchPoint> &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;
 };
 
index 1142d0b..b1eac6f 100644 (file)
 
 #include "qtouchscreen.h"
 #include <QStringList>
+#include <QHash>
 #include <QSocketNotifier>
-#include <QDebug>
 #include <QtCore/private/qcore_unix_p.h>
 #include <linux/input.h>
 #include <libudev.h>
 
 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<QWindowSystemInterface::TouchPoint> 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<int, Contact> m_contacts, m_lastContacts;
+    QHash<int, Contact> m_contacts, m_lastContacts;
     Contact m_currentData;
 
+    int findClosestContact(const QHash<int, Contact> &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<QTouchScreenObserver *> 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<int, Contact>::iterator it = m_contacts.begin(), ite = m_contacts.end();
-             it != ite; ++it) {
+        Qt::TouchPointStates combinedStates;
+        QMutableHashIterator<int, Contact> 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<int, Contact> &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<int, Contact>::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<int, Contact> candidates = m_lastContacts, pending = m_contacts, newContacts;
+    int maxId = -1;
+    QHash<int, Contact>::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;
 }
 
 
index 6127cdb..bcb27d9 100644 (file)
@@ -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<QWindowSystemInterface::TouchPoint> &points) = 0;
 };