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
14QT_BEGIN_NAMESPACE
15
16Q_DECLARE_LOGGING_CATEGORY(qLcLibInput)
17Q_LOGGING_CATEGORY(qLcLibInputEvents, "qt.qpa.input.events")
18
19QWindowSystemInterface::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
30QLibInputTouch::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
36QRect 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
55QPointF 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
64void 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
97void QLibInputTouch::unregisterDevice(libinput_device *dev)
98{
99 Q_UNUSED(dev);
100 // There is no way to remove a QPointingDevice.
101}
102
103void 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
121void 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
143void 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
164void 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
174void 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
197QT_END_NAMESPACE
198

source code of qtbase/src/platformsupport/input/libinput/qlibinputtouch.cpp