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