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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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