Qt 6.x
The Qt SDK
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 <mutex>
19
20#ifdef Q_OS_FREEBSD
21#include <dev/evdev/input.h>
22#else
23#include <linux/input.h>
24#endif
25
26#ifndef input_event_sec
27#define input_event_sec time.tv_sec
28#endif
29
30#ifndef input_event_usec
31#define input_event_usec time.tv_usec
32#endif
33
34#include <math.h>
35
36#if QT_CONFIG(mtdev)
37extern "C" {
38#include <mtdev.h>
39}
40#endif
41
43
44using namespace Qt::StringLiterals;
45
46Q_LOGGING_CATEGORY(qLcEvdevTouch, "qt.qpa.input")
47Q_LOGGING_CATEGORY(qLcEvents, "qt.qpa.input.events")
48
49/* android (and perhaps some other linux-derived stuff) don't define everything
50 * in linux/input.h, so we'll need to do that ourselves.
51 */
52#ifndef ABS_MT_TOUCH_MAJOR
53#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
54#endif
55#ifndef ABS_MT_POSITION_X
56#define ABS_MT_POSITION_X 0x35 /* Center X ellipse position */
57#endif
58#ifndef ABS_MT_POSITION_Y
59#define ABS_MT_POSITION_Y 0x36 /* Center Y ellipse position */
60#endif
61#ifndef ABS_MT_SLOT
62#define ABS_MT_SLOT 0x2f
63#endif
64#ifndef ABS_CNT
65#define ABS_CNT (ABS_MAX+1)
66#endif
67#ifndef ABS_MT_TRACKING_ID
68#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
69#endif
70#ifndef ABS_MT_PRESSURE
71#define ABS_MT_PRESSURE 0x3a
72#endif
73#ifndef SYN_MT_REPORT
74#define SYN_MT_REPORT 2
75#endif
76
78{
79public:
81
82 void processInputEvent(input_event *data);
83 void assignIds();
84
89
90 struct Contact {
91 int trackingId = -1;
92 int x = 0;
93 int y = 0;
94 int maj = -1;
95 int pressure = 0;
97 };
98 QHash<int, Contact> m_contacts; // The key is a tracking id for type A, slot number for type B.
102
105
106 int findClosestContact(const QHash<int, Contact> &contacts, int x, int y, int *dist);
107 void addTouchPoint(const Contact &contact, QEventPoint::States *combinedStates);
108 void reportPoints();
110
111 QRect screenGeometry() const;
112
127
128 // Touch filtering and prediction are part of the same thing. The default
129 // prediction is 0ms, but sensible results can be achieved by setting it
130 // to, for instance, 16ms.
131 // For filtering to work well, the QPA plugin should provide a dead-steady
132 // implementation of QPlatformWindow::requestUpdate().
135
136 // When filtering is enabled, protect the access to current and last
137 // timeStamp and touchPoints, as these are being read on the gui thread.
139};
140
142 : q(q_ptr),
143 m_lastEventType(-1),
144 m_currentSlot(0),
145 m_timeStamp(0), m_lastTimeStamp(0),
146 hw_range_x_min(0), hw_range_x_max(0),
147 hw_range_y_min(0), hw_range_y_max(0),
148 hw_pressure_min(0), hw_pressure_max(0),
149 m_forceToActiveWindow(false), m_typeB(false), m_singleTouch(false),
150 m_filtered(false), m_prediction(0)
151{
152 for (const QString &arg : args) {
153 if (arg == QStringLiteral("force_window"))
155 else if (arg == QStringLiteral("filtered"))
156 m_filtered = true;
157 else if (arg.startsWith(QStringLiteral("prediction=")))
158 m_prediction = arg.mid(11).toInt();
159 }
160}
161
162#define LONG_BITS (sizeof(long) << 3)
163#define NUM_LONGS(bits) (((bits) + LONG_BITS - 1) / LONG_BITS)
164
165#if !QT_CONFIG(mtdev)
166static inline bool testBit(long bit, const long *array)
167{
168 return (array[bit / LONG_BITS] >> bit % LONG_BITS) & 1;
169}
170#endif
171
173 : QObject(parent), m_notify(nullptr), m_fd(-1), d(nullptr), m_device(nullptr)
174#if QT_CONFIG(mtdev)
175 , m_mtdev(nullptr)
176#endif
177{
178 setObjectName("Evdev Touch Handler"_L1);
179
180 const QStringList args = spec.split(u':');
181 int rotationAngle = 0;
182 bool invertx = false;
183 bool inverty = false;
184 for (int i = 0; i < args.size(); ++i) {
185 if (args.at(i).startsWith("rotate"_L1)) {
186 QString rotateArg = args.at(i).section(u'=', 1, 1);
187 bool ok;
188 uint argValue = rotateArg.toUInt(&ok);
189 if (ok) {
190 switch (argValue) {
191 case 90:
192 case 180:
193 case 270:
194 rotationAngle = argValue;
195 default:
196 break;
197 }
198 }
199 } else if (args.at(i) == "invertx"_L1) {
200 invertx = true;
201 } else if (args.at(i) == "inverty"_L1) {
202 inverty = true;
203 }
204 }
205
206 qCDebug(qLcEvdevTouch, "evdevtouch: Using device %ls", qUtf16Printable(device));
207
208 m_fd = QT_OPEN(device.toLocal8Bit().constData(), O_RDONLY | O_NDELAY, 0);
209
210 if (m_fd >= 0) {
211 m_notify = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
213 } else {
214 qErrnoWarning("evdevtouch: Cannot open input device %ls", qUtf16Printable(device));
215 return;
216 }
217
218#if QT_CONFIG(mtdev)
219 m_mtdev = static_cast<mtdev *>(calloc(1, sizeof(mtdev)));
220 int mtdeverr = mtdev_open(m_mtdev, m_fd);
221 if (mtdeverr) {
222 qWarning("evdevtouch: mtdev_open failed: %d", mtdeverr);
223 QT_CLOSE(m_fd);
224 free(m_mtdev);
225 return;
226 }
227#endif
228
229 d = new QEvdevTouchScreenData(this, args);
230
231#if QT_CONFIG(mtdev)
232 const char *mtdevStr = "(mtdev)";
233 d->m_typeB = true;
234#else
235 const char *mtdevStr = "";
236 long absbits[NUM_LONGS(ABS_CNT)];
237 if (ioctl(m_fd, EVIOCGBIT(EV_ABS, sizeof(absbits)), absbits) >= 0) {
238 d->m_typeB = testBit(ABS_MT_SLOT, absbits);
240 }
241#endif
242
243 d->deviceNode = device;
244 qCDebug(qLcEvdevTouch,
245 "evdevtouch: %ls: Protocol type %c %s (%s), filtered=%s",
247 d->m_typeB ? 'B' : 'A', mtdevStr,
248 d->m_singleTouch ? "single" : "multi",
249 d->m_filtered ? "yes" : "no");
250 if (d->m_filtered)
251 qCDebug(qLcEvdevTouch, " - prediction=%d", d->m_prediction);
252
253 input_absinfo absInfo;
254 memset(&absInfo, 0, sizeof(input_absinfo));
255 bool has_x_range = false, has_y_range = false;
256
257 if (ioctl(m_fd, EVIOCGABS((d->m_singleTouch ? ABS_X : ABS_MT_POSITION_X)), &absInfo) >= 0) {
258 qCDebug(qLcEvdevTouch, "evdevtouch: %ls: min X: %d max X: %d", qUtf16Printable(device),
259 absInfo.minimum, absInfo.maximum);
260 d->hw_range_x_min = absInfo.minimum;
261 d->hw_range_x_max = absInfo.maximum;
262 has_x_range = true;
263 }
264
265 if (ioctl(m_fd, EVIOCGABS((d->m_singleTouch ? ABS_Y : ABS_MT_POSITION_Y)), &absInfo) >= 0) {
266 qCDebug(qLcEvdevTouch, "evdevtouch: %ls: min Y: %d max Y: %d", qUtf16Printable(device),
267 absInfo.minimum, absInfo.maximum);
268 d->hw_range_y_min = absInfo.minimum;
269 d->hw_range_y_max = absInfo.maximum;
270 has_y_range = true;
271 }
272
273 if (!has_x_range || !has_y_range)
274 qWarning("evdevtouch: %ls: Invalid ABS limits, behavior unspecified", qUtf16Printable(device));
275
276 if (ioctl(m_fd, EVIOCGABS(ABS_PRESSURE), &absInfo) >= 0) {
277 qCDebug(qLcEvdevTouch, "evdevtouch: %ls: min pressure: %d max pressure: %d", qUtf16Printable(device),
278 absInfo.minimum, absInfo.maximum);
279 if (absInfo.maximum > absInfo.minimum) {
280 d->hw_pressure_min = absInfo.minimum;
281 d->hw_pressure_max = absInfo.maximum;
282 }
283 }
284
285 char name[1024];
286 if (ioctl(m_fd, EVIOCGNAME(sizeof(name) - 1), name) >= 0) {
288 qCDebug(qLcEvdevTouch, "evdevtouch: %ls: device name: %s", qUtf16Printable(device), name);
289 }
290
291 // Fix up the coordinate ranges for am335x in case the kernel driver does not have them fixed.
292 if (d->hw_name == "ti-tsc"_L1) {
293 if (d->hw_range_x_min == 0 && d->hw_range_x_max == 4095) {
294 d->hw_range_x_min = 165;
295 d->hw_range_x_max = 4016;
296 }
297 if (d->hw_range_y_min == 0 && d->hw_range_y_max == 4095) {
298 d->hw_range_y_min = 220;
299 d->hw_range_y_max = 3907;
300 }
301 qCDebug(qLcEvdevTouch, "evdevtouch: found ti-tsc, overriding: min X: %d max X: %d min Y: %d max Y: %d",
303 }
304
305 bool grabSuccess = !ioctl(m_fd, EVIOCGRAB, (void *) 1);
306 if (grabSuccess)
307 ioctl(m_fd, EVIOCGRAB, (void *) 0);
308 else
309 qWarning("evdevtouch: The device is grabbed by another process. No events will be read.");
310
311 if (rotationAngle)
312 d->m_rotate = QTransform::fromTranslate(0.5, 0.5).rotate(rotationAngle).translate(-0.5, -0.5);
313
314 if (invertx)
315 d->m_rotate *= QTransform::fromTranslate(0.5, 0.5).scale(-1.0, 1.0).translate(-0.5, -0.5);
316
317 if (inverty)
318 d->m_rotate *= QTransform::fromTranslate(0.5, 0.5).scale(1.0, -1.0).translate(-0.5, -0.5);
319
321 if (mapping->load()) {
322 d->m_screenName = mapping->screenNameForDeviceNode(d->deviceNode);
323 if (!d->m_screenName.isEmpty())
324 qCDebug(qLcEvdevTouch, "evdevtouch: Mapping device %ls to screen %ls",
326 }
327
328 registerPointingDevice();
329}
330
332{
333#if QT_CONFIG(mtdev)
334 if (m_mtdev) {
335 mtdev_close(m_mtdev);
336 free(m_mtdev);
337 }
338#endif
339
340 if (m_fd >= 0)
341 QT_CLOSE(m_fd);
342
343 delete d;
344
345 unregisterPointingDevice();
346}
347
349{
350 return d && d->m_filtered;
351}
352
354{
355 return m_device;
356}
357
359{
360 ::input_event buffer[32];
361 int events = 0;
362
363#if QT_CONFIG(mtdev)
364 forever {
365 do {
366 events = mtdev_get(m_mtdev, m_fd, buffer, sizeof(buffer) / sizeof(::input_event));
367 // keep trying mtdev_get if we get interrupted. note that we do not
368 // (and should not) handle EAGAIN; EAGAIN means that reading would
369 // block and we'll get back here later to try again anyway.
370 } while (events == -1 && errno == EINTR);
371
372 // 0 events is EOF, -1 means error, handle both in the same place
373 if (events <= 0)
374 goto err;
375
376 // process our shiny new events
377 for (int i = 0; i < events; ++i)
379
380 // and try to get more
381 }
382#else
383 int n = 0;
384 for (; ;) {
385 events = QT_READ(m_fd, reinterpret_cast<char*>(buffer) + n, sizeof(buffer) - n);
386 if (events <= 0)
387 goto err;
388 n += events;
389 if (n % sizeof(::input_event) == 0)
390 break;
391 }
392
393 n /= sizeof(::input_event);
394
395 for (int i = 0; i < n; ++i)
397#endif
398 return;
399
400err:
401 if (!events) {
402 qWarning("evdevtouch: Got EOF from input device");
403 return;
404 } else if (events < 0) {
405 if (errno != EINTR && errno != EAGAIN) {
406 qErrnoWarning("evdevtouch: Could not read from input device");
407 if (errno == ENODEV) { // device got disconnected -> stop reading
408 delete m_notify;
409 m_notify = nullptr;
410
411 QT_CLOSE(m_fd);
412 m_fd = -1;
413
414 unregisterPointingDevice();
415 }
416 return;
417 }
418 }
419}
420
421void QEvdevTouchScreenHandler::registerPointingDevice()
422{
423 if (m_device)
424 return;
425
426 static int id = 1;
430
431 // TODO get evdev ID instead of an incremeting number; set USB ID too
432 m_device = new QPointingDevice(d->hw_name, id++,
434 caps, 16, 0);
435
436 auto geom = d->screenGeometry();
437 if (!geom.isNull())
439
441}
442
472void QEvdevTouchScreenHandler::unregisterPointingDevice()
473{
474 if (!m_device)
475 return;
476
479 m_device->deleteLater();
480 } else {
481 delete m_device;
482 }
483 m_device = nullptr;
484}
485
486void QEvdevTouchScreenData::addTouchPoint(const Contact &contact, QEventPoint::States *combinedStates)
487{
489 tp.id = contact.trackingId;
490 tp.state = contact.state;
491 *combinedStates |= tp.state;
492
493 // Store the HW coordinates for now, will be updated later.
494 tp.area = QRectF(0, 0, contact.maj, contact.maj);
495 tp.area.moveCenter(QPoint(contact.x, contact.y));
496 tp.pressure = contact.pressure;
497
498 // Get a normalized position in range 0..1.
501
502 if (!m_rotate.isIdentity())
504
505 tp.rawPositions.append(QPointF(contact.x, contact.y));
506
508}
509
511{
512 if (data->type == EV_ABS) {
513
514 if (data->code == ABS_MT_POSITION_X || (m_singleTouch && data->code == ABS_X)) {
516 if (m_singleTouch)
518 if (m_typeB) {
522 }
523 } else if (data->code == ABS_MT_POSITION_Y || (m_singleTouch && data->code == ABS_Y)) {
525 if (m_singleTouch)
527 if (m_typeB) {
531 }
532 } else if (data->code == ABS_MT_TRACKING_ID) {
534 if (m_typeB) {
535 if (m_currentData.trackingId == -1) {
537 } else {
540 }
541 }
542 } else if (data->code == ABS_MT_TOUCH_MAJOR) {
543 m_currentData.maj = data->value;
544 if (data->value == 0)
546 if (m_typeB)
548 } else if (data->code == ABS_PRESSURE || data->code == ABS_MT_PRESSURE) {
549 if (Q_UNLIKELY(qLcEvents().isDebugEnabled()))
550 qCDebug(qLcEvents, "EV_ABS code 0x%x: pressure %d; bounding to [%d,%d]",
551 data->code, data->value, hw_pressure_min, hw_pressure_max);
553 if (m_typeB || m_singleTouch)
555 } else if (data->code == ABS_MT_SLOT) {
556 m_currentSlot = data->value;
557 }
558
559 } else if (data->type == EV_KEY && !m_typeB) {
560 if (data->code == BTN_TOUCH && data->value == 0)
562 } else if (data->type == EV_SYN && data->code == SYN_MT_REPORT && m_lastEventType != EV_SYN) {
563
564 // If there is no tracking id, one will be generated later.
565 // Until that use a temporary key.
567 if (key == -1)
568 key = m_contacts.size();
569
572
573 } else if (data->type == EV_SYN && data->code == SYN_REPORT) {
574
575 // Ensure valid IDs even when the driver does not report ABS_MT_TRACKING_ID.
576 if (!m_contacts.isEmpty() && m_contacts.constBegin().value().trackingId == -1)
577 assignIds();
578
579 std::unique_lock<QMutex> locker;
580 if (m_filtered)
581 locker = std::unique_lock<QMutex>{m_mutex};
582
583 // update timestamps
585 m_timeStamp = data->input_event_sec + data->input_event_usec / 1000000.0;
586
589 QEventPoint::States combinedStates;
590 bool hasPressure = false;
591
592 for (auto it = m_contacts.begin(), end = m_contacts.end(); it != end; /*erasing*/) {
593 Contact &contact(it.value());
594
595 if (!contact.state) {
596 ++it;
597 continue;
598 }
599
600 int key = m_typeB ? it.key() : contact.trackingId;
601 if (!m_typeB && m_lastContacts.contains(key)) {
602 const Contact &prev(m_lastContacts.value(key));
603 if (contact.state == QEventPoint::State::Released) {
604 // Copy over the previous values for released points, just in case.
605 contact.x = prev.x;
606 contact.y = prev.y;
607 contact.maj = prev.maj;
608 } else {
609 contact.state = (prev.x == contact.x && prev.y == contact.y)
611 }
612 }
613
614 // Avoid reporting a contact in released state more than once.
616 && !m_lastContacts.contains(key)) {
617 it = m_contacts.erase(it);
618 continue;
619 }
620
621 if (contact.pressure)
622 hasPressure = true;
623
624 addTouchPoint(contact, &combinedStates);
625 ++it;
626 }
627
628 // Now look for contacts that have disappeared since the last sync.
629 for (auto it = m_lastContacts.begin(), end = m_lastContacts.end(); it != end; ++it) {
630 Contact &contact(it.value());
631 int key = m_typeB ? it.key() : contact.trackingId;
632 if (m_typeB) {
633 if (contact.trackingId != m_contacts[key].trackingId && contact.state) {
635 addTouchPoint(contact, &combinedStates);
636 }
637 } else {
638 if (!m_contacts.contains(key)) {
640 addTouchPoint(contact, &combinedStates);
641 }
642 }
643 }
644
645 // Remove contacts that have just been reported as released.
646 for (auto it = m_contacts.begin(), end = m_contacts.end(); it != end; /*erasing*/) {
647 Contact &contact(it.value());
648
649 if (!contact.state) {
650 ++it;
651 continue;
652 }
653
654 if (contact.state == QEventPoint::State::Released) {
655 if (m_typeB) {
657 } else {
658 it = m_contacts.erase(it);
659 continue;
660 }
661 } else {
663 }
664 ++it;
665 }
666
668 if (!m_typeB && !m_singleTouch)
669 m_contacts.clear();
670
671
672 if (!m_touchPoints.isEmpty() && (hasPressure || combinedStates != QEventPoint::State::Stationary))
673 reportPoints();
674 }
675
676 m_lastEventType = data->type;
677}
678
680{
681 int minDist = -1, id = -1;
682 for (QHash<int, Contact>::const_iterator it = contacts.constBegin(), ite = contacts.constEnd();
683 it != ite; ++it) {
684 const Contact &contact(it.value());
685 int dx = x - contact.x;
686 int dy = y - contact.y;
687 int dist = dx * dx + dy * dy;
688 if (minDist == -1 || dist < minDist) {
689 minDist = dist;
690 id = contact.trackingId;
691 }
692 }
693 if (dist)
694 *dist = minDist;
695 return id;
696}
697
699{
700 QHash<int, Contact> candidates = m_lastContacts, pending = m_contacts, newContacts;
701 int maxId = -1;
702 QHash<int, Contact>::iterator it, ite, bestMatch;
703 while (!pending.isEmpty() && !candidates.isEmpty()) {
704 int bestDist = -1, bestId = 0;
705 for (it = pending.begin(), ite = pending.end(); it != ite; ++it) {
706 int dist;
707 int id = findClosestContact(candidates, it->x, it->y, &dist);
708 if (id >= 0 && (bestDist == -1 || dist < bestDist)) {
709 bestDist = dist;
710 bestId = id;
711 bestMatch = it;
712 }
713 }
714 if (bestDist >= 0) {
715 bestMatch->trackingId = bestId;
716 newContacts.insert(bestId, *bestMatch);
717 candidates.remove(bestId);
718 pending.erase(bestMatch);
719 if (bestId > maxId)
720 maxId = bestId;
721 }
722 }
723 if (candidates.isEmpty()) {
724 for (it = pending.begin(), ite = pending.end(); it != ite; ++it) {
725 it->trackingId = ++maxId;
726 newContacts.insert(it->trackingId, *it);
727 }
728 }
729 m_contacts = newContacts;
730}
731
733{
737 }
738
739 // Now it becomes tricky. Traditionally we picked the primaryScreen()
740 // and were done with it. But then, enter multiple screens, and
741 // suddenly it was all broken.
742 //
743 // For now we only support the display configuration of the KMS/DRM
744 // backends of eglfs. See QOutputMapping.
745 //
746 // The good news it that once winRect refers to the correct screen
747 // geometry in the full virtual desktop space, there is nothing else
748 // left to do since qguiapp will handle the rest.
750 if (!m_screenName.isEmpty()) {
751 if (!m_screen) {
753 for (QScreen *s : screens) {
754 if (s->name() == m_screenName) {
755 m_screen = s;
756 break;
757 }
758 }
759 }
760 if (m_screen)
762 }
764}
765
767{
768 QRect winRect = screenGeometry();
769 if (winRect.isNull())
770 return;
771
772 const int hw_w = hw_range_x_max - hw_range_x_min;
773 const int hw_h = hw_range_y_max - hw_range_y_min;
774
775 // Map the coordinates based on the normalized position. QPA expects 'area'
776 // to be in screen coordinates.
777 const int pointCount = m_touchPoints.size();
778 for (int i = 0; i < pointCount; ++i) {
780
781 // Generate a screen position that is always inside the active window
782 // or the primary screen. Even though we report this as a QRectF, internally
783 // Qt uses QRect/QPoint so we need to bound the size to winRect.size() - QSize(1, 1)
784 const qreal wx = winRect.left() + tp.normalPosition.x() * (winRect.width() - 1);
785 const qreal wy = winRect.top() + tp.normalPosition.y() * (winRect.height() - 1);
786 const qreal sizeRatio = (winRect.width() + winRect.height()) / qreal(hw_w + hw_h);
787 if (tp.area.width() == -1) // touch major was not provided
788 tp.area = QRectF(0, 0, 8, 8);
789 else
790 tp.area = QRectF(0, 0, tp.area.width() * sizeRatio, tp.area.height() * sizeRatio);
791 tp.area.moveCenter(QPointF(wx, wy));
792
793 // Calculate normalized pressure.
796 else
798
799 if (Q_UNLIKELY(qLcEvents().isDebugEnabled()))
800 qCDebug(qLcEvents) << "reporting" << tp;
801 }
802
803 // Let qguiapp pick the target window.
804 if (m_filtered)
805 emit q->touchPointsUpdated();
806 else
808}
809
811 : QDaemonThread(parent), m_device(device), m_spec(spec), m_handler(nullptr), m_touchDeviceRegistered(false)
812 , m_touchUpdatePending(false)
813 , m_filterWindow(nullptr)
814 , m_touchRate(-1)
815{
816 start();
817}
818
820{
821 quit();
822 wait();
823}
824
826{
827 m_handler = new QEvdevTouchScreenHandler(m_device, m_spec);
828
829 if (m_handler->isFiltered())
831
832 // Report the registration to the parent thread by invoking the method asynchronously
833 QMetaObject::invokeMethod(this, "notifyTouchDeviceRegistered", Qt::QueuedConnection);
834
835 exec();
836
837 delete m_handler;
838 m_handler = nullptr;
839}
840
842{
843 return m_touchDeviceRegistered;
844}
845
846void QEvdevTouchScreenHandlerThread::notifyTouchDeviceRegistered()
847{
848 m_touchDeviceRegistered = true;
850}
851
853{
855 if (window != m_filterWindow) {
856 if (m_filterWindow)
857 m_filterWindow->removeEventFilter(this);
858 m_filterWindow = window;
859 if (m_filterWindow)
860 m_filterWindow->installEventFilter(this);
861 }
862 if (m_filterWindow) {
863 m_touchUpdatePending = true;
864 m_filterWindow->requestUpdate();
865 }
866}
867
869{
870 if (m_touchUpdatePending && object == m_filterWindow && event->type() == QEvent::UpdateRequest) {
871 m_touchUpdatePending = false;
872 filterAndSendTouchPoints();
873 }
874 return false;
875}
876
877void QEvdevTouchScreenHandlerThread::filterAndSendTouchPoints()
878{
879 QRect winRect = m_handler->d->screenGeometry();
880 if (winRect.isNull())
881 return;
882
883 float vsyncDelta = 1.0f / QGuiApplication::primaryScreen()->refreshRate();
884
885 QHash<int, FilteredTouchPoint> filteredPoints;
886
887 m_handler->d->m_mutex.lock();
888
889 double time = m_handler->d->m_timeStamp;
890 double lastTime = m_handler->d->m_lastTimeStamp;
891 double touchDelta = time - lastTime;
892 if (m_touchRate < 0 || touchDelta > vsyncDelta) {
893 // We're at the very start, with nothing to go on, so make a guess
894 // that the touch rate will be somewhere in the range of half a vsync.
895 // This doesn't have to be accurate as we will calibrate it over time,
896 // but it gives us a better starting point so calibration will be
897 // slightly quicker. If, on the other hand, we already have an
898 // estimate, we'll leave it as is and keep it.
899 if (m_touchRate < 0)
900 m_touchRate = (1.0 / QGuiApplication::primaryScreen()->refreshRate()) / 2.0;
901
902 } else {
903 // Update our estimate for the touch rate. We're making the assumption
904 // that this value will be mostly accurate with the occasional bump,
905 // so we're weighting the existing value high compared to the update.
906 const double ratio = 0.9;
907 m_touchRate = sqrt(m_touchRate * m_touchRate * ratio + touchDelta * touchDelta * (1.0 - ratio));
908 }
909
912
913 m_handler->d->m_mutex.unlock();
914
915 for (int i=0; i<points.size(); ++i) {
918 FilteredTouchPoint f;
919
921 ltp.id = -1;
922 for (int j=0; j<lastPoints.size(); ++j) {
923 if (lastPoints.at(j).id == tp.id) {
924 ltp = lastPoints.at(j);
925 break;
926 }
927 }
928
929 QPointF velocity;
930 if (lastTime != 0 && ltp.id >= 0)
931 velocity = (pos - ltp.normalPosition) / m_touchRate;
932 if (m_filteredPoints.contains(tp.id)) {
933 f = m_filteredPoints.take(tp.id);
934 f.x.update(pos.x(), velocity.x(), vsyncDelta);
935 f.y.update(pos.y(), velocity.y(), vsyncDelta);
936 pos = QPointF(f.x.position(), f.y.position());
937 } else {
938 f.x.initialize(pos.x(), velocity.x());
939 f.y.initialize(pos.y(), velocity.y());
940 // Make sure the first instance of a touch point we send has the
941 // 'pressed' state.
944 }
945
946 tp.velocity = QVector2D(f.x.velocity() * winRect.width(), f.y.velocity() * winRect.height());
947
948 qreal filteredNormalizedX = f.x.position() + f.x.velocity() * m_handler->d->m_prediction / 1000.0;
949 qreal filteredNormalizedY = f.y.position() + f.y.velocity() * m_handler->d->m_prediction / 1000.0;
950
951 // Clamp to the screen
952 tp.normalPosition = QPointF(qBound<qreal>(0, filteredNormalizedX, 1),
953 qBound<qreal>(0, filteredNormalizedY, 1));
954
955 qreal x = winRect.x() + (tp.normalPosition.x() * (winRect.width() - 1));
956 qreal y = winRect.y() + (tp.normalPosition.y() * (winRect.height() - 1));
957
958 tp.area.moveCenter(QPointF(x, y));
959
960 // Store the touch point for later so we can release it if we've
961 // missed the actual release between our last update and this.
962 f.touchPoint = tp;
963
964 // Don't store the point for future reference if it is a release.
966 filteredPoints[tp.id] = f;
967 }
968
969 for (QHash<int, FilteredTouchPoint>::const_iterator it = m_filteredPoints.constBegin(), end = m_filteredPoints.constEnd(); it != end; ++it) {
970 const FilteredTouchPoint &f = it.value();
973 tp.velocity = QVector2D();
974 points.append(tp);
975 }
976
977 m_filteredPoints = filteredPoints;
978
980 m_handler->touchDevice(),
981 points);
982}
983
984
986
987#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:49
\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:1135
\inmodule QtCore
Definition qhash.h:1093
\inmodule QtCore
Definition qhash.h:818
bool remove(const Key &key)
Removes the item that has the key from the hash.
Definition qhash.h:956
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:1209
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:1205
T take(const Key &key)
Removes the item with the key from the hash and returns the value associated with it.
Definition qhash.h:975
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:991
bool isEmpty() const noexcept
Returns true if the hash contains no items; otherwise returns false.
Definition qhash.h:926
void setAvailableVirtualGeometry(QRect a)
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
\inmodule QtCore
Definition qmutex.h:285
void unlock() noexcept
Unlocks the mutex.
Definition qmutex.h:293
void lock() noexcept
Locks the mutex.
Definition qmutex.h:290
\inmodule QtCore
Definition qobject.h:90
void installEventFilter(QObject *filterObj)
Installs an event filter filterObj on this object.
Definition qobject.cpp:2269
void moveToThread(QThread *thread)
Changes the thread affinity for this object and its children.
Definition qobject.cpp:1606
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
void removeEventFilter(QObject *obj)
Removes an event filter object obj from this object.
Definition qobject.cpp:2300
QThread * thread() const
Returns the thread in which the object lives.
Definition qobject.cpp:1561
Q_WEAK_OVERLOAD void setObjectName(const QString &name)
Sets the object's name to name.
Definition qobject.h:114
void deleteLater()
\threadsafe
Definition qobject.cpp:2352
static QOutputMapping * get()
\inmodule QtCore\reentrant
Definition qpoint.h:214
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:333
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:338
\inmodule QtCore\reentrant
Definition qpoint.h:23
\inmodule QtCore
Definition qpointer.h:18
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:483
constexpr void moveCenter(const QPointF &p) noexcept
Moves the rectangle, leaving the center point at the given position.
Definition qrect.h:712
constexpr qreal height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:718
constexpr qreal width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:715
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr int height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:238
constexpr bool isNull() const noexcept
Returns true if the rectangle is a null rectangle, otherwise returns false.
Definition qrect.h:163
constexpr int top() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:175
constexpr int left() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:172
constexpr int x() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:184
constexpr int width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:235
constexpr int y() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:187
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
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
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:7956
static QString fromLocal8Bit(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5788
uint toUInt(bool *ok=nullptr, int base=10) const
Returns the string converted to an {unsigned int} using base base, which is 10 by default and must be...
Definition qstring.h:662
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
The QTransform class specifies 2D transformations of a coordinate system.
Definition qtransform.h:20
QTransform & rotate(qreal a, Qt::Axis axis=Qt::ZAxis, qreal distanceToPlane=1024.0f)
QTransform & scale(qreal sx, qreal sy)
Scales the coordinate system by sx horizontally and sy vertically, and returns a reference to the mat...
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.
QTransform & translate(qreal dx, qreal dy)
Moves the coordinate system dx along the x axis and dy along the y axis, and returns a reference to t...
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:162
#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]
GLfloat GLfloat f
GLenum GLuint buffer
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint start
GLuint name
GLfloat n
GLint y
struct _cl_event * event
GLfixed GLfixed GLint GLint GLfixed points
GLenum array
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLdouble s
[6]
Definition qopenglext.h:235
GLenum GLenum GLenum GLenum mapping
GLenum GLenum GLenum input
SSL_CTX int(*) void arg)
#define qUtf16Printable(string)
Definition qstring.h:1403
#define QStringLiteral(str)
QScreen * screen
[1]
Definition main.cpp:29
#define QT_CONFIG(feature)
#define emit
unsigned int uint
Definition qtypes.h:29
double qreal
Definition qtypes.h:92
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
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...
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent