1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qevdevmousehandler_p.h"
41
42#include <QSocketNotifier>
43#include <QStringList>
44#include <QPoint>
45#include <QGuiApplication>
46#include <QScreen>
47#include <QLoggingCategory>
48#include <qpa/qwindowsysteminterface.h>
49
50#include <qplatformdefs.h>
51#include <private/qcore_unix_p.h> // overrides QT_OPEN
52#include <private/qhighdpiscaling_p.h>
53
54#include <errno.h>
55
56#ifdef Q_OS_FREEBSD
57#include <dev/evdev/input.h>
58#else
59#include <linux/kd.h>
60#include <linux/input.h>
61#endif
62
63#define TEST_BIT(array, bit) (array[bit/8] & (1<<(bit%8)))
64
65QT_BEGIN_NAMESPACE
66
67Q_LOGGING_CATEGORY(qLcEvdevMouse, "qt.qpa.input")
68
69std::unique_ptr<QEvdevMouseHandler> QEvdevMouseHandler::create(const QString &device, const QString &specification)
70{
71 qCDebug(qLcEvdevMouse) << "create mouse handler for" << device << specification;
72
73 bool compression = true;
74 int jitterLimit = 0;
75 int grab = 0;
76 bool abs = false;
77
78 const auto args = QStringView{specification}.split(QLatin1Char(':'));
79 for (const auto &arg : args) {
80 if (arg == QLatin1String("nocompress"))
81 compression = false;
82 else if (arg.startsWith(QLatin1String("dejitter=")))
83 jitterLimit = arg.mid(9).toInt();
84 else if (arg.startsWith(QLatin1String("grab=")))
85 grab = arg.mid(5).toInt();
86 else if (arg == QLatin1String("abs"))
87 abs = true;
88 }
89
90 int fd;
91 fd = qt_safe_open(device.toLocal8Bit().constData(), O_RDONLY | O_NDELAY, 0);
92 if (fd >= 0) {
93 ::ioctl(fd, EVIOCGRAB, grab);
94 return std::unique_ptr<QEvdevMouseHandler>(new QEvdevMouseHandler(device, fd, abs, compression, jitterLimit));
95 } else {
96 qErrnoWarning(errno, "Cannot open mouse input device %s", qPrintable(device));
97 return nullptr;
98 }
99}
100
101QEvdevMouseHandler::QEvdevMouseHandler(const QString &device, int fd, bool abs, bool compression, int jitterLimit)
102 : m_device(device), m_fd(fd), m_abs(abs), m_compression(compression)
103{
104 setObjectName(QLatin1String("Evdev Mouse Handler"));
105
106 m_jitterLimitSquared = jitterLimit * jitterLimit;
107
108 // Some touch screens present as mice with absolute coordinates.
109 // These can not be differentiated from touchpads, so supplying abs to QT_QPA_EVDEV_MOUSE_PARAMETERS
110 // will force qevdevmousehandler to treat the coordinates as absolute, scaled to the hardware maximums.
111 // Turning this on will not affect mice as these do not report in absolute coordinates
112 // but will make touchpads act like touch screens
113 if (m_abs)
114 m_abs = getHardwareMaximum();
115
116 detectHiResWheelSupport();
117
118 // socket notifier for events on the mouse device
119 m_notify = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
120 connect(m_notify, &QSocketNotifier::activated,
121 this, &QEvdevMouseHandler::readMouseData);
122}
123
124QEvdevMouseHandler::~QEvdevMouseHandler()
125{
126 if (m_fd >= 0)
127 qt_safe_close(m_fd);
128}
129
130void QEvdevMouseHandler::detectHiResWheelSupport()
131{
132#if defined(REL_WHEEL_HI_RES) || defined(REL_HWHEEL_HI_RES)
133 // Check if we can expect hires events as we will get both
134 // legacy and hires event and needs to know if we should
135 // ignore the legacy events.
136 unsigned char relFeatures[(REL_MAX / 8) + 1]{};
137 if (ioctl(m_fd, EVIOCGBIT(EV_REL, sizeof (relFeatures)), relFeatures) == -1)
138 return;
139
140#if defined(REL_WHEEL_HI_RES)
141 m_hiResWheel = TEST_BIT(relFeatures, REL_WHEEL_HI_RES);
142#endif
143#if defined(REL_HWHEEL_HI_RES)
144 m_hiResHWheel = TEST_BIT(relFeatures, REL_HWHEEL_HI_RES);
145#endif
146#endif
147}
148
149// Ask touch screen hardware for information on coordinate maximums
150// If any ioctls fail, revert to non abs mode
151bool QEvdevMouseHandler::getHardwareMaximum()
152{
153 unsigned char absFeatures[(ABS_MAX / 8) + 1];
154 memset(absFeatures, '\0', sizeof (absFeatures));
155
156 // test if ABS_X, ABS_Y are available
157 if (ioctl(m_fd, EVIOCGBIT(EV_ABS, sizeof (absFeatures)), absFeatures) == -1)
158 return false;
159
160 if ((!TEST_BIT(absFeatures, ABS_X)) || (!TEST_BIT(absFeatures, ABS_Y)))
161 return false;
162
163 // ask hardware for minimum and maximum values
164 struct input_absinfo absInfo;
165 if (ioctl(m_fd, EVIOCGABS(ABS_X), &absInfo) == -1)
166 return false;
167
168 m_hardwareWidth = absInfo.maximum - absInfo.minimum;
169
170 if (ioctl(m_fd, EVIOCGABS(ABS_Y), &absInfo) == -1)
171 return false;
172
173 m_hardwareHeight = absInfo.maximum - absInfo.minimum;
174
175 QScreen *primaryScreen = QGuiApplication::primaryScreen();
176 QRect g = QHighDpi::toNativePixels(primaryScreen->virtualGeometry(), primaryScreen);
177 m_hardwareScalerX = static_cast<qreal>(m_hardwareWidth) / (g.right() - g.left());
178 m_hardwareScalerY = static_cast<qreal>(m_hardwareHeight) / (g.bottom() - g.top());
179
180 qCDebug(qLcEvdevMouse) << "Absolute pointing device"
181 << "hardware max x" << m_hardwareWidth
182 << "hardware max y" << m_hardwareHeight
183 << "hardware scalers x" << m_hardwareScalerX << 'y' << m_hardwareScalerY;
184
185 return true;
186}
187
188void QEvdevMouseHandler::sendMouseEvent()
189{
190 int x;
191 int y;
192
193 if (!m_abs) {
194 x = m_x - m_prevx;
195 y = m_y - m_prevy;
196 }
197 else {
198 x = m_x / m_hardwareScalerX;
199 y = m_y / m_hardwareScalerY;
200 }
201
202 if (m_prevInvalid) {
203 x = y = 0;
204 m_prevInvalid = false;
205 }
206
207 emit handleMouseEvent(x, y, m_abs, m_buttons, m_button, m_eventType);
208
209 m_prevx = m_x;
210 m_prevy = m_y;
211}
212
213void QEvdevMouseHandler::readMouseData()
214{
215 struct ::input_event buffer[32];
216 int n = 0;
217 bool posChanged = false, btnChanged = false;
218 bool pendingMouseEvent = false;
219 int eventCompressCount = 0;
220 forever {
221 int result = QT_READ(m_fd, reinterpret_cast<char *>(buffer) + n, sizeof(buffer) - n);
222
223 if (result == 0) {
224 qWarning("evdevmouse: Got EOF from the input device");
225 return;
226 } else if (result < 0) {
227 if (errno != EINTR && errno != EAGAIN) {
228 qErrnoWarning(errno, "evdevmouse: Could not read from input device");
229 // If the device got disconnected, stop reading, otherwise we get flooded
230 // by the above error over and over again.
231 if (errno == ENODEV) {
232 delete m_notify;
233 m_notify = nullptr;
234 qt_safe_close(m_fd);
235 m_fd = -1;
236 }
237 return;
238 }
239 } else {
240 n += result;
241 if (n % sizeof(buffer[0]) == 0)
242 break;
243 }
244 }
245
246 n /= sizeof(buffer[0]);
247
248 for (int i = 0; i < n; ++i) {
249 struct ::input_event *data = &buffer[i];
250 if (data->type == EV_ABS) {
251 // Touchpads: store the absolute position for now, will calculate a relative one later.
252 if (data->code == ABS_X && m_x != data->value) {
253 m_x = data->value;
254 posChanged = true;
255 } else if (data->code == ABS_Y && m_y != data->value) {
256 m_y = data->value;
257 posChanged = true;
258 }
259 } else if (data->type == EV_REL) {
260 QPoint delta;
261 if (data->code == REL_X) {
262 m_x += data->value;
263 posChanged = true;
264 } else if (data->code == REL_Y) {
265 m_y += data->value;
266 posChanged = true;
267 } else if (!m_hiResWheel && data->code == REL_WHEEL) {
268 // data->value: positive == up, negative == down
269 delta.setY(120 * data->value);
270 emit handleWheelEvent(delta);
271#ifdef REL_WHEEL_HI_RES
272 } else if (data->code == REL_WHEEL_HI_RES) {
273 delta.setY(data->value);
274 emit handleWheelEvent(delta);
275#endif
276 } else if (!m_hiResHWheel && data->code == REL_HWHEEL) {
277 // data->value: positive == right, negative == left
278 delta.setX(-120 * data->value);
279 emit handleWheelEvent(delta);
280#ifdef REL_HWHEEL_HI_RES
281 } else if (data->code == REL_HWHEEL_HI_RES) {
282 delta.setX(-data->value);
283 emit handleWheelEvent(delta);
284#endif
285 }
286 } else if (data->type == EV_KEY && data->code == BTN_TOUCH) {
287 // We care about touchpads only, not touchscreens -> don't map to button press.
288 // Need to invalidate prevx/y however to get proper relative pos.
289 m_prevInvalid = true;
290 } else if (data->type == EV_KEY && data->code >= BTN_LEFT && data->code <= BTN_JOYSTICK) {
291 Qt::MouseButton button = Qt::NoButton;
292 // BTN_LEFT == 0x110 in kernel's input.h
293 // The range of possible mouse buttons ends just before BTN_JOYSTICK, value 0x120.
294 switch (data->code) {
295 case 0x110: button = Qt::LeftButton; break; // BTN_LEFT
296 case 0x111: button = Qt::RightButton; break;
297 case 0x112: button = Qt::MiddleButton; break;
298 case 0x113: button = Qt::ExtraButton1; break; // AKA Qt::BackButton
299 case 0x114: button = Qt::ExtraButton2; break; // AKA Qt::ForwardButton
300 case 0x115: button = Qt::ExtraButton3; break; // AKA Qt::TaskButton
301 case 0x116: button = Qt::ExtraButton4; break;
302 case 0x117: button = Qt::ExtraButton5; break;
303 case 0x118: button = Qt::ExtraButton6; break;
304 case 0x119: button = Qt::ExtraButton7; break;
305 case 0x11a: button = Qt::ExtraButton8; break;
306 case 0x11b: button = Qt::ExtraButton9; break;
307 case 0x11c: button = Qt::ExtraButton10; break;
308 case 0x11d: button = Qt::ExtraButton11; break;
309 case 0x11e: button = Qt::ExtraButton12; break;
310 case 0x11f: button = Qt::ExtraButton13; break;
311 }
312 m_buttons.setFlag(button, data->value);
313 m_button = button;
314 m_eventType = data->value == 0 ? QEvent::MouseButtonRelease : QEvent::MouseButtonPress;
315 btnChanged = true;
316 } else if (data->type == EV_SYN && data->code == SYN_REPORT) {
317 if (btnChanged) {
318 btnChanged = posChanged = false;
319 sendMouseEvent();
320 pendingMouseEvent = false;
321 } else if (posChanged) {
322 m_eventType = QEvent::MouseMove;
323 posChanged = false;
324 if (m_compression) {
325 pendingMouseEvent = true;
326 eventCompressCount++;
327 } else {
328 sendMouseEvent();
329 }
330 }
331 } else if (data->type == EV_MSC && data->code == MSC_SCAN) {
332 // kernel encountered an unmapped key - just ignore it
333 continue;
334 }
335 }
336 if (m_compression && pendingMouseEvent) {
337 int distanceSquared = (m_x - m_prevx)*(m_x - m_prevx) + (m_y - m_prevy)*(m_y - m_prevy);
338 if (distanceSquared > m_jitterLimitSquared)
339 sendMouseEvent();
340 }
341}
342
343QT_END_NAMESPACE
344