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