summaryrefslogtreecommitdiffstats
path: root/src/platformsupport/input/evdevtablet/qevdevtablethandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/platformsupport/input/evdevtablet/qevdevtablethandler.cpp')
-rw-r--r--src/platformsupport/input/evdevtablet/qevdevtablethandler.cpp292
1 files changed, 292 insertions, 0 deletions
diff --git a/src/platformsupport/input/evdevtablet/qevdevtablethandler.cpp b/src/platformsupport/input/evdevtablet/qevdevtablethandler.cpp
new file mode 100644
index 0000000000..aa87340112
--- /dev/null
+++ b/src/platformsupport/input/evdevtablet/qevdevtablethandler.cpp
@@ -0,0 +1,292 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the plugins module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL21$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** As a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qevdevtablethandler_p.h"
+
+#include <QStringList>
+#include <QSocketNotifier>
+#include <QGuiApplication>
+#include <QLoggingCategory>
+#include <QtCore/private/qcore_unix_p.h>
+#include <qpa/qwindowsysteminterface.h>
+#include <linux/input.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(qLcEvdevTablet, "qt.qpa.input")
+
+class QEvdevTabletData
+{
+public:
+ QEvdevTabletData(QEvdevTabletHandler *q_ptr);
+
+ void processInputEvent(input_event *ev);
+ void report();
+
+ QEvdevTabletHandler *q;
+ int lastEventType;
+ QString devName;
+ struct {
+ int x, y, p, d;
+ } minValues, maxValues;
+ struct {
+ int x, y, p, d;
+ bool down, lastReportDown;
+ int tool, lastReportTool;
+ QPointF lastReportPos;
+ } state;
+};
+
+QEvdevTabletData::QEvdevTabletData(QEvdevTabletHandler *q_ptr)
+ : q(q_ptr), lastEventType(0)
+{
+ memset(&minValues, 0, sizeof(minValues));
+ memset(&maxValues, 0, sizeof(maxValues));
+ memset(&state, 0, sizeof(state));
+}
+
+void QEvdevTabletData::processInputEvent(input_event *ev)
+{
+ if (ev->type == EV_ABS) {
+ switch (ev->code) {
+ case ABS_X:
+ state.x = ev->value;
+ break;
+ case ABS_Y:
+ state.y = ev->value;
+ break;
+ case ABS_PRESSURE:
+ state.p = ev->value;
+ break;
+ case ABS_DISTANCE:
+ state.d = ev->value;
+ break;
+ default:
+ break;
+ }
+ } else if (ev->type == EV_KEY) {
+ // code BTN_TOOL_* value 1 -> proximity enter
+ // code BTN_TOOL_* value 0 -> proximity leave
+ // code BTN_TOUCH value 1 -> contact with screen
+ // code BTN_TOUCH value 0 -> no contact
+ switch (ev->code) {
+ case BTN_TOUCH:
+ state.down = ev->value != 0;
+ break;
+ case BTN_TOOL_PEN:
+ state.tool = ev->value ? QTabletEvent::Pen : 0;
+ break;
+ case BTN_TOOL_RUBBER:
+ state.tool = ev->value ? QTabletEvent::Eraser : 0;
+ break;
+ default:
+ break;
+ }
+ } else if (ev->type == EV_SYN && ev->code == SYN_REPORT && lastEventType != ev->type) {
+ report();
+ }
+ lastEventType = ev->type;
+}
+
+void QEvdevTabletData::report()
+{
+ if (!state.lastReportTool && state.tool)
+ QWindowSystemInterface::handleTabletEnterProximityEvent(QTabletEvent::Stylus, state.tool, q->deviceId());
+
+ qreal nx = (state.x - minValues.x) / qreal(maxValues.x - minValues.x);
+ qreal ny = (state.y - minValues.y) / qreal(maxValues.y - minValues.y);
+
+ QRect winRect = QGuiApplication::primaryScreen()->geometry();
+ QPointF globalPos(nx * winRect.width(), ny * winRect.height());
+ int pointer = state.tool;
+ // Prevent sending confusing values of 0 when moving the pen outside the active area.
+ if (!state.down && state.lastReportDown) {
+ globalPos = state.lastReportPos;
+ pointer = state.lastReportTool;
+ }
+
+ int pressureRange = maxValues.p - minValues.p;
+ qreal pressure = pressureRange ? (state.p - minValues.p) / qreal(pressureRange) : qreal(1);
+
+ if (state.down || state.lastReportDown) {
+ QWindowSystemInterface::handleTabletEvent(0, state.down, QPointF(), globalPos,
+ QTabletEvent::Stylus, pointer,
+ pressure, 0, 0, 0, 0, 0, q->deviceId(), qGuiApp->keyboardModifiers());
+ }
+
+ if (state.lastReportTool && !state.tool)
+ QWindowSystemInterface::handleTabletLeaveProximityEvent(QTabletEvent::Stylus, state.tool, q->deviceId());
+
+ state.lastReportDown = state.down;
+ state.lastReportTool = state.tool;
+ state.lastReportPos = globalPos;
+}
+
+
+QEvdevTabletHandler::QEvdevTabletHandler(const QString &device, const QString &spec, QObject *parent)
+ : QObject(parent), m_fd(-1), m_device(device), m_notifier(0), d(0)
+{
+ Q_UNUSED(spec)
+
+ setObjectName(QLatin1String("Evdev Tablet Handler"));
+
+ qCDebug(qLcEvdevTablet, "evdevtablet: using %s", qPrintable(device));
+
+ m_fd = QT_OPEN(device.toLocal8Bit().constData(), O_RDONLY | O_NDELAY, 0);
+ if (m_fd < 0) {
+ qErrnoWarning(errno, "evdevtablet: Cannot open input device %s", qPrintable(device));
+ return;
+ }
+
+ bool grabSuccess = !ioctl(m_fd, EVIOCGRAB, (void *) 1);
+ if (grabSuccess)
+ ioctl(m_fd, EVIOCGRAB, (void *) 0);
+ else
+ qWarning("evdevtablet: %s: The device is grabbed by another process. No events will be read.", qPrintable(device));
+
+ d = new QEvdevTabletData(this);
+ if (!queryLimits())
+ qWarning("evdevtablet: %s: Unset or invalid ABS limits. Behavior will be unspecified.", qPrintable(device));
+
+ m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
+ connect(m_notifier, &QSocketNotifier::activated, this, &QEvdevTabletHandler::readData);
+}
+
+QEvdevTabletHandler::~QEvdevTabletHandler()
+{
+ if (m_fd >= 0)
+ QT_CLOSE(m_fd);
+
+ delete d;
+}
+
+qint64 QEvdevTabletHandler::deviceId() const
+{
+ return m_fd;
+}
+
+bool QEvdevTabletHandler::queryLimits()
+{
+ bool ok = true;
+ input_absinfo absInfo;
+ memset(&absInfo, 0, sizeof(input_absinfo));
+ ok &= ioctl(m_fd, EVIOCGABS(ABS_X), &absInfo) >= 0;
+ if (ok) {
+ d->minValues.x = absInfo.minimum;
+ d->maxValues.x = absInfo.maximum;
+ qCDebug(qLcEvdevTablet, "evdevtablet: %s: min X: %d max X: %d", qPrintable(m_device),
+ d->minValues.x, d->maxValues.x);
+ }
+ ok &= ioctl(m_fd, EVIOCGABS(ABS_Y), &absInfo) >= 0;
+ if (ok) {
+ d->minValues.y = absInfo.minimum;
+ d->maxValues.y = absInfo.maximum;
+ qCDebug(qLcEvdevTablet, "evdevtablet: %s: min Y: %d max Y: %d", qPrintable(m_device),
+ d->minValues.y, d->maxValues.y);
+ }
+ if (ioctl(m_fd, EVIOCGABS(ABS_PRESSURE), &absInfo) >= 0) {
+ d->minValues.p = absInfo.minimum;
+ d->maxValues.p = absInfo.maximum;
+ qCDebug(qLcEvdevTablet, "evdevtablet: %s: min pressure: %d max pressure: %d", qPrintable(m_device),
+ d->minValues.p, d->maxValues.p);
+ }
+ if (ioctl(m_fd, EVIOCGABS(ABS_DISTANCE), &absInfo) >= 0) {
+ d->minValues.d = absInfo.minimum;
+ d->maxValues.d = absInfo.maximum;
+ qCDebug(qLcEvdevTablet, "evdevtablet: %s: min distance: %d max distance: %d", qPrintable(m_device),
+ d->minValues.d, d->maxValues.d);
+ }
+ char name[128];
+ if (ioctl(m_fd, EVIOCGNAME(sizeof(name) - 1), name) >= 0) {
+ d->devName = QString::fromLocal8Bit(name);
+ qCDebug(qLcEvdevTablet, "evdevtablet: %s: device name: %s", qPrintable(m_device), name);
+ }
+ return ok;
+}
+
+void QEvdevTabletHandler::readData()
+{
+ static input_event buffer[32];
+ int n = 0;
+ for (; ;) {
+ int result = QT_READ(m_fd, reinterpret_cast<char*>(buffer) + n, sizeof(buffer) - n);
+ if (!result) {
+ qWarning("evdevtablet: %s: Got EOF from input device", qPrintable(m_device));
+ return;
+ } else if (result < 0) {
+ if (errno != EINTR && errno != EAGAIN) {
+ qErrnoWarning(errno, "evdevtablet: %s: Could not read from input device", qPrintable(m_device));
+ if (errno == ENODEV) { // device got disconnected -> stop reading
+ delete m_notifier;
+ m_notifier = 0;
+ QT_CLOSE(m_fd);
+ m_fd = -1;
+ }
+ return;
+ }
+ } else {
+ n += result;
+ if (n % sizeof(input_event) == 0)
+ break;
+ }
+ }
+
+ n /= sizeof(input_event);
+
+ for (int i = 0; i < n; ++i)
+ d->processInputEvent(&buffer[i]);
+}
+
+
+QEvdevTabletHandlerThread::QEvdevTabletHandlerThread(const QString &device, const QString &spec, QObject *parent)
+ : QDaemonThread(parent), m_device(device), m_spec(spec), m_handler(0)
+{
+ start();
+}
+
+QEvdevTabletHandlerThread::~QEvdevTabletHandlerThread()
+{
+ quit();
+ wait();
+}
+
+void QEvdevTabletHandlerThread::run()
+{
+ m_handler = new QEvdevTabletHandler(m_device, m_spec);
+ exec();
+ delete m_handler;
+ m_handler = 0;
+}
+
+
+QT_END_NAMESPACE