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