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
qmultitouch_mac.mm
Go to the documentation of this file.
1// Copyright (C) 2016 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// Qt-Security score:significant reason:default
4
5#include <AppKit/AppKit.h>
6
9#include "qcocoascreen.h"
10#include <private/qpointingdevice_p.h>
11
13
14using namespace Qt::StringLiterals;
15
16Q_CONSTINIT QHash<qint64, QCocoaTouch*> QCocoaTouch::_currentTouches;
17Q_CONSTINIT QHash<quint64, QPointingDevice*> QCocoaTouch::_touchDevices;
18Q_CONSTINIT QPointF QCocoaTouch::_screenReferencePos;
19Q_CONSTINIT QPointF QCocoaTouch::_trackpadReferencePos;
20int QCocoaTouch::_idAssignmentCount = 0;
21int QCocoaTouch::_touchCount = 0;
22bool QCocoaTouch::_updateInternalStateOnly = true;
23
24QCocoaTouch::QCocoaTouch(NSTouch *nstouch)
25{
26 if (_currentTouches.size() == 0)
27 _idAssignmentCount = 0;
28
29 _touchPoint.id = _idAssignmentCount++;
30 _touchPoint.pressure = 1.0;
31 _identity = qint64([nstouch identity]);
32 _currentTouches.insert(_identity, this);
33 updateTouchData(nstouch, NSTouchPhaseBegan);
34}
35
36QCocoaTouch::~QCocoaTouch()
37{
38 _currentTouches.remove(_identity);
39}
40
41void QCocoaTouch::updateTouchData(NSTouch *nstouch, NSTouchPhase phase)
42{
43 _touchPoint.state = toTouchPointState(phase);
44
45 // From the normalized position on the trackpad, calculate
46 // where on screen the touchpoint should be according to the
47 // reference position:
48 NSPoint npos = [nstouch normalizedPosition];
49 QPointF qnpos = QPointF(npos.x, 1 - npos.y);
50 _touchPoint.normalPosition = qnpos;
51
52 if (_touchPoint.id == 0 && phase == NSTouchPhaseBegan) {
53 _trackpadReferencePos = qnpos;
54 _screenReferencePos = QCocoaScreen::mapFromNative([NSEvent mouseLocation]);
55 }
56
57 QPointF screenPos = _screenReferencePos;
58
59 NSSize dsize = [nstouch deviceSize];
60 float ppiX = (qnpos.x() - _trackpadReferencePos.x()) * dsize.width;
61 float ppiY = (qnpos.y() - _trackpadReferencePos.y()) * dsize.height;
62 QPointF relativePos = _trackpadReferencePos - QPointF(ppiX, ppiY);
63 screenPos -= relativePos;
64 // Mac does not support area touch, only points, hence set width/height to 1.
65 // The touch point is supposed to be in the center of '_touchPoint.area', and
66 // since width/height is 1 it means we must subtract 0.5 from x and y.
67 screenPos.rx() -= 0.5;
68 screenPos.ry() -= 0.5;
69 _touchPoint.area = QRectF(screenPos, QSize(1, 1));
70}
71
72QCocoaTouch *QCocoaTouch::findQCocoaTouch(NSTouch *nstouch)
73{
74 qint64 identity = qint64([nstouch identity]);
75 if (_currentTouches.contains(identity))
76 return _currentTouches.value(identity);
77 return nullptr;
78}
79
80QEventPoint::State QCocoaTouch::toTouchPointState(NSTouchPhase nsState)
81{
82 QEventPoint::State qtState = QEventPoint::State::Released;
83 switch (nsState) {
84 case NSTouchPhaseBegan:
85 qtState = QEventPoint::State::Pressed;
86 break;
87 case NSTouchPhaseMoved:
88 qtState = QEventPoint::State::Updated;
89 break;
90 case NSTouchPhaseStationary:
91 qtState = QEventPoint::State::Stationary;
92 break;
93 case NSTouchPhaseEnded:
94 case NSTouchPhaseCancelled:
95 qtState = QEventPoint::State::Released;
96 break;
97 default:
98 break;
99 }
100 return qtState;
101}
102
103QList<QWindowSystemInterface::TouchPoint>
104QCocoaTouch::getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch)
105{
106 QMap<int, QWindowSystemInterface::TouchPoint> touchPoints;
107 NSSet *ended = [event touchesMatchingPhase:NSTouchPhaseEnded | NSTouchPhaseCancelled inView:nil];
108 NSSet *active = [event
109 touchesMatchingPhase:NSTouchPhaseBegan | NSTouchPhaseMoved | NSTouchPhaseStationary
110 inView:nil];
111 _touchCount = [active count];
112
113 // First: remove touches that were ended by the user. If we are
114 // currently not accepting single touches, a corresponding 'begin'
115 // has never been send to the app for these events.
116 // So should therefore not send the following removes either.
117
118 for (int i=0; i<int([ended count]); ++i) {
119 NSTouch *touch = [[ended allObjects] objectAtIndex:i];
120 QCocoaTouch *qcocoaTouch = findQCocoaTouch(touch);
121 if (qcocoaTouch) {
122 qcocoaTouch->updateTouchData(touch, [touch phase]);
123 if (!_updateInternalStateOnly)
124 touchPoints.insert(qcocoaTouch->_touchPoint.id, qcocoaTouch->_touchPoint);
125 delete qcocoaTouch;
126 }
127 }
128
129 bool wasUpdateInternalStateOnly = _updateInternalStateOnly;
130 _updateInternalStateOnly = !acceptSingleTouch && _touchCount < 2;
131
132 // Next: update, or create, existing touches.
133 // We always keep track of all touch points, even
134 // when not accepting single touches.
135
136 for (int i=0; i<int([active count]); ++i) {
137 NSTouch *touch = [[active allObjects] objectAtIndex:i];
138 QCocoaTouch *qcocoaTouch = findQCocoaTouch(touch);
139 if (!qcocoaTouch)
140 qcocoaTouch = new QCocoaTouch(touch);
141 else
142 qcocoaTouch->updateTouchData(touch, wasUpdateInternalStateOnly ? NSTouchPhaseBegan : [touch phase]);
143 if (!_updateInternalStateOnly)
144 touchPoints.insert(qcocoaTouch->_touchPoint.id, qcocoaTouch->_touchPoint);
145 }
146
147 // Next: sadly, we need to check that our touch hash is in
148 // sync with cocoa. This is typically not the case after a system
149 // gesture happened (like a four-finger-swipe to show expose).
150
151 if (_touchCount != _currentTouches.size()) {
152 // Remove all instances, and basically start from scratch:
153 touchPoints.clear();
154 // Deleting touch points will remove them from current touches,
155 // so we make a copy of the touches before iterating them.
156 const auto currentTouchesSnapshot = _currentTouches;
157 for (QCocoaTouch *qcocoaTouch : currentTouchesSnapshot) {
158 if (!_updateInternalStateOnly) {
159 qcocoaTouch->_touchPoint.state = QEventPoint::State::Released;
160 touchPoints.insert(qcocoaTouch->_touchPoint.id, qcocoaTouch->_touchPoint);
161 }
162 delete qcocoaTouch;
163 }
164 _currentTouches.clear();
165 _updateInternalStateOnly = !acceptSingleTouch;
166 return touchPoints.values();
167 }
168
169 // Finally: If this call _started_ to reject single
170 // touches, we need to fake a release for the remaining
171 // touch now (and refake a begin for it later, if needed).
172
173 if (_updateInternalStateOnly && !wasUpdateInternalStateOnly && !_currentTouches.isEmpty()) {
174 QCocoaTouch *qcocoaTouch = _currentTouches.cbegin().value();
175 qcocoaTouch->_touchPoint.state = QEventPoint::State::Released;
176 touchPoints.insert(qcocoaTouch->_touchPoint.id, qcocoaTouch->_touchPoint);
177 // Since this last touch also will end up being the first
178 // touch (if the user adds a second finger without lifting
179 // the first), we promote it to be the primary touch:
180 qcocoaTouch->_touchPoint.id = 0;
181 _idAssignmentCount = 1;
182 }
183
184 return touchPoints.values();
185}
186
187QPointingDevice *QCocoaTouch::getTouchDevice(QInputDevice::DeviceType type, quint64 id)
188{
189 QPointingDevice *ret = _touchDevices.value(id);
190 if (!ret) {
191 ret = new QPointingDevice(type == QInputDevice::DeviceType::TouchScreen ? "touchscreen"_L1 : "trackpad"_L1,
192 id, type, QPointingDevice::PointerType::Finger,
193 QInputDevice::Capability::Position |
194 QInputDevice::Capability::NormalizedPosition |
195 QInputDevice::Capability::MouseEmulation,
196 10, 0);
197 QWindowSystemInterface::registerInputDevice(ret);
198 _touchDevices.insert(id, ret);
199 }
200 return ret;
201}
202
203QT_END_NAMESPACE