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 | |
4 | #include "qlibinputtouch_p.h" |
5 | #include "qoutputmapping_p.h" |
6 | #include <libinput.h> |
7 | #include <QtGui/QGuiApplication> |
8 | #include <QtGui/QPointingDevice> |
9 | #include <QtGui/QScreen> |
10 | #include <QtGui/QPointingDevice> |
11 | #include <QtGui/private/qhighdpiscaling_p.h> |
12 | #include <QtGui/private/qpointingdevice_p.h> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | Q_DECLARE_LOGGING_CATEGORY(qLcLibInput) |
17 | Q_LOGGING_CATEGORY(qLcLibInputEvents, "qt.qpa.input.events" ) |
18 | |
19 | QWindowSystemInterface::TouchPoint *QLibInputTouch::DeviceState::point(int32_t slot) |
20 | { |
21 | const int id = qMax(a: 0, b: slot); |
22 | |
23 | for (int i = 0; i < m_points.size(); ++i) |
24 | if (m_points.at(i).id == id) |
25 | return &m_points[i]; |
26 | |
27 | return nullptr; |
28 | } |
29 | |
30 | QLibInputTouch::DeviceState *QLibInputTouch::deviceState(libinput_event_touch *e) |
31 | { |
32 | libinput_device *dev = libinput_event_get_device(event: libinput_event_touch_get_base_event(event: e)); |
33 | return &m_devState[dev]; |
34 | } |
35 | |
36 | QRect QLibInputTouch::screenGeometry(DeviceState *state) |
37 | { |
38 | QScreen *screen = QGuiApplication::primaryScreen(); |
39 | if (!state->m_screenName.isEmpty()) { |
40 | if (!m_screen) { |
41 | const QList<QScreen *> screens = QGuiApplication::screens(); |
42 | for (QScreen *s : screens) { |
43 | if (s->name() == state->m_screenName) { |
44 | m_screen = s; |
45 | break; |
46 | } |
47 | } |
48 | } |
49 | if (m_screen) |
50 | screen = m_screen; |
51 | } |
52 | return screen ? QHighDpi::toNativePixels(value: screen->geometry(), context: screen) : QRect(); |
53 | } |
54 | |
55 | QPointF QLibInputTouch::getPos(libinput_event_touch *e) |
56 | { |
57 | DeviceState *state = deviceState(e); |
58 | QRect geom = screenGeometry(state); |
59 | const double x = libinput_event_touch_get_x_transformed(event: e, width: geom.width()); |
60 | const double y = libinput_event_touch_get_y_transformed(event: e, height: geom.height()); |
61 | return geom.topLeft() + QPointF(x, y); |
62 | } |
63 | |
64 | void QLibInputTouch::registerDevice(libinput_device *dev) |
65 | { |
66 | struct udev_device *udev_device; |
67 | udev_device = libinput_device_get_udev_device(device: dev); |
68 | QString devNode = QString::fromUtf8(utf8: udev_device_get_devnode(udev_device)); |
69 | QString devName = QString::fromUtf8(utf8: libinput_device_get_name(device: dev)); |
70 | |
71 | qCDebug(qLcLibInput, "libinput: registerDevice %s - %s" , |
72 | qPrintable(devNode), qPrintable(devName)); |
73 | |
74 | QOutputMapping *mapping = QOutputMapping::get(); |
75 | QRect geom; |
76 | if (mapping->load()) { |
77 | m_devState[dev].m_screenName = mapping->screenNameForDeviceNode(deviceNode: devNode); |
78 | if (!m_devState[dev].m_screenName.isEmpty()) { |
79 | geom = screenGeometry(state: &m_devState[dev]); |
80 | qCDebug(qLcLibInput) << "libinput: Mapping device" << devNode |
81 | << "to screen" << m_devState[dev].m_screenName |
82 | << "with geometry" << geom; |
83 | } |
84 | } |
85 | |
86 | QPointingDevice *&td = m_devState[dev].m_touchDevice; |
87 | td = new QPointingDevice(devName, udev_device_get_devnum(udev_device), |
88 | QInputDevice::DeviceType::TouchScreen, QPointingDevice::PointerType::Finger, |
89 | QPointingDevice::Capability::Position | QPointingDevice::Capability::Area, 16, 0); |
90 | auto devPriv = QPointingDevicePrivate::get(q: td); |
91 | devPriv->busId = QString::fromLocal8Bit(ba: udev_device_get_syspath(udev_device)); // TODO is that the best to choose? |
92 | if (!geom.isNull()) |
93 | devPriv->setAvailableVirtualGeometry(geom); |
94 | QWindowSystemInterface::registerInputDevice(device: td); |
95 | } |
96 | |
97 | void QLibInputTouch::unregisterDevice(libinput_device *dev) |
98 | { |
99 | Q_UNUSED(dev); |
100 | // There is no way to remove a QPointingDevice. |
101 | } |
102 | |
103 | void QLibInputTouch::processTouchDown(libinput_event_touch *e) |
104 | { |
105 | int slot = libinput_event_touch_get_slot(event: e); |
106 | DeviceState *state = deviceState(e); |
107 | QWindowSystemInterface::TouchPoint *tp = state->point(slot); |
108 | if (tp) { |
109 | qWarning(msg: "Incorrect touch state" ); |
110 | } else { |
111 | QWindowSystemInterface::TouchPoint newTp; |
112 | newTp.id = qMax(a: 0, b: slot); |
113 | newTp.state = QEventPoint::State::Pressed; |
114 | newTp.area = QRect(0, 0, 8, 8); |
115 | newTp.area.moveCenter(p: getPos(e)); |
116 | state->m_points.append(t: newTp); |
117 | qCDebug(qLcLibInputEvents) << "touch down" << newTp; |
118 | } |
119 | } |
120 | |
121 | void QLibInputTouch::processTouchMotion(libinput_event_touch *e) |
122 | { |
123 | int slot = libinput_event_touch_get_slot(event: e); |
124 | DeviceState *state = deviceState(e); |
125 | QWindowSystemInterface::TouchPoint *tp = state->point(slot); |
126 | if (tp) { |
127 | QEventPoint::State tmpState = QEventPoint::State::Updated; |
128 | const QPointF p = getPos(e); |
129 | if (tp->area.center() == p) |
130 | tmpState = QEventPoint::State::Stationary; |
131 | else |
132 | tp->area.moveCenter(p); |
133 | // 'down' may be followed by 'motion' within the same "frame". |
134 | // Handle this by compressing and keeping the Pressed state until the 'frame'. |
135 | if (tp->state != QEventPoint::State::Pressed && tp->state != QEventPoint::State::Released) |
136 | tp->state = tmpState; |
137 | qCDebug(qLcLibInputEvents) << "touch move" << tp; |
138 | } else { |
139 | qWarning(msg: "Inconsistent touch state (got 'motion' without 'down')" ); |
140 | } |
141 | } |
142 | |
143 | void QLibInputTouch::processTouchUp(libinput_event_touch *e) |
144 | { |
145 | int slot = libinput_event_touch_get_slot(event: e); |
146 | DeviceState *state = deviceState(e); |
147 | QWindowSystemInterface::TouchPoint *tp = state->point(slot); |
148 | if (tp) { |
149 | tp->state = QEventPoint::State::Released; |
150 | // There may not be a Frame event after the last Up. Work this around. |
151 | QEventPoint::States s; |
152 | for (int i = 0; i < state->m_points.size(); ++i) |
153 | s |= state->m_points.at(i).state; |
154 | qCDebug(qLcLibInputEvents) << "touch up" << s << tp; |
155 | if (s == QEventPoint::State::Released) |
156 | processTouchFrame(e); |
157 | else |
158 | qCDebug(qLcLibInputEvents, "waiting for all points to be released" ); |
159 | } else { |
160 | qWarning(msg: "Inconsistent touch state (got 'up' without 'down')" ); |
161 | } |
162 | } |
163 | |
164 | void QLibInputTouch::processTouchCancel(libinput_event_touch *e) |
165 | { |
166 | DeviceState *state = deviceState(e); |
167 | qCDebug(qLcLibInputEvents) << "touch cancel" << state->m_points; |
168 | if (state->m_touchDevice) |
169 | QWindowSystemInterface::handleTouchCancelEvent(window: nullptr, device: state->m_touchDevice, mods: QGuiApplication::keyboardModifiers()); |
170 | else |
171 | qWarning(msg: "TouchCancel without registered device" ); |
172 | } |
173 | |
174 | void QLibInputTouch::processTouchFrame(libinput_event_touch *e) |
175 | { |
176 | DeviceState *state = deviceState(e); |
177 | if (!state->m_touchDevice) { |
178 | qWarning(msg: "TouchFrame without registered device" ); |
179 | return; |
180 | } |
181 | qCDebug(qLcLibInputEvents) << "touch frame" << state->m_points; |
182 | if (state->m_points.isEmpty()) |
183 | return; |
184 | |
185 | QWindowSystemInterface::handleTouchEvent(window: nullptr, device: state->m_touchDevice, points: state->m_points, |
186 | mods: QGuiApplication::keyboardModifiers()); |
187 | |
188 | for (int i = 0; i < state->m_points.size(); ++i) { |
189 | QWindowSystemInterface::TouchPoint &tp(state->m_points[i]); |
190 | if (tp.state == QEventPoint::State::Released) |
191 | state->m_points.removeAt(i: i--); |
192 | else if (tp.state == QEventPoint::State::Pressed) |
193 | tp.state = QEventPoint::State::Stationary; |
194 | } |
195 | } |
196 | |
197 | QT_END_NAMESPACE |
198 | |