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