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 "qevdevtablethandler_p.h"
5
6#include <QStringList>
7#include <QSocketNotifier>
8#include <QGuiApplication>
9#include <QPointingDevice>
10#include <QLoggingCategory>
11#include <QtCore/private/qcore_unix_p.h>
12#include <qpa/qwindowsysteminterface.h>
13#ifdef Q_OS_FREEBSD
14#include <dev/evdev/input.h>
15#else
16#include <linux/input.h>
17#endif
18
19QT_BEGIN_NAMESPACE
20
21using namespace Qt::StringLiterals;
22
23Q_LOGGING_CATEGORY(qLcEvdevTablet, "qt.qpa.input")
24
25class QEvdevTabletData
26{
27public:
28 QEvdevTabletData(QEvdevTabletHandler *q_ptr);
29
30 void processInputEvent(input_event *ev);
31 void report();
32
33 QEvdevTabletHandler *q;
34 int lastEventType;
35 QString devName;
36 struct {
37 int x, y, p, d;
38 } minValues, maxValues;
39 struct {
40 int x, y, p, d;
41 bool down, lastReportDown;
42 int tool, lastReportTool;
43 QPointF lastReportPos;
44 } state;
45};
46
47QEvdevTabletData::QEvdevTabletData(QEvdevTabletHandler *q_ptr)
48 : q(q_ptr), lastEventType(0)
49{
50 memset(s: &minValues, c: 0, n: sizeof(minValues));
51 memset(s: &maxValues, c: 0, n: sizeof(maxValues));
52 memset(s: static_cast<void *>(&state), c: 0, n: sizeof(state));
53}
54
55void QEvdevTabletData::processInputEvent(input_event *ev)
56{
57 if (ev->type == EV_ABS) {
58 switch (ev->code) {
59 case ABS_X:
60 state.x = ev->value;
61 break;
62 case ABS_Y:
63 state.y = ev->value;
64 break;
65 case ABS_PRESSURE:
66 state.p = ev->value;
67 break;
68 case ABS_DISTANCE:
69 state.d = ev->value;
70 break;
71 default:
72 break;
73 }
74 } else if (ev->type == EV_KEY) {
75 // code BTN_TOOL_* value 1 -> proximity enter
76 // code BTN_TOOL_* value 0 -> proximity leave
77 // code BTN_TOUCH value 1 -> contact with screen
78 // code BTN_TOUCH value 0 -> no contact
79 switch (ev->code) {
80 case BTN_TOUCH:
81 state.down = ev->value != 0;
82 break;
83 case BTN_TOOL_PEN:
84 state.tool = ev->value ? int(QPointingDevice::PointerType::Pen) : 0;
85 break;
86 case BTN_TOOL_RUBBER:
87 state.tool = ev->value ? int(QPointingDevice::PointerType::Eraser) : 0;
88 break;
89 default:
90 break;
91 }
92 } else if (ev->type == EV_SYN && ev->code == SYN_REPORT && lastEventType != ev->type) {
93 report();
94 }
95 lastEventType = ev->type;
96}
97
98void QEvdevTabletData::report()
99{
100 if (!state.lastReportTool && state.tool)
101 QWindowSystemInterface::handleTabletEnterProximityEvent(deviceType: int(QInputDevice::DeviceType::Stylus), pointerType: state.tool, uid: q->deviceId());
102
103 qreal nx = (state.x - minValues.x) / qreal(maxValues.x - minValues.x);
104 qreal ny = (state.y - minValues.y) / qreal(maxValues.y - minValues.y);
105
106 QRect winRect = QGuiApplication::primaryScreen()->geometry();
107 QPointF globalPos(nx * winRect.width(), ny * winRect.height());
108 int pointer = state.tool;
109 // Prevent sending confusing values of 0 when moving the pen outside the active area.
110 if (!state.down && state.lastReportDown) {
111 globalPos = state.lastReportPos;
112 pointer = state.lastReportTool;
113 }
114
115 int pressureRange = maxValues.p - minValues.p;
116 qreal pressure = pressureRange ? (state.p - minValues.p) / qreal(pressureRange) : qreal(1);
117
118 if (state.down || state.lastReportDown) {
119 QWindowSystemInterface::handleTabletEvent(window: 0, local: QPointF(), global: globalPos,
120 device: int(QInputDevice::DeviceType::Stylus), pointerType: pointer,
121 buttons: state.down ? Qt::LeftButton : Qt::NoButton,
122 pressure, xTilt: 0, yTilt: 0, tangentialPressure: 0, rotation: 0, z: 0, uid: q->deviceId(),
123 qGuiApp->keyboardModifiers());
124 }
125
126 if (state.lastReportTool && !state.tool)
127 QWindowSystemInterface::handleTabletLeaveProximityEvent(deviceType: int(QInputDevice::DeviceType::Stylus), pointerType: state.tool, uid: q->deviceId());
128
129 state.lastReportDown = state.down;
130 state.lastReportTool = state.tool;
131 state.lastReportPos = globalPos;
132}
133
134
135QEvdevTabletHandler::QEvdevTabletHandler(const QString &device, const QString &spec, QObject *parent)
136 : QObject(parent), m_fd(-1), m_device(device), m_notifier(0), d(0)
137{
138 Q_UNUSED(spec);
139
140 setObjectName("Evdev Tablet Handler"_L1);
141
142 qCDebug(qLcEvdevTablet, "evdevtablet: using %ls", qUtf16Printable(device));
143
144 m_fd = QT_OPEN(pathname: device.toLocal8Bit().constData(), O_RDONLY | O_NDELAY, mode: 0);
145 if (m_fd < 0) {
146 qErrnoWarning(msg: "evdevtablet: Cannot open input device %ls", qUtf16Printable(device));
147 return;
148 }
149
150 bool grabSuccess = !ioctl(fd: m_fd, EVIOCGRAB, (void *) 1);
151 if (grabSuccess)
152 ioctl(fd: m_fd, EVIOCGRAB, (void *) 0);
153 else
154 qWarning(msg: "evdevtablet: %ls: The device is grabbed by another process. No events will be read.", qUtf16Printable(device));
155
156 d = new QEvdevTabletData(this);
157 if (!queryLimits())
158 qWarning(msg: "evdevtablet: %ls: Unset or invalid ABS limits. Behavior will be unspecified.", qUtf16Printable(device));
159
160 m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
161 connect(sender: m_notifier, signal: &QSocketNotifier::activated, context: this, slot: &QEvdevTabletHandler::readData);
162}
163
164QEvdevTabletHandler::~QEvdevTabletHandler()
165{
166 if (m_fd >= 0)
167 QT_CLOSE(fd: m_fd);
168
169 delete d;
170}
171
172qint64 QEvdevTabletHandler::deviceId() const
173{
174 return m_fd;
175}
176
177bool QEvdevTabletHandler::queryLimits()
178{
179 bool ok = true;
180 input_absinfo absInfo;
181 memset(s: &absInfo, c: 0, n: sizeof(input_absinfo));
182 ok &= ioctl(fd: m_fd, EVIOCGABS(ABS_X), &absInfo) >= 0;
183 if (ok) {
184 d->minValues.x = absInfo.minimum;
185 d->maxValues.x = absInfo.maximum;
186 qCDebug(qLcEvdevTablet, "evdevtablet: %ls: min X: %d max X: %d", qUtf16Printable(m_device),
187 d->minValues.x, d->maxValues.x);
188 }
189 ok &= ioctl(fd: m_fd, EVIOCGABS(ABS_Y), &absInfo) >= 0;
190 if (ok) {
191 d->minValues.y = absInfo.minimum;
192 d->maxValues.y = absInfo.maximum;
193 qCDebug(qLcEvdevTablet, "evdevtablet: %ls: min Y: %d max Y: %d", qUtf16Printable(m_device),
194 d->minValues.y, d->maxValues.y);
195 }
196 if (ioctl(fd: m_fd, EVIOCGABS(ABS_PRESSURE), &absInfo) >= 0) {
197 d->minValues.p = absInfo.minimum;
198 d->maxValues.p = absInfo.maximum;
199 qCDebug(qLcEvdevTablet, "evdevtablet: %ls: min pressure: %d max pressure: %d", qUtf16Printable(m_device),
200 d->minValues.p, d->maxValues.p);
201 }
202 if (ioctl(fd: m_fd, EVIOCGABS(ABS_DISTANCE), &absInfo) >= 0) {
203 d->minValues.d = absInfo.minimum;
204 d->maxValues.d = absInfo.maximum;
205 qCDebug(qLcEvdevTablet, "evdevtablet: %ls: min distance: %d max distance: %d", qUtf16Printable(m_device),
206 d->minValues.d, d->maxValues.d);
207 }
208 char name[128];
209 if (ioctl(fd: m_fd, EVIOCGNAME(sizeof(name) - 1), name) >= 0) {
210 d->devName = QString::fromLocal8Bit(ba: name);
211 qCDebug(qLcEvdevTablet, "evdevtablet: %ls: device name: %s", qUtf16Printable(m_device), name);
212 }
213 return ok;
214}
215
216void QEvdevTabletHandler::readData()
217{
218 input_event buffer[32];
219 int n = 0;
220 for (; ;) {
221 int result = QT_READ(fd: m_fd, data: reinterpret_cast<char*>(buffer) + n, maxlen: sizeof(buffer) - n);
222 if (!result) {
223 qWarning(msg: "evdevtablet: %ls: Got EOF from input device", qUtf16Printable(m_device));
224 return;
225 } else if (result < 0) {
226 if (errno != EINTR && errno != EAGAIN) {
227 qErrnoWarning(msg: "evdevtablet: %ls: Could not read from input device", qUtf16Printable(m_device));
228 if (errno == ENODEV) { // device got disconnected -> stop reading
229 delete m_notifier;
230 m_notifier = 0;
231 QT_CLOSE(fd: m_fd);
232 m_fd = -1;
233 }
234 return;
235 }
236 } else {
237 n += result;
238 if (n % sizeof(input_event) == 0)
239 break;
240 }
241 }
242
243 n /= sizeof(input_event);
244
245 for (int i = 0; i < n; ++i)
246 d->processInputEvent(ev: &buffer[i]);
247}
248
249
250QEvdevTabletHandlerThread::QEvdevTabletHandlerThread(const QString &device, const QString &spec, QObject *parent)
251 : QDaemonThread(parent), m_device(device), m_spec(spec), m_handler(0)
252{
253 start();
254}
255
256QEvdevTabletHandlerThread::~QEvdevTabletHandlerThread()
257{
258 quit();
259 wait();
260}
261
262void QEvdevTabletHandlerThread::run()
263{
264 m_handler = new QEvdevTabletHandler(m_device, m_spec);
265 exec();
266 delete m_handler;
267 m_handler = 0;
268}
269
270
271QT_END_NAMESPACE
272

source code of qtbase/src/platformsupport/input/evdevtablet/qevdevtablethandler.cpp