evdevtablet plugin

Change-Id: Ie8fbaac929180e6d4c626253c4c20d1b3a9083f5
Reviewed-by: Paul Olav Tvete <paul.tvete@nokia.com>
Reviewed-by: Girish Ramakrishnan <girish.1.ramakrishnan@nokia.com>
bb10
Laszlo Agocs 2012-06-01 10:36:51 +03:00 committed by Qt by Nokia
parent 13714cdd64
commit bc3cfebc66
12 changed files with 555 additions and 3 deletions

View File

@ -72,7 +72,8 @@ public:
Device_Touchscreen = 0x04,
Device_Keyboard = 0x08,
Device_DRM = 0x10,
Device_InputMask = Device_Mouse | Device_Touchpad | Device_Touchscreen | Device_Keyboard,
Device_Tablet = 0x20,
Device_InputMask = Device_Mouse | Device_Touchpad | Device_Touchscreen | Device_Keyboard | Device_Tablet,
Device_VideoMask = Device_DRM
};
Q_DECLARE_FLAGS(QDeviceTypes, QDeviceType)

View File

@ -160,6 +160,11 @@ bool QDeviceDiscovery::checkDeviceType(const QString &device)
} else if ((m_types & Device_Touchscreen) && testBit(BTN_TOUCH, bitsKey)) {
#ifdef QT_QPA_DEVICE_DISCOVERY_DEBUG
qWarning() << "DeviceDiscovery found touchscreen at" << device;
#endif
ret = true;
} else if ((m_types & Device_Tablet) && (testBit(BTN_STYLUS, bitsKey) || testBit(BTN_TOOL_PEN, bitsKey))) {
#ifdef QT_QPA_DEVICE_DISCOVERY_DEBUG
qWarning() << "DeviceDiscovery found tablet at" << device;
#endif
ret = true;
}

View File

