1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the plugins module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU Lesser General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU Lesser
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
22 ** packaging of this file. Please review the following information to
23 ** ensure the GNU Lesser General Public License version 3 requirements
24 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25 **
26 ** GNU General Public License Usage
27 ** Alternatively, this file may be used under the terms of the GNU
28 ** General Public License version 2.0 or (at your option) the GNU General
29 ** Public license version 3 or any later version approved by the KDE Free
30 ** Qt Foundation. The licenses are as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32 ** included in the packaging of this file. Please review the following
33 ** information to ensure the GNU General Public License requirements will
34 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35 ** https://www.gnu.org/licenses/gpl-3.0.html.
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41 #include "qevdevtouchhandler_p.h"
42 #include "qtouchoutputmapping_p.h"
43 #include <QStringList>
44 #include <QHash>
45 #include <QSocketNotifier>
46 #include <QGuiApplication>
47 #include <QTouchDevice>
48 #include <QLoggingCategory>
49 #include <QtCore/private/qcore_unix_p.h>
50 #include <QtGui/private/qhighdpiscaling_p.h>
51 #include <QtGui/private/qguiapplication_p.h>
52
53 #include <mutex>
54
55 #include <linux/input.h>
56
57 #ifndef input_event_sec
58 #define input_event_sec time.tv_sec
59 #endif
60
61 #ifndef input_event_usec
62 #define input_event_usec time.tv_usec
63 #endif
64
65 #include <math.h>
66
67 #if QT_CONFIG(mtdev)
68 extern "C" {
69 #include <mtdev.h>
70 }
71 #endif
72
73 QT_BEGIN_NAMESPACE
74
75 Q_LOGGING_CATEGORY(qLcEvdevTouch, "qt.qpa.input")
76 Q_LOGGING_CATEGORY(qLcEvents, "qt.qpa.input.events")
77
78 /* android (and perhaps some other linux-derived stuff) don't define everything
79 * in linux/input.h, so we'll need to do that ourselves.
80 */
81 #ifndef ABS_MT_TOUCH_MAJOR
82 #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
83 #endif
84 #ifndef ABS_MT_POSITION_X
85 #define ABS_MT_POSITION_X 0x35 /* Center X ellipse position */
86 #endif
87 #ifndef ABS_MT_POSITION_Y
88 #define ABS_MT_POSITION_Y 0x36 /* Center Y ellipse position */
89 #endif
90 #ifndef ABS_MT_SLOT
91 #define ABS_MT_SLOT 0x2f
92 #endif
93 #ifndef ABS_CNT
94 #define ABS_CNT (ABS_MAX+1)
95 #endif
96 #ifndef ABS_MT_TRACKING_ID
97 #define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
98 #endif
99 #ifndef ABS_MT_PRESSURE
100 #define ABS_MT_PRESSURE 0x3a
101 #endif
102 #ifndef SYN_MT_REPORT
103 #define SYN_MT_REPORT 2
104 #endif
105
106 class QEvdevTouchScreenData
107 {
108 public:
109 QEvdevTouchScreenData(QEvdevTouchScreenHandler *q_ptr, const QStringList &args);
110
111 void processInputEvent(input_event *data);
112 void assignIds();
113
114 QEvdevTouchScreenHandler *q;
115 int m_lastEventType;
116 QList<QWindowSystemInterface::TouchPoint> m_touchPoints;
117 QList<QWindowSystemInterface::TouchPoint> m_lastTouchPoints;
118
119 struct Contact {
120 int trackingId = -1;
121 int x = 0;
122 int y = 0;
123 int maj = -1;
124 int pressure = 0;
125 Qt::TouchPointState state = Qt::TouchPointPressed;
126 QTouchEvent::TouchPoint::InfoFlags flags;
127 };
128 QHash<int, Contact> m_contacts; // The key is a tracking id for type A, slot number for type B.
129 QHash<int, Contact> m_lastContacts;
130 Contact m_currentData;
131 int m_currentSlot;
132
133 double m_timeStamp;
134 double m_lastTimeStamp;
135
136 int findClosestContact(const QHash<int, Contact> &contacts, int x, int y, int *dist);
137 void addTouchPoint(const Contact &contact, Qt::TouchPointStates *combinedStates);
138 void reportPoints();
139 void loadMultiScreenMappings();
140
141 QRect screenGeometry() const;
142
143 int hw_range_x_min;
144 int hw_range_x_max;
145 int hw_range_y_min;
146 int hw_range_y_max;
147 int hw_pressure_min;
148 int hw_pressure_max;
149 QString hw_name;
150 QString deviceNode;
151 bool m_forceToActiveWindow;
152 bool m_typeB;
153 QTransform m_rotate;
154 bool m_singleTouch;
155 QString m_screenName;
156 mutable QPointer<QScreen> m_screen;
157
158 // Touch filtering and prediction are part of the same thing. The default
159 // prediction is 0ms, but sensible results can be achieved by setting it
160 // to, for instance, 16ms.
161 // For filtering to work well, the QPA plugin should provide a dead-steady
162 // implementation of QPlatformWindow::requestUpdate().
163 bool m_filtered;
164 int m_prediction;
165
166 // When filtering is enabled, protect the access to current and last
167 // timeStamp and touchPoints, as these are being read on the gui thread.
168 QMutex m_mutex;
169 };
170
QEvdevTouchScreenData(QEvdevTouchScreenHandler * q_ptr,const QStringList & args)171 QEvdevTouchScreenData::QEvdevTouchScreenData(QEvdevTouchScreenHandler *q_ptr, const QStringList &args)
172 : q(q_ptr),
173 m_lastEventType(-1),
174 m_currentSlot(0),
175 m_timeStamp(0), m_lastTimeStamp(0),
176 hw_range_x_min(0), hw_range_x_max(0),
177 hw_range_y_min(0), hw_range_y_max(0),
178 hw_pressure_min(0), hw_pressure_max(0),
179 m_forceToActiveWindow(false), m_typeB(false), m_singleTouch(false),
180 m_filtered(false), m_prediction(0)
181 {
182 for (const QString &arg : args) {
183 if (arg == QStringLiteral("force_window"))
184 m_forceToActiveWindow = true;
185 else if (arg == QStringLiteral("filtered"))
186 m_filtered = true;
187 else if (arg.startsWith(QStringLiteral("prediction=")))
188 m_prediction = arg.mid(11).toInt();
189 }
190 }
191
192 #define LONG_BITS (sizeof(long) << 3)
193 #define NUM_LONGS(bits) (((bits) + LONG_BITS - 1) / LONG_BITS)
194
195 #if !QT_CONFIG(mtdev)
testBit(long bit,const long * array)196 static inline bool testBit(long bit, const long *array)
197 {
198 return (array[bit / LONG_BITS] >> bit % LONG_BITS) & 1;
199 }
200 #endif
201
QEvdevTouchScreenHandler(const QString & device,const QString & spec,QObject * parent)202 QEvdevTouchScreenHandler::QEvdevTouchScreenHandler(const QString &device, const QString &spec, QObject *parent)
203 : QObject(parent), m_notify(nullptr), m_fd(-1), d(nullptr), m_device(nullptr)
204 #if QT_CONFIG(mtdev)
205 , m_mtdev(nullptr)
206 #endif
207 {
208 setObjectName(QLatin1String("Evdev Touch Handler"));
209
210 const QStringList args = spec.split(QLatin1Char(':'));
211 int rotationAngle = 0;
212 bool invertx = false;
213 bool inverty = false;
214 for (int i = 0; i < args.count(); ++i) {
215 if (args.at(i).startsWith(QLatin1String("rotate"))) {
216 QString rotateArg = args.at(i).section(QLatin1Char('='), 1, 1);
217 bool ok;
218 uint argValue = rotateArg.toUInt(&ok);
219 if (ok) {
220 switch (argValue) {
221 case 90:
222 case 180:
223 case 270:
224 rotationAngle = argValue;
225 default:
226 break;
227 }
228 }
229 } else if (args.at(i) == QLatin1String("invertx")) {
230 invertx = true;
231 } else if (args.at(i) == QLatin1String("inverty")) {
232 inverty = true;
233 }
234 }
235
236 qCDebug(qLcEvdevTouch, "evdevtouch: Using device %ls", qUtf16Printable(device));
237
238 m_fd = QT_OPEN(device.toLocal8Bit().constData(), O_RDONLY | O_NDELAY, 0);
239
240 if (m_fd >= 0) {
241 m_notify = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
242 connect(m_notify, &QSocketNotifier::activated, this, &QEvdevTouchScreenHandler::readData);
243 } else {
244 qErrnoWarning("evdevtouch: Cannot open input device %ls", qUtf16Printable(device));
245 return;
246 }
247
248 #if QT_CONFIG(mtdev)
249 m_mtdev = static_cast<mtdev *>(calloc(1, sizeof(mtdev)));
250 int mtdeverr = mtdev_open(m_mtdev, m_fd);
251 if (mtdeverr) {
252 qWarning("evdevtouch: mtdev_open failed: %d", mtdeverr);
253 QT_CLOSE(m_fd);
254 free(m_mtdev);
255 return;
256 }
257 #endif
258
259 d = new QEvdevTouchScreenData(this, args);
260
261 #if QT_CONFIG(mtdev)
262 const char *mtdevStr = "(mtdev)";
263 d->m_typeB = true;
264 #else
265 const char *mtdevStr = "";
266 long absbits[NUM_LONGS(ABS_CNT)];
267 if (ioctl(m_fd, EVIOCGBIT(EV_ABS, sizeof(absbits)), absbits) >= 0) {
268 d->m_typeB = testBit(ABS_MT_SLOT, absbits);
269 d->m_singleTouch = !testBit(ABS_MT_POSITION_X, absbits);
270 }
271 #endif
272
273 d->deviceNode = device;
274 qCDebug(qLcEvdevTouch,
275 "evdevtouch: %ls: Protocol type %c %s (%s), filtered=%s",
276 qUtf16Printable(d->deviceNode),
277 d->m_typeB ? 'B' : 'A', mtdevStr,
278 d->m_singleTouch ? "single" : "multi",
279 d->m_filtered ? "yes" : "no");
280 if (d->m_filtered)
281 qCDebug(qLcEvdevTouch, " - prediction=%d", d->m_prediction);
282
283 input_absinfo absInfo;
284 memset(&absInfo, 0, sizeof(input_absinfo));
285 bool has_x_range = false, has_y_range = false;
286
287 if (ioctl(m_fd, EVIOCGABS((d->m_singleTouch ? ABS_X : ABS_MT_POSITION_X)), &absInfo) >= 0) {
288 qCDebug(qLcEvdevTouch, "evdevtouch: %ls: min X: %d max X: %d", qUtf16Printable(device),
289 absInfo.minimum, absInfo.maximum);
290 d->hw_range_x_min = absInfo.minimum;
291 d->hw_range_x_max = absInfo.maximum;
292 has_x_range = true;
293 }
294
295 if (ioctl(m_fd, EVIOCGABS((d->m_singleTouch ? ABS_Y : ABS_MT_POSITION_Y)), &absInfo) >= 0) {
296 qCDebug(qLcEvdevTouch, "evdevtouch: %ls: min Y: %d max Y: %d", qUtf16Printable(device),
297 absInfo.minimum, absInfo.maximum);
298 d->hw_range_y_min = absInfo.minimum;
299 d->hw_range_y_max = absInfo.maximum;
300 has_y_range = true;
301 }
302
303 if (!has_x_range || !has_y_range)
304 qWarning("evdevtouch: %ls: Invalid ABS limits, behavior unspecified", qUtf16Printable(device));
305
306 if (ioctl(m_fd, EVIOCGABS(ABS_PRESSURE), &absInfo) >= 0) {
307 qCDebug(qLcEvdevTouch, "evdevtouch: %ls: min pressure: %d max pressure: %d", qUtf16Printable(device),
308 absInfo.minimum, absInfo.maximum);
309 if (absInfo.maximum > absInfo.minimum) {
310 d->hw_pressure_min = absInfo.minimum;
311 d->hw_pressure_max = absInfo.maximum;
312 }
313 }
314
315 char name[1024];
316 if (ioctl(m_fd, EVIOCGNAME(sizeof(name) - 1), name) >= 0) {
317 d->hw_name = QString::fromLocal8Bit(name);
318 qCDebug(qLcEvdevTouch, "evdevtouch: %ls: device name: %s", qUtf16Printable(device), name);
319 }
320
321 // Fix up the coordinate ranges for am335x in case the kernel driver does not have them fixed.
322 if (d->hw_name == QLatin1String("ti-tsc")) {
323 if (d->hw_range_x_min == 0 && d->hw_range_x_max == 4095) {
324 d->hw_range_x_min = 165;
325 d->hw_range_x_max = 4016;
326 }
327 if (d->hw_range_y_min == 0 && d->hw_range_y_max == 4095) {
328 d->hw_range_y_min = 220;
329 d->hw_range_y_max = 3907;
330 }
331 qCDebug(qLcEvdevTouch, "evdevtouch: found ti-tsc, overriding: min X: %d max X: %d min Y: %d max Y: %d",
332 d->hw_range_x_min, d->hw_range_x_max, d->hw_range_y_min, d->hw_range_y_max);
333 }
334
335 bool grabSuccess = !ioctl(m_fd, EVIOCGRAB, (void *) 1);
336 if (grabSuccess)
337 ioctl(m_fd, EVIOCGRAB, (void *) 0);
338 else
339 qWarning("evdevtouch: The device is grabbed by another process. No events will be read.");
340
341 if (rotationAngle)
342 d->m_rotate = QTransform::fromTranslate(0.5, 0.5).rotate(rotationAngle).translate(-0.5, -0.5);
343
344 if (invertx)
345 d->m_rotate *= QTransform::fromTranslate(0.5, 0.5).scale(-1.0, 1.0).translate(-0.5, -0.5);
346
347 if (inverty)
348 d->m_rotate *= QTransform::fromTranslate(0.5, 0.5).scale(1.0, -1.0).translate(-0.5, -0.5);
349
350 QTouchOutputMapping mapping;
351 if (mapping.load()) {
352 d->m_screenName = mapping.screenNameForDeviceNode(d->deviceNode);
353 if (!d->m_screenName.isEmpty())
354 qCDebug(qLcEvdevTouch, "evdevtouch: Mapping device %ls to screen %ls",
355 qUtf16Printable(d->deviceNode), qUtf16Printable(d->m_screenName));
356 }
357
358 registerTouchDevice();
359 }
360
~QEvdevTouchScreenHandler()361 QEvdevTouchScreenHandler::~QEvdevTouchScreenHandler()
362 {
363 #if QT_CONFIG(mtdev)
364 if (m_mtdev) {
365 mtdev_close(m_mtdev);
366 free(m_mtdev);
367 }
368 #endif
369
370 if (m_fd >= 0)
371 QT_CLOSE(m_fd);
372
373 delete d;
374
375 unregisterTouchDevice();
376 }
377
isFiltered() const378 bool QEvdevTouchScreenHandler::isFiltered() const
379 {
380 return d && d->m_filtered;
381 }
382
touchDevice() const383 QTouchDevice *QEvdevTouchScreenHandler::touchDevice() const
384 {
385 return m_device;
386 }
387
readData()388 void QEvdevTouchScreenHandler::readData()
389 {
390 ::input_event buffer[32];
391 int events = 0;
392
393 #if QT_CONFIG(mtdev)
394 forever {
395 do {
396 events = mtdev_get(m_mtdev, m_fd, buffer, sizeof(buffer) / sizeof(::input_event));
397 // keep trying mtdev_get if we get interrupted. note that we do not
398 // (and should not) handle EAGAIN; EAGAIN means that reading would
399 // block and we'll get back here later to try again anyway.
400 } while (events == -1 && errno == EINTR);
401
402 // 0 events is EOF, -1 means error, handle both in the same place
403 if (events <= 0)
404 goto err;
405
406 // process our shiny new events
407 for (int i = 0; i < events; ++i)
408 d->processInputEvent(&buffer[i]);
409
410 // and try to get more
411 }
412 #else
413 int n = 0;
414 for (; ;) {
415 events = QT_READ(m_fd, reinterpret_cast<char*>(buffer) + n, sizeof(buffer) - n);
416 if (events <= 0)
417 goto err;
418 n += events;
419 if (n % sizeof(::input_event) == 0)
420 break;
421 }
422
423 n /= sizeof(::input_event);
424
425 for (int i = 0; i < n; ++i)
426 d->processInputEvent(&buffer[i]);
427 #endif
428 return;
429
430 err:
431 if (!events) {
432 qWarning("evdevtouch: Got EOF from input device");
433 return;
434 } else if (events < 0) {
435 if (errno != EINTR && errno != EAGAIN) {
436 qErrnoWarning("evdevtouch: Could not read from input device");
437 if (errno == ENODEV) { // device got disconnected -> stop reading
438 delete m_notify;
439 m_notify = nullptr;
440
441 QT_CLOSE(m_fd);
442 m_fd = -1;
443
444 unregisterTouchDevice();
445 }
446 return;
447 }
448 }
449 }
450
registerTouchDevice()451 void QEvdevTouchScreenHandler::registerTouchDevice()
452 {
453 if (m_device)
454 return;
455
456 m_device = new QTouchDevice;
457 m_device->setName(d->hw_name);
458 m_device->setType(QTouchDevice::TouchScreen);
459 m_device->setCapabilities(QTouchDevice::Position | QTouchDevice::Area);
460 if (d->hw_pressure_max > d->hw_pressure_min)
461 m_device->setCapabilities(m_device->capabilities() | QTouchDevice::Pressure);
462
463 QWindowSystemInterface::registerTouchDevice(m_device);
464 }
465
unregisterTouchDevice()466 void QEvdevTouchScreenHandler::unregisterTouchDevice()
467 {
468 if (!m_device)
469 return;
470
471 // At app exit the cleanup may have already been done, avoid
472 // double delete by checking the list first.
473 if (QWindowSystemInterface::isTouchDeviceRegistered(m_device)) {
474 QWindowSystemInterface::unregisterTouchDevice(m_device);
475 delete m_device;
476 }
477
478 m_device = nullptr;
479 }
480
addTouchPoint(const Contact & contact,Qt::TouchPointStates * combinedStates)481 void QEvdevTouchScreenData::addTouchPoint(const Contact &contact, Qt::TouchPointStates *combinedStates)
482 {
483 QWindowSystemInterface::TouchPoint tp;
484 tp.id = contact.trackingId;
485 tp.flags = contact.flags;
486 tp.state = contact.state;
487 *combinedStates |= tp.state;
488
489 // Store the HW coordinates for now, will be updated later.
490 tp.area = QRectF(0, 0, contact.maj, contact.maj);
491 tp.area.moveCenter(QPoint(contact.x, contact.y));
492 tp.pressure = contact.pressure;
493
494 // Get a normalized position in range 0..1.
495 tp.normalPosition = QPointF((contact.x - hw_range_x_min) / qreal(hw_range_x_max - hw_range_x_min),
496 (contact.y - hw_range_y_min) / qreal(hw_range_y_max - hw_range_y_min));
497
498 if (!m_rotate.isIdentity())
499 tp.normalPosition = m_rotate.map(tp.normalPosition);
500
501 tp.rawPositions.append(QPointF(contact.x, contact.y));
502
503 m_touchPoints.append(tp);
504 }
505
processInputEvent(input_event * data)506 void QEvdevTouchScreenData::processInputEvent(input_event *data)
507 {
508 if (data->type == EV_ABS) {
509
510 if (data->code == ABS_MT_POSITION_X || (m_singleTouch && data->code == ABS_X)) {
511 m_currentData.x = qBound(hw_range_x_min, data->value, hw_range_x_max);
512 if (m_singleTouch)
513 m_contacts[m_currentSlot].x = m_currentData.x;
514 if (m_typeB) {
515 m_contacts[m_currentSlot].x = m_currentData.x;
516 if (m_contacts[m_currentSlot].state == Qt::TouchPointStationary)
517 m_contacts[m_currentSlot].state = Qt::TouchPointMoved;
518 }
519 } else if (data->code == ABS_MT_POSITION_Y || (m_singleTouch && data->code == ABS_Y)) {
520 m_currentData.y = qBound(hw_range_y_min, data->value, hw_range_y_max);
521 if (m_singleTouch)
522 m_contacts[m_currentSlot].y = m_currentData.y;
523 if (m_typeB) {
524 m_contacts[m_currentSlot].y = m_currentData.y;
525 if (m_contacts[m_currentSlot].state == Qt::TouchPointStationary)
526 m_contacts[m_currentSlot].state = Qt::TouchPointMoved;
527 }
528 } else if (data->code == ABS_MT_TRACKING_ID) {
529 m_currentData.trackingId = data->value;
530 if (m_typeB) {
531 if (m_currentData.trackingId == -1) {
532 m_contacts[m_currentSlot].state = Qt::TouchPointReleased;
533 } else {
534 m_contacts[m_currentSlot].state = Qt::TouchPointPressed;
535 m_contacts[m_currentSlot].trackingId = m_currentData.trackingId;
536 }
537 }
538 } else if (data->code == ABS_MT_TOUCH_MAJOR) {
539 m_currentData.maj = data->value;
540 if (data->value == 0)
541 m_currentData.state = Qt::TouchPointReleased;
542 if (m_typeB)
543 m_contacts[m_currentSlot].maj = m_currentData.maj;
544 } else if (data->code == ABS_PRESSURE || data->code == ABS_MT_PRESSURE) {
545 if (Q_UNLIKELY(qLcEvents().isDebugEnabled()))
546 qCDebug(qLcEvents, "EV_ABS code 0x%x: pressure %d; bounding to [%d,%d]",
547 data->code, data->value, hw_pressure_min, hw_pressure_max);
548 m_currentData.pressure = qBound(hw_pressure_min, data->value, hw_pressure_max);
549 if (m_typeB || m_singleTouch)
550 m_contacts[m_currentSlot].pressure = m_currentData.pressure;
551 } else if (data->code == ABS_MT_SLOT) {
552 m_currentSlot = data->value;
553 }
554
555 } else if (data->type == EV_KEY && !m_typeB) {
556 if (data->code == BTN_TOUCH && data->value == 0)
557 m_contacts[m_currentSlot].state = Qt::TouchPointReleased;
558 } else if (data->type == EV_SYN && data->code == SYN_MT_REPORT && m_lastEventType != EV_SYN) {
559
560 // If there is no tracking id, one will be generated later.
561 // Until that use a temporary key.
562 int key = m_currentData.trackingId;
563 if (key == -1)
564 key = m_contacts.count();
565
566 m_contacts.insert(key, m_currentData);
567 m_currentData = Contact();
568
569 } else if (data->type == EV_SYN && data->code == SYN_REPORT) {
570
571 // Ensure valid IDs even when the driver does not report ABS_MT_TRACKING_ID.
572 if (!m_contacts.isEmpty() && m_contacts.constBegin().value().trackingId == -1)
573 assignIds();
574
575 std::unique_lock<QMutex> locker;
576 if (m_filtered)
577 locker = std::unique_lock<QMutex>{m_mutex};
578
579 // update timestamps
580 m_lastTimeStamp = m_timeStamp;
581 m_timeStamp = data->input_event_sec + data->input_event_usec / 1000000.0;
582
583 m_lastTouchPoints = m_touchPoints;
584 m_touchPoints.clear();
585 Qt::TouchPointStates combinedStates;
586 bool hasPressure = false;
587
588 for (auto i = m_contacts.begin(), end = m_contacts.end(); i != end; /*erasing*/) {
589 auto it = i++;
590
591 Contact &contact(it.value());
592
593 if (!contact.state)
594 continue;
595
596 int key = m_typeB ? it.key() : contact.trackingId;
597 if (!m_typeB && m_lastContacts.contains(key)) {
598 const Contact &prev(m_lastContacts.value(key));
599 if (contact.state == Qt::TouchPointReleased) {
600 // Copy over the previous values for released points, just in case.
601 contact.x = prev.x;
602 contact.y = prev.y;
603 contact.maj = prev.maj;
604 } else {
605 contact.state = (prev.x == contact.x && prev.y == contact.y)
606 ? Qt::TouchPointStationary : Qt::TouchPointMoved;
607 }
608 }
609
610 // Avoid reporting a contact in released state more than once.
611 if (!m_typeB && contact.state == Qt::TouchPointReleased
612 && !m_lastContacts.contains(key)) {
613 m_contacts.erase(it);
614 continue;
615 }
616
617 if (contact.pressure)
618 hasPressure = true;
619
620 addTouchPoint(contact, &combinedStates);
621 }
622
623 // Now look for contacts that have disappeared since the last sync.
624 for (auto it = m_lastContacts.begin(), end = m_lastContacts.end(); it != end; ++it) {
625 Contact &contact(it.value());
626 int key = m_typeB ? it.key() : contact.trackingId;
627 if (m_typeB) {
628 if (contact.trackingId != m_contacts[key].trackingId && contact.state) {
629 contact.state = Qt::TouchPointReleased;
630 addTouchPoint(contact, &combinedStates);
631 }
632 } else {
633 if (!m_contacts.contains(key)) {
634 contact.state = Qt::TouchPointReleased;
635 addTouchPoint(contact, &combinedStates);
636 }
637 }
638 }
639
640 // Remove contacts that have just been reported as released.
641 for (auto i = m_contacts.begin(), end = m_contacts.end(); i != end; /*erasing*/) {
642 auto it = i++;
643
644 Contact &contact(it.value());
645
646 if (!contact.state)
647 continue;
648
649 if (contact.state == Qt::TouchPointReleased) {
650 if (m_typeB)
651 contact.state = static_cast<Qt::TouchPointState>(0);
652 else
653 m_contacts.erase(it);
654 } else {
655 contact.state = Qt::TouchPointStationary;
656 }
657 }
658
659 m_lastContacts = m_contacts;
660 if (!m_typeB && !m_singleTouch)
661 m_contacts.clear();
662
663
664 if (!m_touchPoints.isEmpty() && (hasPressure || combinedStates != Qt::TouchPointStationary))
665 reportPoints();
666 }
667
668 m_lastEventType = data->type;
669 }
670
findClosestContact(const QHash<int,Contact> & contacts,int x,int y,int * dist)671 int QEvdevTouchScreenData::findClosestContact(const QHash<int, Contact> &contacts, int x, int y, int *dist)
672 {
673 int minDist = -1, id = -1;
674 for (QHash<int, Contact>::const_iterator it = contacts.constBegin(), ite = contacts.constEnd();
675 it != ite; ++it) {
676 const Contact &contact(it.value());
677 int dx = x - contact.x;
678 int dy = y - contact.y;
679 int dist = dx * dx + dy * dy;
680 if (minDist == -1 || dist < minDist) {
681 minDist = dist;
682 id = contact.trackingId;
683 }
684 }
685 if (dist)
686 *dist = minDist;
687 return id;
688 }
689
assignIds()690 void QEvdevTouchScreenData::assignIds()
691 {
692 QHash<int, Contact> candidates = m_lastContacts, pending = m_contacts, newContacts;
693 int maxId = -1;
694 QHash<int, Contact>::iterator it, ite, bestMatch;
695 while (!pending.isEmpty() && !candidates.isEmpty()) {
696 int bestDist = -1, bestId = 0;
697 for (it = pending.begin(), ite = pending.end(); it != ite; ++it) {
698 int dist;
699 int id = findClosestContact(candidates, it->x, it->y, &dist);
700 if (id >= 0 && (bestDist == -1 || dist < bestDist)) {
701 bestDist = dist;
702 bestId = id;
703 bestMatch = it;
704 }
705 }
706 if (bestDist >= 0) {
707 bestMatch->trackingId = bestId;
708 newContacts.insert(bestId, *bestMatch);
709 candidates.remove(bestId);
710 pending.erase(bestMatch);
711 if (bestId > maxId)
712 maxId = bestId;
713 }
714 }
715 if (candidates.isEmpty()) {
716 for (it = pending.begin(), ite = pending.end(); it != ite; ++it) {
717 it->trackingId = ++maxId;
718 newContacts.insert(it->trackingId, *it);
719 }
720 }
721 m_contacts = newContacts;
722 }
723
screenGeometry() const724 QRect QEvdevTouchScreenData::screenGeometry() const
725 {
726 if (m_forceToActiveWindow) {
727 QWindow *win = QGuiApplication::focusWindow();
728 return win ? QHighDpi::toNativePixels(win->geometry(), win) : QRect();
729 }
730
731 // Now it becomes tricky. Traditionally we picked the primaryScreen()
732 // and were done with it. But then, enter multiple screens, and
733 // suddenly it was all broken.
734 //
735 // For now we only support the display configuration of the KMS/DRM
736 // backends of eglfs. See QTouchOutputMapping.
737 //
738 // The good news it that once winRect refers to the correct screen
739 // geometry in the full virtual desktop space, there is nothing else
740 // left to do since qguiapp will handle the rest.
741 QScreen *screen = QGuiApplication::primaryScreen();
742 if (!m_screenName.isEmpty()) {
743 if (!m_screen) {
744 const QList<QScreen *> screens = QGuiApplication::screens();
745 for (QScreen *s : screens) {
746 if (s->name() == m_screenName) {
747 m_screen = s;
748 break;
749 }
750 }
751 }
752 if (m_screen)
753 screen = m_screen;
754 }
755 return QHighDpi::toNativePixels(screen->geometry(), screen);
756 }
757
reportPoints()758 void QEvdevTouchScreenData::reportPoints()
759 {
760 QRect winRect = screenGeometry();
761 if (winRect.isNull())
762 return;
763
764 const int hw_w = hw_range_x_max - hw_range_x_min;
765 const int hw_h = hw_range_y_max - hw_range_y_min;
766
767 // Map the coordinates based on the normalized position. QPA expects 'area'
768 // to be in screen coordinates.
769 const int pointCount = m_touchPoints.count();
770 for (int i = 0; i < pointCount; ++i) {
771 QWindowSystemInterface::TouchPoint &tp(m_touchPoints[i]);
772
773 // Generate a screen position that is always inside the active window
774 // or the primary screen. Even though we report this as a QRectF, internally
775 // Qt uses QRect/QPoint so we need to bound the size to winRect.size() - QSize(1, 1)
776 const qreal wx = winRect.left() + tp.normalPosition.x() * (winRect.width() - 1);
777 const qreal wy = winRect.top() + tp.normalPosition.y() * (winRect.height() - 1);
778 const qreal sizeRatio = (winRect.width() + winRect.height()) / qreal(hw_w + hw_h);
779 if (tp.area.width() == -1) // touch major was not provided
780 tp.area = QRectF(0, 0, 8, 8);
781 else
782 tp.area = QRectF(0, 0, tp.area.width() * sizeRatio, tp.area.height() * sizeRatio);
783 tp.area.moveCenter(QPointF(wx, wy));
784
785 // Calculate normalized pressure.
786 if (!hw_pressure_min && !hw_pressure_max)
787 tp.pressure = tp.state == Qt::TouchPointReleased ? 0 : 1;
788 else
789 tp.pressure = (tp.pressure - hw_pressure_min) / qreal(hw_pressure_max - hw_pressure_min);
790
791 if (Q_UNLIKELY(qLcEvents().isDebugEnabled()))
792 qCDebug(qLcEvents) << "reporting" << tp;
793 }
794
795 // Let qguiapp pick the target window.
796 if (m_filtered)
797 emit q->touchPointsUpdated();
798 else
799 QWindowSystemInterface::handleTouchEvent(nullptr, q->touchDevice(), m_touchPoints);
800 }
801
QEvdevTouchScreenHandlerThread(const QString & device,const QString & spec,QObject * parent)802 QEvdevTouchScreenHandlerThread::QEvdevTouchScreenHandlerThread(const QString &device, const QString &spec, QObject *parent)
803 : QDaemonThread(parent), m_device(device), m_spec(spec), m_handler(nullptr), m_touchDeviceRegistered(false)
804 , m_touchUpdatePending(false)
805 , m_filterWindow(nullptr)
806 , m_touchRate(-1)
807 {
808 start();
809 }
810
~QEvdevTouchScreenHandlerThread()811 QEvdevTouchScreenHandlerThread::~QEvdevTouchScreenHandlerThread()
812 {
813 quit();
814 wait();
815 }
816
run()817 void QEvdevTouchScreenHandlerThread::run()
818 {
819 m_handler = new QEvdevTouchScreenHandler(m_device, m_spec);
820
821 if (m_handler->isFiltered())
822 connect(m_handler, &QEvdevTouchScreenHandler::touchPointsUpdated, this, &QEvdevTouchScreenHandlerThread::scheduleTouchPointUpdate);
823
824 // Report the registration to the parent thread by invoking the method asynchronously
825 QMetaObject::invokeMethod(this, "notifyTouchDeviceRegistered", Qt::QueuedConnection);
826
827 exec();
828
829 delete m_handler;
830 m_handler = nullptr;
831 }
832
isTouchDeviceRegistered() const833 bool QEvdevTouchScreenHandlerThread::isTouchDeviceRegistered() const
834 {
835 return m_touchDeviceRegistered;
836 }
837
notifyTouchDeviceRegistered()838 void QEvdevTouchScreenHandlerThread::notifyTouchDeviceRegistered()
839 {
840 m_touchDeviceRegistered = true;
841 emit touchDeviceRegistered();
842 }
843
scheduleTouchPointUpdate()844 void QEvdevTouchScreenHandlerThread::scheduleTouchPointUpdate()
845 {
846 QWindow *window = QGuiApplication::focusWindow();
847 if (window != m_filterWindow) {
848 if (m_filterWindow)
849 m_filterWindow->removeEventFilter(this);
850 m_filterWindow = window;
851 if (m_filterWindow)
852 m_filterWindow->installEventFilter(this);
853 }
854 if (m_filterWindow) {
855 m_touchUpdatePending = true;
856 m_filterWindow->requestUpdate();
857 }
858 }
859
eventFilter(QObject * object,QEvent * event)860 bool QEvdevTouchScreenHandlerThread::eventFilter(QObject *object, QEvent *event)
861 {
862 if (m_touchUpdatePending && object == m_filterWindow && event->type() == QEvent::UpdateRequest) {
863 m_touchUpdatePending = false;
864 filterAndSendTouchPoints();
865 }
866 return false;
867 }
868
filterAndSendTouchPoints()869 void QEvdevTouchScreenHandlerThread::filterAndSendTouchPoints()
870 {
871 QRect winRect = m_handler->d->screenGeometry();
872 if (winRect.isNull())
873 return;
874
875 float vsyncDelta = 1.0f / QGuiApplication::primaryScreen()->refreshRate();
876
877 QHash<int, FilteredTouchPoint> filteredPoints;
878
879 m_handler->d->m_mutex.lock();
880
881 double time = m_handler->d->m_timeStamp;
882 double lastTime = m_handler->d->m_lastTimeStamp;
883 double touchDelta = time - lastTime;
884 if (m_touchRate < 0 || touchDelta > vsyncDelta) {
885 // We're at the very start, with nothing to go on, so make a guess
886 // that the touch rate will be somewhere in the range of half a vsync.
887 // This doesn't have to be accurate as we will calibrate it over time,
888 // but it gives us a better starting point so calibration will be
889 // slightly quicker. If, on the other hand, we already have an
890 // estimate, we'll leave it as is and keep it.
891 if (m_touchRate < 0)
892 m_touchRate = (1.0 / QGuiApplication::primaryScreen()->refreshRate()) / 2.0;
893
894 } else {
895 // Update our estimate for the touch rate. We're making the assumption
896 // that this value will be mostly accurate with the occational bump,
897 // so we're weighting the existing value high compared to the update.
898 const double ratio = 0.9;
899 m_touchRate = sqrt(m_touchRate * m_touchRate * ratio + touchDelta * touchDelta * (1.0 - ratio));
900 }
901
902 QList<QWindowSystemInterface::TouchPoint> points = m_handler->d->m_touchPoints;
903 QList<QWindowSystemInterface::TouchPoint> lastPoints = m_handler->d->m_lastTouchPoints;
904
905 m_handler->d->m_mutex.unlock();
906
907 for (int i=0; i<points.size(); ++i) {
908 QWindowSystemInterface::TouchPoint &tp = points[i];
909 QPointF pos = tp.normalPosition;
910 FilteredTouchPoint f;
911
912 QWindowSystemInterface::TouchPoint ltp;
913 ltp.id = -1;
914 for (int j=0; j<lastPoints.size(); ++j) {
915 if (lastPoints.at(j).id == tp.id) {
916 ltp = lastPoints.at(j);
917 break;
918 }
919 }
920
921 QPointF velocity;
922 if (lastTime != 0 && ltp.id >= 0)
923 velocity = (pos - ltp.normalPosition) / m_touchRate;
924 if (m_filteredPoints.contains(tp.id)) {
925 f = m_filteredPoints.take(tp.id);
926 f.x.update(pos.x(), velocity.x(), vsyncDelta);
927 f.y.update(pos.y(), velocity.y(), vsyncDelta);
928 pos = QPointF(f.x.position(), f.y.position());
929 } else {
930 f.x.initialize(pos.x(), velocity.x());
931 f.y.initialize(pos.y(), velocity.y());
932 // Make sure the first instance of a touch point we send has the
933 // 'pressed' state.
934 if (tp.state != Qt::TouchPointPressed)
935 tp.state = Qt::TouchPointPressed;
936 }
937
938 tp.velocity = QVector2D(f.x.velocity() * winRect.width(), f.y.velocity() * winRect.height());
939
940 qreal filteredNormalizedX = f.x.position() + f.x.velocity() * m_handler->d->m_prediction / 1000.0;
941 qreal filteredNormalizedY = f.y.position() + f.y.velocity() * m_handler->d->m_prediction / 1000.0;
942
943 // Clamp to the screen
944 tp.normalPosition = QPointF(qBound<qreal>(0, filteredNormalizedX, 1),
945 qBound<qreal>(0, filteredNormalizedY, 1));
946
947 qreal x = winRect.x() + (tp.normalPosition.x() * (winRect.width() - 1));
948 qreal y = winRect.y() + (tp.normalPosition.y() * (winRect.height() - 1));
949
950 tp.area.moveCenter(QPointF(x, y));
951
952 // Store the touch point for later so we can release it if we've
953 // missed the actual release between our last update and this.
954 f.touchPoint = tp;
955
956 // Don't store the point for future reference if it is a release.
957 if (tp.state != Qt::TouchPointReleased)
958 filteredPoints[tp.id] = f;
959 }
960
961 for (QHash<int, FilteredTouchPoint>::const_iterator it = m_filteredPoints.constBegin(), end = m_filteredPoints.constEnd(); it != end; ++it) {
962 const FilteredTouchPoint &f = it.value();
963 QWindowSystemInterface::TouchPoint tp = f.touchPoint;
964 tp.state = Qt::TouchPointReleased;
965 tp.velocity = QVector2D();
966 points.append(tp);
967 }
968
969 m_filteredPoints = filteredPoints;
970
971 QWindowSystemInterface::handleTouchEvent(nullptr,
972 m_handler->touchDevice(),
973 points);
974 }
975
976
977 QT_END_NAMESPACE
978