Update the touchscreen plug-in.

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>
This commit is contained in:
Laszlo Agocs 2011-12-17 21:15:50 +02:00 committed by Qt by Nokia
parent 87679491cb
commit 2edada763a
5 changed files with 191 additions and 151 deletions

View 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.

View 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);

View 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;
};

View File

@ -41,28 +41,23 @@
#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) {
m_touchPoints.clear();
for (QMap<int, Contact>::iterator it = m_contacts.begin(), ite = m_contacts.end();
it != ite; ++it) {
QWindowSystemInterface::TouchPoint tp;
tp.id = it->trackingId;
tp.flags = it->flags;
tp.pressure = it->state == Qt::TouchPointReleased ? 0 : 1;
// 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();
if (m_lastContacts.contains(it->trackingId)) {
const Contact &prev(m_lastContacts.value(it->trackingId));
if (it->state == Qt::TouchPointReleased) {
m_touchPoints.clear();
Qt::TouchPointStates combinedStates;
QMutableHashIterator<int, Contact> it(m_contacts);
while (it.hasNext()) {
it.next();
QWindowSystemInterface::TouchPoint tp;
Contact &contact(it.value());
tp.id = contact.trackingId;
tp.flags = contact.flags;
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;
}
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;
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() << " " << tp.id << tp.area << pointState << tp.normalPosition
<< tp.pressure << tp.flags << tp.area.center();
}
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);
}
}
m_contacts = newContacts;
}

View 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;
};