Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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#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
45
46using namespace Qt::StringLiterals;
47
48Q_LOGGING_CATEGORY(qLcEvdevTouch, "qt.qpa.input")
49Q_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 */
54#ifndef ABS_MT_TOUCH_MAJOR
55#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
56#endif
57#ifndef ABS_MT_POSITION_X
58#define ABS_MT_POSITION_X 0x35 /* Center X ellipse position */
59#endif
60#ifndef ABS_MT_POSITION_Y
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
69#ifndef ABS_MT_TRACKING_ID
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
80{
81public:
83
84 void processInputEvent(input_event *data);
85 void assignIds();
86
89 QList<QWindowSystemInterface::TouchPoint> m_touchPoints;
90 QList<QWindowSystemInterface::TouchPoint> m_lastTouchPoints;
91
92 struct Contact {
93 int trackingId = -1;
94 int x = 0;
95 int y = 0;
96 int maj = -1;
97 int pressure = 0;
99 };
100 QHash<int, Contact> m_contacts; // The key is a tracking id for type A, slot number for type B.
101 QHash<int, Contact> m_lastContacts;
104
107
108 int findClosestContact(const QHash<int, Contact> &contacts, int x, int y, int *dist);
109 void addTouchPoint(const Contact &contact, QEventPoint::States *combinedStates);
110 void reportPoints();
112
113 QRect screenGeometry() const;
114
128 mutable QPointer<QScreen> m_screen;
129
130 // Touch filtering and prediction are part of the same thing. The default
131 // prediction is 0ms, but sensible results can be achieved by setting it
132 // to, for instance, 16ms.
133 // For filtering to work well, the QPA plugin should provide a dead-steady
134 // implementation of QPlatformWindow::requestUpdate().
137
138 // When filtering is enabled, protect the access to current and last
139 // timeStamp and touchPoints, as these are being read on the gui thread.
141};
142
144 : q(q_ptr),
145 m_lastEventType(-1),
146 m_currentSlot(0),
147 m_timeStamp(0), m_lastTimeStamp(0),
148 hw_range_x_min(0), hw_range_x_max(0),
149 hw_range_y_min(0), hw_range_y_max(0),
150 hw_pressure_min(0), hw_pressure_max(0),
151 m_forceToActiveWindow(false), m_typeB(false), m_singleTouch(false),
152 m_filtered(false), m_prediction(0)
153{
154 for (const QString &arg : args) {
155 if (arg == u"force_window")
157 else if (arg == u"filtered")
158 m_filtered = true;
159 else if (const QStringView prefix = u"prediction="; arg.startsWith(prefix))
160 m_prediction = QStringView(arg).mid(prefix.size()).toInt();
161 }
162}
163
164#define LONG_BITS (sizeof(long) << 3)
165#define NUM_LONGS(bits) (((bits) + LONG_BITS - 1) / LONG_BITS)
166
167#if !QT_CONFIG(mtdev)
168static inline bool testBit(long bit, const long *array)
169{
170 return (array[bit / LONG_BITS] >> bit % LONG_BITS) & 1;
171}
172#endif
173
175 : QObject(parent), m_notify(nullptr), m_fd(-1), d(nullptr), m_device(nullptr)
176#if QT_CONFIG(mtdev)
177 , m_mtdev(nullptr)
178#endif
179{
180 setObjectName("Evdev Touch Handler"_L1);
181
182 const QStringList args = spec.split(u':');
183 int rotationAngle = 0;
184 bool invertx = false;
185 bool inverty = false;
186 for (int i = 0; i < args.size(); ++i) {
187 if (args.at(i).startsWith("rotate"_L1)) {
188 QString rotateArg = args.at(i).section(u'=', 1, 1);
189 bool ok;
190 uint argValue = rotateArg.toUInt(&ok);
191 if (ok) {
192 switch (argValue) {
193 case 90:
194 case 180:
195 case 270:
196 rotationAngle = argValue;
197 break;
198 default:
199 break;
200 }
201 }
202 } else if (args.at(i) == "invertx"_L1) {
203 invertx = true;
204 } else if (args.at(i) == "inverty"_L1) {
205 inverty = true;
206 }
207 }
208
209 qCDebug(qLcEvdevTouch, "evdevtouch: Using device %ls", qUtf16Printable(device));
210
211 m_fd = QT_OPEN(device.toLocal8Bit().constData(), O_RDONLY | O_NDELAY, 0);
212
213 if (m_fd >= 0) {
214 m_notify = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
216 } else {
217 qErrnoWarning("evdevtouch: Cannot open input device %ls", qUtf16Printable(device));
218 return;
219 }
220
221#if QT_CONFIG(mtdev)
222 m_mtdev = static_cast<mtdev *>(calloc(1, sizeof(mtdev)));
223 int mtdeverr = mtdev_open(m_mtdev, m_fd);
224 if (mtdeverr) {
225 qWarning("evdevtouch: mtdev_open failed: %d", mtdeverr);
226 QT_CLOSE(m_fd);
227 free(m_mtdev);
228 return;
229 }
230#endif
231
232 d = new QEvdevTouchScreenData(this, args);
233
234#if QT_CONFIG(mtdev)
235 const char *mtdevStr = "(mtdev)";
236 d->m_typeB = true;
237#else
238 const char *mtdevStr = "";
239 long absbits[NUM_LONGS(ABS_CNT)];
240 if (ioctl(m_fd, EVIOCGBIT(EV_ABS, sizeof(absbits)), absbits) >= 0) {
241 d->m_typeB = testBit(ABS_MT_SLOT, absbits);
243 }
244#endif
245
246 d->deviceNode = device;
247 qCDebug(qLcEvdevTouch,
248 "evdevtouch: %ls: Protocol type %c %s (%s), filtered=%s",
250 d->m_typeB ? 'B' : 'A', mtdevStr,
251 d->m_singleTouch ? "single" : "multi",
252 d->m_filtered ? "yes" : "no");
253 if (d->m_filtered)
254 qCDebug(qLcEvdevTouch, " - prediction=%d", d->m_prediction);
255
256 input_absinfo absInfo;
257 memset(&absInfo, 0, sizeof(input_absinfo));
258 bool has_x_range = false, has_y_range = false;
259
260 if (ioctl(m_fd, EVIOCGABS((d->m_singleTouch ? ABS_X : ABS_MT_POSITION_X)), &absInfo) >= 0) {
261 qCDebug(qLcEvdevTouch, "evdevtouch: %ls: min X: %d max X: %d", qUtf16Printable(device),
262 absInfo.minimum, absInfo.maximum);
263 d->hw_range_x_min = absInfo.minimum;
264 d->hw_range_x_max = absInfo.maximum;
265 has_x_range = true;
266 }
267
268 if (ioctl(m_fd, EVIOCGABS((d->m_singleTouch ? ABS_Y : ABS_MT_POSITION_Y)), &absInfo) >= 0) {
269 qCDebug(qLcEvdevTouch, "evdevtouch: %ls: min Y: %d max Y: %d", qUtf16Printable(device),
270 absInfo.minimum, absInfo.maximum);
271 d->hw_range_y_min = absInfo.minimum;
272 d->hw_range_y_max = absInfo.maximum;
273 has_y_range = true;
274 }
275
276 if (!has_x_range || !has_y_range)
277 qWarning("evdevtouch: %ls: Invalid ABS limits, behavior unspecified", qUtf16Printable(device));
278
279 if (ioctl(m_fd, EVIOCGABS(ABS_PRESSURE), &absInfo) >= 0) {
280 qCDebug(qLcEvdevTouch, "evdevtouch: %ls: min pressure: %d max pressure: %d", qUtf16Printable(device),
281 absInfo.minimum, absInfo.maximum);
282 if (absInfo.maximum > absInfo.minimum) {
283 d->hw_pressure_min = absInfo.minimum;
284 d->hw_pressure_max = absInfo.maximum;
285 }
286 }
287
288 char name[1024];
289 if (ioctl(m_fd, EVIOCGNAME(sizeof(name) - 1), name) >= 0) {
291 qCDebug(qLcEvdevTouch, "evdevtouch: %ls: device name: %s", qUtf16Printable(device), name);
292 }
293
294 // Fix up the coordinate ranges for am335x in case the kernel driver does not have them fixed.
295 if (d->hw_name == "ti-tsc"_L1) {
296 if (d->hw_range_x_min == 0 && d->hw_range_x_max == 4095) {
297 d->hw_range_x_min = 165;
298 d->hw_range_x_max = 4016;
299 }
300 if (d->hw_range_y_min == 0 && d->hw_range_y_max == 4095) {
301 d->hw_range_y_min = 220;
302 d->hw_range_y_max = 3907;
303 }
304 qCDebug(qLcEvdevTouch, "evdevtouch: found ti-tsc, overriding: min X: %d max X: %d min Y: %d max Y: %d",
306 }
307
308 bool grabSuccess = !ioctl(m_fd, EVIOCGRAB, (void *) 1);
309 if (grabSuccess)
310 ioctl(m_fd, EVIOCGRAB, (void *) 0);
311 else
312 qWarning("evdevtouch: The device is grabbed by another process. No events will be read.");
313
314 if (rotationAngle)
315 d->m_rotate = QTransform::fromTranslate(0.5, 0.5).rotate(rotationAngle).translate(-0.5, -0.5);
316
317 if (invertx)
318 d->m_rotate *= QTransform::fromTranslate(0.5, 0.5).scale(-1.0, 1.0).translate(-0.5, -0.5);
319
320 if (inverty)
321 d->m_rotate *= QTransform::fromTranslate(0.5, 0.5).scale(1.0, -1.0).translate(-0.5, -0.5);
322
324 if (mapping->load()) {
325 d->m_screenName = mapping->screenNameForDeviceNode(d->deviceNode);
326 if (!d->m_screenName.isEmpty())
327 qCDebug(qLcEvdevTouch, "evdevtouch: Mapping device %ls to screen %ls",
329 }
330
331 registerPointingDevice();
332}
333
335{
336#if QT_CONFIG(mtdev)
337 if (m_mtdev) {
338 mtdev_close(m_mtdev);
339 free(m_mtdev);
340 }
341#endif
342
343 if (m_fd >= 0)
344 QT_CLOSE(m_fd);
345
346 delete d;
347
348 unregisterPointingDevice();
349}
350
352{
353 return d && d->m_filtered;
354}
355
357{
358 return m_device;
359}
360
362{
363 ::input_event buffer[32];
364 int events = 0;
365
366#if QT_CONFIG(mtdev)
367 forever {
368 do {
369 events = mtdev_get(m_mtdev, m_fd, buffer, sizeof(buffer) / sizeof(::input_event));
370 // keep trying mtdev_get if we get interrupted. note that we do not
371 // (and should not) handle EAGAIN; EAGAIN means that reading would
372 // block and we'll get back here later to try again anyway.
373 } while (events == -1 && errno == EINTR);
374
375 // 0 events is EOF, -1 means error, handle both in the same place
376 if (events <= 0)
377 goto err;
378
379 // process our shiny new events
380 for (int i = 0; i < events; ++i)
382
383 // and try to get more
384 }
385#else
386 int n = 0;
387 for (; ;) {
388 events = QT_READ(m_fd, reinterpret_cast<char*>(buffer) + n, sizeof(buffer) - n);
389 if (events <= 0)
390 goto err;
391 n += events;
392 if (n % sizeof(::input_event) == 0)
393 break;
394 }
395
396 n /= sizeof(::input_event);
397
398 for (int i = 0; i < n; ++i)
400#endif
401 return;
402
403err:
404 if (!events) {
405 qWarning("evdevtouch: Got EOF from input device");
406 return;
407 } else if (events < 0) {
408 if (errno != EINTR && errno != EAGAIN) {
409 qErrnoWarning("evdevtouch: Could not read from input device");
410 if (errno == ENODEV) { // device got disconnected -> stop reading
411 delete m_notify;
412 m_notify = nullptr;
413
414 QT_CLOSE(m_fd);
415 m_fd = -1;
416
417 unregisterPointingDevice();
418 }
419 return;
420 }
421 }
422}
423
424void QEvdevTouchScreenHandler::registerPointingDevice()
425{
426 if (m_device)
427 return;
428
429 static int id = 1;
433
434 // TODO get evdev ID instead of an incremeting number; set USB ID too
435 m_device = new QPointingDevice(d->hw_name, id++,
437 caps, 16, 0);
438
439 auto geom = d->screenGeometry();
440 if (!geom.isNull())
441 QPointingDevicePrivate::get(m_device)->setAvailableVirtualGeometry(geom);
442
444}
445
475void QEvdevTouchScreenHandler::unregisterPointingDevice()
476{
477 if (!m_device)
478 return;
479
482 m_device->deleteLater();
483 } else {
484 delete m_device;
485 }
486 m_device = nullptr;
487}
488
489void QEvdevTouchScreenData::addTouchPoint(const Contact &contact, QEventPoint::States *combinedStates)
490{
492 tp.id = contact.trackingId;
493 tp.state = contact.state;
494 *combinedStates |= tp.state;
495
496 // Store the HW coordinates for now, will be updated later.
497 tp.area = QRectF(0, 0, contact.maj, contact.maj);
498 tp.area.moveCenter(QPoint(contact.x, contact.y));
499 tp.pressure = contact.pressure;
500
501 // Get a normalized position in range 0..1.
504
505 if (!m_rotate.isIdentity())
507
508 tp.rawPositions.append(QPointF(contact.x, contact.y));
509
511}
512
514{
515 if (data->type == EV_ABS) {
516
517 if (data->code == ABS_MT_POSITION_X || (m_singleTouch && data->code == ABS_X)) {
519 if (m_singleTouch)
521 if (m_typeB) {
525 }
526 } else if (data->code == ABS_MT_POSITION_Y || (m_singleTouch && data->code == ABS_Y)) {
528 if (m_singleTouch)
530 if (m_typeB) {
534 }
535 } else if (data->code == ABS_MT_TRACKING_ID) {
537 if (m_typeB) {
538 if (m_currentData.trackingId == -1) {
540 } else {
543 }
544 }
545 } else if (data->code == ABS_MT_TOUCH_MAJOR) {
546 m_currentData.maj = data->value;
547 if (data->value == 0)
549 if (m_typeB)
551 } else if (data->code == ABS_PRESSURE || data->code == ABS_MT_PRESSURE) {
552 if (Q_UNLIKELY(qLcEvents().isDebugEnabled()))
553 qCDebug(qLcEvents, "EV_ABS code 0x%x: pressure %d; bounding to [%d,%d]",
554 data->code, data->value, hw_pressure_min, hw_pressure_max);
556 if (m_typeB || m_singleTouch)
558 } else if (data->code == ABS_MT_SLOT) {
559 m_currentSlot = data->value;
560 }
561
562 } else if (data->type == EV_KEY && !m_typeB) {
563 if (data->code == BTN_TOUCH && data->value == 0)
565 } else if (data->type == EV_SYN && data->code == SYN_MT_REPORT && m_lastEventType != EV_SYN) {
566
567 // If there is no tracking id, one will be generated later.
568 // Until that use a temporary key.
570 if (key == -1)
571 key = m_contacts.size();
572
575
576 } else if (data->type == EV_SYN && data->code == SYN_REPORT) {
577
578 // Ensure valid IDs even when the driver does not report ABS_MT_TRACKING_ID.
579 if (!m_contacts.isEmpty() && m_contacts.constBegin().value().trackingId == -1)
580 assignIds();
581
582 std::unique_lock<QMutex> locker;
583 if (m_filtered)
584 locker = std::unique_lock<QMutex>{m_mutex};
585
586 // update timestamps
588 m_timeStamp = data->input_event_sec + data->input_event_usec / 1000000.0;
589
592 QEventPoint::States combinedStates;
593 bool hasPressure = false;
594
595 for (auto it = m_contacts.begin(), end = m_contacts.end(); it != end; /*erasing*/) {
596 Contact &contact(it.value());
597
598 if (!contact.state) {
599 ++it;
600 continue;
601 }
602
603 int key = m_typeB ? it.key() : contact.trackingId;
604 if (!m_typeB && m_lastContacts.contains(key)) {
605 const Contact &prev(m_lastContacts.value(key));
606 if (contact.state == QEventPoint::State::Released) {
607 // Copy over the previous values for released points, just in case.
608 contact.x = prev.x;
609 contact.y = prev.y;
610 contact.maj = prev.maj;
611 } else {
612 contact.state = (prev.x == contact.x && prev.y == contact.y)
614 }
615 }
616
617 // Avoid reporting a contact in released state more than once.
618 if (!m_typeB && contact.state == QEventPoint::State::Released
619 && !m_lastContacts.contains(key)) {
620 it = m_contacts.erase(it);
621 continue;
622 }
623
624 if (contact.pressure)
625 hasPressure = true;
626
627 addTouchPoint(contact, &combinedStates);
628 ++it;
629 }
630
631 // Now look for contacts that have disappeared since the last sync.
632 for (auto it = m_lastContacts.begin(), end = m_lastContacts.end(); it != end; ++it) {
633 Contact &contact(it.value());
634 int key = m_typeB ? it.key() : contact.trackingId;
635 if (m_typeB) {
636 if (contact.trackingId != m_contacts[key].trackingId && contact.state) {
637 contact.state = QEventPoint::State::Released;
638 addTouchPoint(contact, &combinedStates);
639 }
640 } else {
641 if (!m_contacts.contains(key)) {
642 contact.state = QEventPoint::State::Released;
643 addTouchPoint(contact, &combinedStates);
644 }
645 }
646 }
647
648 // Remove contacts that have just been reported as released.
649 for (auto it = m_contacts.begin(), end = m_contacts.end(); it != end; /*erasing*/) {
650 Contact &contact(it.value());
651
652 if (!contact.state) {
653 ++it;
654 continue;
655 }
656
657 if (contact.state == QEventPoint::State::Released) {
658 if (m_typeB) {
659 contact.state = QEventPoint::State::Unknown;
660 } else {
661 it = m_contacts.erase(it);
662 continue;
663 }
664 } else {
665 contact.state = QEventPoint::State::Stationary;
666 }
667 ++it;
668 }
669
671 if (!m_typeB && !m_singleTouch)
672 m_contacts.clear();
673
674
675 if (!m_touchPoints.isEmpty() && (hasPressure || combinedStates != QEventPoint::State::Stationary))
676 reportPoints();
677 }
678
679 m_lastEventType = data->type;
680}
681
682int QEvdevTouchScreenData::findClosestContact(const QHash<int, Contact> &contacts, int x, int y, int *dist)
683{
684 int minDist = -1, id = -1;
685 for (QHash<int, Contact>::const_iterator it = contacts.constBegin(), ite = contacts.constEnd();
686 it != ite; ++it) {
687 const Contact &contact(it.value());
688 int dx = x - contact.x;
689 int dy = y - contact.y;
690 int dist = dx * dx + dy * dy;
691 if (minDist == -1 || dist < minDist) {
692 minDist = dist;
693 id = contact.trackingId;
694 }
695 }
696 if (dist)
697 *dist = minDist;
698 return id;
699}
700
702{
703 QHash<int, Contact> candidates = m_lastContacts, pending = m_contacts, newContacts;
704 int maxId = -1;
705 QHash<int, Contact>::iterator it, ite, bestMatch;
706 while (!pending.isEmpty() && !candidates.isEmpty()) {
707 int bestDist = -1, bestId = 0;
708 for (it = pending.begin(), ite = pending.end(); it != ite; ++it) {
709 int dist;
710 int id = findClosestContact(candidates, it->x, it->y, &dist);
711 if (id >= 0 && (bestDist == -1 || dist < bestDist)) {
712 bestDist = dist;
713 bestId = id;
714 bestMatch = it;
715 }
716 }
717 if (bestDist >= 0) {
718 bestMatch->trackingId = bestId;
719 newContacts.insert(bestId, *bestMatch);
720 candidates.remove(bestId);
721 pending.erase(bestMatch);
722 if (bestId > maxId)
723 maxId = bestId;
724 }
725 }
726 if (candidates.isEmpty()) {
727 for (it = pending.begin(), ite = pending.end(); it != ite; ++it) {
728 it->trackingId = ++maxId;
729 newContacts.insert(it->trackingId, *it);
730 }
731 }
732 m_contacts = newContacts;
733}
734
736{
740 }
741
742 // Now it becomes tricky. Traditionally we picked the primaryScreen()
743 // and were done with it. But then, enter multiple screens, and
744 // suddenly it was all broken.
745 //
746 // For now we only support the display configuration of the KMS/DRM
747 // backends of eglfs. See QOutputMapping.
748 //
749 // The good news it that once winRect refers to the correct screen
750 // geometry in the full virtual desktop space, there is nothing else
751 // left to do since qguiapp will handle the rest.
753 if (!m_screenName.isEmpty()) {
754 if (!m_screen) {
755 const QList<QScreen *> screens = QGuiApplication::screens();
756 for (QScreen *s : screens) {
757 if (s->name() == m_screenName) {
758 m_screen = s;
759 break;
760 }
761 }
762 }
763 if (m_screen)
765 }
767}
768
770{
771 QRect winRect = screenGeometry();
772 if (winRect.isNull())
773 return;
774
775 const int hw_w = hw_range_x_max - hw_range_x_min;
776 const int hw_h = hw_range_y_max - hw_range_y_min;
777
778 // Map the coordinates based on the normalized position. QPA expects 'area'
779 // to be in screen coordinates.
780 const int pointCount = m_touchPoints.size();
781 for (int i = 0; i < pointCount; ++i) {
783
784 // Generate a screen position that is always inside the active window
785 // or the primary screen. Even though we report this as a QRectF, internally
786 // Qt uses QRect/QPoint so we need to bound the size to winRect.size() - QSize(1, 1)
787 const qreal wx = winRect.left() + tp.normalPosition.x() * (winRect.width() - 1);
788 const qreal wy = winRect.top() + tp.normalPosition.y() * (winRect.height() - 1);
789 const qreal sizeRatio = (winRect.width() + winRect.height()) / qreal(hw_w + hw_h);
790 if (tp.area.width() == -1) // touch major was not provided
791 tp.area = QRectF(0, 0, 8, 8);
792 else
793 tp.area = QRectF(0, 0, tp.area.width() * sizeRatio, tp.area.height() * sizeRatio);
794 tp.area.moveCenter(QPointF(wx, wy));
795
796 // Calculate normalized pressure.
799 else
801
802 if (Q_UNLIKELY(qLcEvents().isDebugEnabled()))
803 qCDebug(qLcEvents) << "reporting" << tp;
804 }
805
806 // Let qguiapp pick the target window.
807 if (m_filtered)
808 emit q->touchPointsUpdated();
809 else
811}
812
814 : QDaemonThread(parent), m_device(device), m_spec(spec), m_handler(nullptr), m_touchDeviceRegistered(false)
815 , m_touchUpdatePending(false)
816 , m_filterWindow(nullptr)
817 , m_touchRate(-1)
818{
819 start();
820}
821
827
829{
830 m_handler = new QEvdevTouchScreenHandler(m_device, m_spec);
831
832 if (m_handler->isFiltered())
834
835 // Report the registration to the parent thread by invoking the method asynchronously
836 QMetaObject::invokeMethod(this, "notifyTouchDeviceRegistered", Qt::QueuedConnection);
837
838 exec();
839
840 delete m_handler;
841 m_handler = nullptr;
842}
843
845{
846 return m_touchDeviceRegistered;
847}
848
849void QEvdevTouchScreenHandlerThread::notifyTouchDeviceRegistered()
850{
851 m_touchDeviceRegistered = true;
853}
854
856{
858 if (window != m_filterWindow) {
859 if (m_filterWindow)
860 m_filterWindow->removeEventFilter(this);
861 m_filterWindow = window;
862 if (m_filterWindow)
863 m_filterWindow->installEventFilter(this);
864 }
865 if (m_filterWindow) {
866 m_touchUpdatePending = true;
867 m_filterWindow->requestUpdate();
868 }
869}
870
872{
873 if (m_touchUpdatePending && object == m_filterWindow && event->type() == QEvent::UpdateRequest) {
874 m_touchUpdatePending = false;
875 filterAndSendTouchPoints();
876 }
877 return false;
878}
879
880void QEvdevTouchScreenHandlerThread::filterAndSendTouchPoints()
881{
882 QRect winRect = m_handler->d->screenGeometry();
883 if (winRect.isNull())
884 return;
885
886 float vsyncDelta = 1.0f / QGuiApplication::primaryScreen()->refreshRate();
887
888 QHash<int, FilteredTouchPoint> filteredPoints;
889
890 m_handler->d->m_mutex.lock();
891
892 double time = m_handler->d->m_timeStamp;
893 double lastTime = m_handler->d->m_lastTimeStamp;
894 double touchDelta = time - lastTime;
895 if (m_touchRate < 0 || touchDelta > vsyncDelta) {
896 // We're at the very start, with nothing to go on, so make a guess
897 // that the touch rate will be somewhere in the range of half a vsync.
898 // This doesn't have to be accurate as we will calibrate it over time,
899 // but it gives us a better starting point so calibration will be
900 // slightly quicker. If, on the other hand, we already have an
901 // estimate, we'll leave it as is and keep it.
902 if (m_touchRate < 0)
903 m_touchRate = (1.0 / QGuiApplication::primaryScreen()->refreshRate()) / 2.0;
904
905 } else {
906 // Update our estimate for the touch rate. We're making the assumption
907 // that this value will be mostly accurate with the occasional bump,
908 // so we're weighting the existing value high compared to the update.
909 const double ratio = 0.9;
910 m_touchRate = sqrt(m_touchRate * m_touchRate * ratio + touchDelta * touchDelta * (1.0 - ratio));
911 }
912
913 QList<QWindowSystemInterface::TouchPoint> points = m_handler->d->m_touchPoints;
914 QList<QWindowSystemInterface::TouchPoint> lastPoints = m_handler->d->m_lastTouchPoints;
915
916 m_handler->d->m_mutex.unlock();
917
918 for (int i=0; i<points.size(); ++i) {
921 FilteredTouchPoint f;
922
924 ltp.id = -1;
925 for (int j=0; j<lastPoints.size(); ++j) {
926 if (lastPoints.at(j).id == tp.id) {
927 ltp = lastPoints.at(j);
928 break;
929 }
930 }
931
932 QPointF velocity;
933 if (lastTime != 0 && ltp.id >= 0)
934 velocity = (pos - ltp.normalPosition) / m_touchRate;
935 if (m_filteredPoints.contains(tp.id)) {
936 f = m_filteredPoints.take(tp.id);
937 f.x.update(pos.x(), velocity.x(), vsyncDelta);
938 f.y.update(pos.y(), velocity.y(), vsyncDelta);
939 pos = QPointF(f.x.position(), f.y.position());
940 } else {
941 f.x.initialize(pos.x(), velocity.x());
942 f.y.initialize(pos.y(), velocity.y());
943 // Make sure the first instance of a touch point we send has the
944 // 'pressed' state.
947 }
948
949 tp.velocity = QVector2D(f.x.velocity() * winRect.width(), f.y.velocity() * winRect.height());
950
951 qreal filteredNormalizedX = f.x.position() + f.x.velocity() * m_handler->d->m_prediction / 1000.0;
952 qreal filteredNormalizedY = f.y.position() + f.y.velocity() * m_handler->d->m_prediction / 1000.0;
953
954 // Clamp to the screen
955 tp.normalPosition = QPointF(qBound<qreal>(0, filteredNormalizedX, 1),
956 qBound<qreal>(0, filteredNormalizedY, 1));
957
958 qreal x = winRect.x() + (tp.normalPosition.x() * (winRect.width() - 1));
959 qreal y = winRect.y() + (tp.normalPosition.y() * (winRect.height() - 1));
960
961 tp.area.moveCenter(QPointF(x, y));
962
963 // Store the touch point for later so we can release it if we've
964 // missed the actual release between our last update and this.
965 f.touchPoint = tp;
966
967 // Don't store the point for future reference if it is a release.
969 filteredPoints[tp.id] = f;
970 }
971
972 for (QHash<int, FilteredTouchPoint>::const_iterator it = m_filteredPoints.constBegin(), end = m_filteredPoints.constEnd(); it != end; ++it) {
973 const FilteredTouchPoint &f = it.value();
976 tp.velocity = QVector2D();
977 points.append(tp);
978 }
979
980 m_filteredPoints = filteredPoints;
981
983 m_handler->touchDevice(),
984 points);
985}
986
987
989
990#include "moc_qevdevtouchhandler_p.cpp"
IOBluetoothDevice * device
static QCoreApplication * instance() noexcept
Returns a pointer to the application's QCoreApplication (or QGuiApplication/QApplication) instance.
QEvdevTouchScreenHandler * q
int findClosestContact(const QHash< int, Contact > &contacts, int x, int y, int *dist)
QHash< int, Contact > m_lastContacts
QEvdevTouchScreenData(QEvdevTouchScreenHandler *q_ptr, const QStringList &args)
QList< QWindowSystemInterface::TouchPoint > m_lastTouchPoints
QHash< int, Contact > m_contacts
void addTouchPoint(const Contact &contact, QEventPoint::States *combinedStates)
QPointer< QScreen > m_screen
QList< QWindowSystemInterface::TouchPoint > m_touchPoints
void processInputEvent(input_event *data)
bool eventFilter(QObject *object, QEvent *event) override
QEvdevTouchScreenHandlerThread(const QString &device, const QString &spec, QObject *parent=nullptr)
QPointingDevice * touchDevice() const
QEvdevTouchScreenHandler(const QString &device, const QString &spec=QString(), QObject *parent=nullptr)
State
Specifies the state of this event point.
Definition qeventpoint.h:48
\inmodule QtCore
Definition qcoreevent.h:45
@ UpdateRequest
Definition qcoreevent.h:113
QScreen * primaryScreen
the primary (or default) screen of the application.
static QWindow * focusWindow()
Returns the QWindow that receives events tied to focus, such as key events.
static QList< QScreen * > screens()
Returns a list of all the screens associated with the windowing system the application is connected t...
\inmodule QtCore
Definition qhash.h:1145
\inmodule QtCore
Definition qhash.h:1103
const_iterator constEnd() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
Definition qhash.h:1219
const_iterator constBegin() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash.
Definition qhash.h:1215
T take(const Key &key)
Removes the item with the key from the hash and returns the value associated with it.
Definition qhash.h:985
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:1007
qsizetype size() const noexcept
Definition qlist.h:397
bool isEmpty() const noexcept
Definition qlist.h:401
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
\inmodule QtCore
Definition qmutex.h:281
void unlock() noexcept
Unlocks the mutex.
Definition qmutex.h:289
void lock() noexcept
Locks the mutex.
Definition qmutex.h:286
\inmodule QtCore
Definition qobject.h:103
void installEventFilter(QObject *filterObj)
Installs an event filter filterObj on this object.
Definition qobject.cpp:2339
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void removeEventFilter(QObject *obj)
Removes an event filter object obj from this object.
Definition qobject.cpp:2370
QThread * thread() const
Returns the thread in which the object lives.
Definition qobject.cpp:1598
bool moveToThread(QThread *thread QT6_DECL_NEW_OVERLOAD_TAIL)
Changes the thread affinity for this object and its children and returns true on success.
Definition qobject.cpp:1643
Q_WEAK_OVERLOAD void setObjectName(const QString &name)
Sets the object's name to name.
Definition qobject.h:127
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
static QOutputMapping * get()
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:348
\inmodule QtCore\reentrant
Definition qpoint.h:25
static QPointingDevicePrivate * get(QPointingDevice *q)
The QPointingDevice class describes a device from which mouse, touch or tablet events originate.
\inmodule QtCore\reentrant
Definition qrect.h:484
constexpr void moveCenter(const QPointF &p) noexcept
Moves the rectangle, leaving the center point at the given position.
Definition qrect.h:726
constexpr qreal height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:732
constexpr qreal width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:729
\inmodule QtCore\reentrant
Definition qrect.h:30
The QScreen class is used to query screen properties. \inmodule QtGui.
Definition qscreen.h:32
QRect geometry
the screen's geometry in pixels
Definition qscreen.h:45
qreal refreshRate
the approximate vertical refresh rate of the screen in Hz
Definition qscreen.h:64
iterator insert(const T &value)
Definition qset.h:155
\inmodule QtCore
void activated(QSocketDescriptor socket, QSocketNotifier::Type activationEvent, QPrivateSignal)
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:78
constexpr QStringView mid(qsizetype pos, qsizetype n=-1) const noexcept
Returns the substring of length length starting at position start in this object.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QStringList split(const QString &sep, Qt::SplitBehavior behavior=Qt::KeepEmptyParts, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Splits the string into substrings wherever sep occurs, and returns the list of those strings.
Definition qstring.cpp:8218
static QString fromLocal8Bit(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5949
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
The QTransform class specifies 2D transformations of a coordinate system.
Definition qtransform.h:20
QPoint map(const QPoint &p) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
static QTransform fromTranslate(qreal dx, qreal dy)
Creates a matrix which corresponds to a translation of dx along the x axis and dy along the y axis.
bool isIdentity() const
Returns true if the matrix is the identity matrix, otherwise returns false.
Definition qtransform.h:169
The QVector2D class represents a vector or vertex in 2D space.
Definition qvectornd.h:31
QRect geometry
the geometry of the widget relative to its parent and excluding the window frame
Definition qwidget.h:106
static bool handleTouchEvent(QWindow *window, const QPointingDevice *device, const QList< struct TouchPoint > &points, Qt::KeyboardModifiers mods=Qt::NoModifier)
static void registerInputDevice(const QInputDevice *device)
\inmodule QtGui
Definition qwindow.h:63
QSet< QString >::iterator it
else opt state
[0]
void qErrnoWarning(const char *msg,...)
T toNativePixels(const T &value, const C *context)
T toNativeWindowGeometry(const T &value, const C *context)
Combined button and popup list for selecting options.
@ QueuedConnection
#define Q_UNLIKELY(x)
#define QT_READ
#define QT_OPEN
#define QT_CLOSE
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage return DBusPendingCall * pending
#define ABS_CNT
#define ABS_MT_POSITION_X
static bool testBit(long bit, const long *field)
#define ABS_MT_POSITION_Y
#define ABS_MT_POSITION_X
#define ABS_MT_PRESSURE
static bool testBit(long bit, const long *array)
#define NUM_LONGS(bits)
#define SYN_MT_REPORT
#define ABS_MT_SLOT
#define ABS_MT_TRACKING_ID
#define ABS_MT_TOUCH_MAJOR
#define LONG_BITS
#define ABS_MT_POSITION_Y
#define forever
Definition qforeach.h:78
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
GLint GLint GLint GLint GLint x
[0]
GLuint64 key
GLuint GLuint end
GLenum GLuint id
[7]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLfloat GLfloat f
GLenum GLuint buffer
GLuint start
GLuint name
GLfloat n
GLint y
struct _cl_event * event
GLfixed GLfixed GLint GLint GLfixed points
GLdouble s
[6]
Definition qopenglext.h:235
GLenum array
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLenum GLenum GLenum GLenum mapping
GLenum GLenum GLenum input
SSL_CTX int void * arg
#define qUtf16Printable(string)
Definition qstring.h:1543
QScreen * screen
[1]
Definition main.cpp:29
#define QT_CONFIG(feature)
#define emit
unsigned int uint
Definition qtypes.h:34
double qreal
Definition qtypes.h:187
static int toInt(const QChar &qc, int R)
QWidget * win
Definition settings.cpp:6
if(qFloatDistance(a, b)<(1<< 7))
[0]
std::uniform_real_distribution dist(1, 2.5)
[2]
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
QObject::connect nullptr
dialog exec()
aWidget window() -> setWindowTitle("New Window Title")
[2]
QJSValueList args
void update(float pos, float velocity, float timeDelta)
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...