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

source code of qtbase/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp