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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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