@ -132,6 +132,8 @@ QStringList QDeviceDiscovery::scanConnectedDevices()
udev_enumerate_add_match_property(ue, "ID_INPUT_TOUCHSCREEN", "1");
if (m_types & Device_Keyboard)
udev_enumerate_add_match_property(ue, "ID_INPUT_KEYBOARD", "1");
if (m_types & Device_Tablet)
udev_enumerate_add_match_property(ue, "ID_INPUT_TABLET", "1");
if (udev_enumerate_scan_devices(ue) != 0) {
#ifdef QT_QPA_DEVICE_DISCOVERY_DEBUG
@ -241,6 +243,9 @@ bool QDeviceDiscovery::checkDeviceType(udev_device *dev)
if ((m_types & Device_Touchscreen) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN"), "1") == 0))
return true;
if ((m_types & Device_Tablet) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_TABLET"), "1") == 0))
return true;
if ((m_types & Device_DRM) && (qstrcmp(udev_device_get_subsystem(dev), "drm") == 0))
return true;

View File

@ -0,0 +1,9 @@
HEADERS += \
$$PWD/qevdevtablet_p.h
SOURCES += \
$$PWD/qevdevtablet.cpp
contains(QT_CONFIG, libudev) {
LIBS += $$QMAKE_LIBS_LIBUDEV
}

View File

@ -0,0 +1,306 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the plugins module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qevdevtablet_p.h"
#include <QWindowSystemInterface>
#include <QStringList>
#include <QSocketNotifier>
#include <QGuiApplication>
#include <QDebug>
#include <QtCore/private/qcore_unix_p.h>
#include <QtPlatformSupport/private/qdevicediscovery_p.h>
#include <linux/input.h>
QT_BEGIN_NAMESPACE
class QEvdevTabletData
{
public:
QEvdevTabletData(QEvdevTabletHandler *q_ptr);
bool queryLimits();
void processInputEvent(input_event *ev);
void reportProximityEnter();
void reportProximityLeave();
void report();
QEvdevTabletHandler *q;
QSocketNotifier *notifier;
int fd;
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), notifier(0), fd(-1), lastEventType(0)
{
memset(&minValues, 0, sizeof(minValues));
memset(&maxValues, 0, sizeof(maxValues));
memset(&state, 0, sizeof(state));
}
bool QEvdevTabletData::queryLimits()
{
bool ok = true;
input_absinfo absInfo;
memset(&absInfo, 0, sizeof(input_absinfo));
ok &= ioctl(fd, EVIOCGABS(ABS_X), &absInfo) >= 0;
if (ok) {
minValues.x = absInfo.minimum;
maxValues.x = absInfo.maximum;
qDebug("evdevtablet: min X: %d max X: %d", minValues.x, maxValues.x);
}
ok &= ioctl(fd, EVIOCGABS(ABS_Y), &absInfo) >= 0;
if (ok) {
minValues.y = absInfo.minimum;
maxValues.y = absInfo.maximum;
qDebug("evdevtablet: min Y: %d max Y: %d", minValues.y, maxValues.y);
}
if (ioctl(fd, EVIOCGABS(ABS_PRESSURE), &absInfo) >= 0) {
minValues.p = absInfo.minimum;
maxValues.p = absInfo.maximum;
qDebug("evdevtablet: min pressure: %d max pressure: %d", minValues.p, maxValues.p);
}
if (ioctl(fd, EVIOCGABS(ABS_DISTANCE), &absInfo) >= 0) {
minValues.d = absInfo.minimum;
maxValues.d = absInfo.maximum;
qDebug("evdevtablet: min distance: %d max distance: %d", minValues.d, maxValues.d);
}
char name[128];
if (ioctl(fd, EVIOCGNAME(sizeof(name) - 1), name) >= 0) {
devName = QString::fromLocal8Bit(name);
qDebug("evdevtablet: device name: %s", name);
}
return ok;
}
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::reportProximityEnter()
{
QWindowSystemInterface::handleTabletEnterProximityEvent(QTabletEvent::Stylus, state.tool, 1);
}
void QEvdevTabletData::reportProximityLeave()
{
QWindowSystemInterface::handleTabletLeaveProximityEvent(QTabletEvent::Stylus, state.tool, 1);
}
void QEvdevTabletData::report()
{
if (!state.lastReportTool && state.tool)
reportProximityEnter();
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;
}
qreal pressure = (state.p - minValues.p) / qreal(maxValues.p - minValues.p);
if (state.down || state.lastReportDown) {
QWindowSystemInterface::handleTabletEvent(0, state.down, QPointF(), globalPos,
QTabletEvent::Stylus, pointer,
pressure, 0, 0, 0, 0, 0, 1, qGuiApp->keyboardModifiers());
}
if (state.lastReportTool && !state.tool)
reportProximityLeave();
state.lastReportDown = state.down;
state.lastReportTool = state.tool;
state.lastReportPos = globalPos;
}
QEvdevTabletHandler::QEvdevTabletHandler(const QString &spec, QObject *parent)
: QObject(parent), d(0)
{
setObjectName(QLatin1String("Evdev Tablet Handler"));
d = new QEvdevTabletData(this);
QString dev;
QStringList args = spec.split(QLatin1Char(':'));
for (int i = 0; i < args.count(); ++i) {
if (args.at(i).startsWith(QLatin1String("/dev/"))) {
dev = args.at(i);
break;
}
}
if (dev.isEmpty()) {
QScopedPointer<QDeviceDiscovery> deviceDiscovery(
QDeviceDiscovery::create(QDeviceDiscovery::Device_Tablet, this));
if (deviceDiscovery) {
QStringList devices = deviceDiscovery->scanConnectedDevices();
if (!devices.isEmpty())
dev = devices.at(0);
}
}
if (!dev.isEmpty()) {
qDebug("evdevtablet: using %s", qPrintable(dev));
d->fd = QT_OPEN(dev.toLocal8Bit().constData(), O_RDONLY | O_NDELAY, 0);
if (d->fd >= 0 && d->queryLimits()) {
d->notifier = new QSocketNotifier(d->fd, QSocketNotifier::Read, this);
connect(d->notifier, SIGNAL(activated(int)), this, SLOT(readData()));
}
}
}
QEvdevTabletHandler::~QEvdevTabletHandler()
{
delete d->notifier;
if (d->fd >= 0)
QT_CLOSE(d->fd);
delete d;
}
void QEvdevTabletHandler::readData()
{
static input_event buffer[32];
int n = 0;
for (; ;) {
int result = QT_READ(d->fd, reinterpret_cast<char*>(buffer) + n, sizeof(buffer) - n);
if (!result) {
qWarning("evdevtablet: Got EOF from input device");
return;
} else if (result < 0) {
if (errno != EINTR && errno != EAGAIN) {
qErrnoWarning(errno, "evdevtablet: Could not read from input device");
if (errno == ENODEV) { // device got disconnected -> stop reading
delete d->notifier;
d->notifier = 0;
QT_CLOSE(d->fd);
d->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 &spec, QObject *parent)
: QThread(parent), m_spec(spec), m_handler(0)
{
start();
}
QEvdevTabletHandlerThread::~QEvdevTabletHandlerThread()
{
quit();
wait();
}
void QEvdevTabletHandlerThread::run()
{
m_handler = new QEvdevTabletHandler(m_spec);
exec();
delete m_handler;
m_handler = 0;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,87 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the plugins module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QEVDEVTABLET_P_H
#define QEVDEVTABLET_P_H
#include <QObject>
#include <QString>
#include <QThread>
QT_BEGIN_HEADER
QT_BEGIN_NAMESPACE
class QEvdevTabletData;
class QEvdevTabletHandler : public QObject
{
Q_OBJECT
public:
QEvdevTabletHandler(const QString &spec = QString(), QObject *parent = 0);
~QEvdevTabletHandler();
private slots:
void readData();
private:
QEvdevTabletData *d;
};
class QEvdevTabletHandlerThread : public QThread
{
public:
QEvdevTabletHandlerThread(const QString &spec, QObject *parent = 0);
~QEvdevTabletHandlerThread();
void run();
QEvdevTabletHandler *handler() { return m_handler; }
private:
QString m_spec;
QEvdevTabletHandler *m_handler;
};
QT_END_NAMESPACE
QT_END_HEADER
#endif // QEVDEVTABLET_P_H

View File

@ -2,5 +2,5 @@ contains(QT_CONFIG, evdev) {
include($$PWD/evdevmouse/evdevmouse.pri)
include($$PWD/evdevkeyboard/evdevkeyboard.pri)
include($$PWD/evdevtouch/evdevtouch.pri)
include($$PWD/evdevtablet/evdevtablet.pri)
}

View File

@ -0,0 +1,44 @@
Generic plug-in for accessing pen-based tablets via evdev
To test, run the demo app from examples/widgets/tablet with the
command-line argument -plugin EvdevTablet
Known to work with 3rd generation Wacom Bamboo Pen & Touch. Recent
tablets like this one may need an updated wacom kernel driver for
kernels 3.2 and older.
Supports x, y, pressure, pen & eraser pointer types, proximity enter,
proximity leave. The additional advanced fields of QTabletEvent will
not be utilized.
Connecting the tablet will result in two input devices: one for finger
touch and the tablet buttons, and another for pen input including the
stylus buttons. This plugin cares about the latter only. For touch,
try the evdevtouch plugin.
The plugin generates QTabletEvents only. For compatibility with Qt 4,
TabletPress, Move and Release are only sent when the stylus is
touching the screen.
TabletEnterProximity and TabletLeaveProximity are always sent to the
QGuiApplication instance. Use an event filter (or subclass) to handle
these. In proximity events most of the data (incl. position) will be
left unspecified as these are mere indication of the fact that the
stylus entered the range of the tablet.
When running under X, it is likely that no events are received due to
the input device being grabbed. A temporary workaround is to disable
the device from X via
xinput set-prop <device> <device enabled property> 0.
TODO #1: Indicate motion when lifted but still in proximity. Needs new
event types.
TODO #1.1: Where to report ABS_DISTANCE? Needs a new QTabletEvent member
(unless we hijack z).
TODO #2: QKeyEvent from the stylus buttons (BTN_STYLUS, BTN_STYLUS2,
but there are no suitable Qt::Key_* constants?)
TODO #3: QKeyEvent in evdevtouch (or this plugin?) from tablet buttons

View File

@ -0,0 +1,3 @@
{
"Keys": [ "EvdevTablet" ]
}

View File

@ -0,0 +1,13 @@
TARGET = qevdevtabletplugin
load(qt_plugin)
DESTDIR = $$QT.gui.plugins/generic
target.path = $$[QT_INSTALL_PLUGINS]/generic
INSTALLS += target
SOURCES = main.cpp
QT += core-private platformsupport-private
OTHER_FILES += \
evdevtablet.json

View File

@ -0,0 +1,79 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qgenericplugin_qpa.h>
#include <QtPlatformSupport/private/qevdevtablet_p.h>
QT_BEGIN_NAMESPACE
class QEvdevTabletPlugin : public QGenericPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QGenericPluginFactoryInterface" FILE "evdevtablet.json")
public:
QEvdevTabletPlugin();
QStringList keys() const;
QObject* create(const QString &key, const QString &specification);
};
QEvdevTabletPlugin::QEvdevTabletPlugin()
{
}
QStringList QEvdevTabletPlugin::keys() const
{
return QStringList() << "EvdevTablet";
}
QObject *QEvdevTabletPlugin::create(const QString &key,
const QString &spec)
{
if (!key.compare(QLatin1String("EvdevTablet"), Qt::CaseInsensitive))
return new QEvdevTabletHandlerThread(spec);
return 0;
}
QT_END_NAMESPACE
#include "main.moc"

View File

@ -3,5 +3,5 @@ TEMPLATE = subdirs
linux-g++-maemo: SUBDIRS += meego
contains(QT_CONFIG, evdev) {
SUBDIRS += evdevmouse evdevtouch evdevkeyboard
SUBDIRS += evdevmouse evdevtouch evdevkeyboard evdevtablet
}