From dfae6a7593a6bb4ad6accc30d6aaf01a98bc2a6f Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Tue, 9 Aug 2016 15:48:12 +0200 Subject: evdevtouch: Enable touch in multi-screen eglfs environments Parse the touchDevice property from the KMS/DRM config file. When all outputs have an explicitly specified index in the virtual desktop, we can set up a mapping between the device node and the screen index. It is somewhat fragile (device nodes may change, requires explicit virtualIndex properties for all outputs, etc.) but better than nothing. For example, having the screen on DisplayPort as primary and the touchscreen on HDMI as the secondary screen breaks by default because touching the second screen generates touch (and synthesized mouse) events for the first screen. Assuming the touchscreen is /dev/input/event5, the issue can now be fixed by setting QT_QPA_EGLFS_KMS_CONFIG with a configuration like the following: { "device": "drm-nvdc", "outputs": [ { "name": "HDMI1", "touchDevice": "/dev/input/event5", "virtualIndex": 1 }, { "name": "DP1", "virtualIndex": 0 } ] } Task-number: QTBUG-54151 Change-Id: If97fa18a65599ccfe64ce408ea43086ec3863682 Reviewed-by: Andy Nichols --- .../input/evdevtouch/evdevtouch.pri | 3 +- .../input/evdevtouch/qevdevtouchhandler.cpp | 40 +++++++++- src/platformsupport/input/input.pri | 4 + src/platformsupport/input/libinput/libinput.pri | 2 + .../input/libinput/qlibinputtouch.cpp | 11 ++- .../input/shared/qtouchoutputmapping.cpp | 91 ++++++++++++++++++++++ .../input/shared/qtouchoutputmapping_p.h | 71 +++++++++++++++++ src/platformsupport/input/shared/shared.pri | 5 ++ .../eglfs_kms_support/qeglfskmsdevice.cpp | 3 + 9 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 src/platformsupport/input/shared/qtouchoutputmapping.cpp create mode 100644 src/platformsupport/input/shared/qtouchoutputmapping_p.h create mode 100644 src/platformsupport/input/shared/shared.pri diff --git a/src/platformsupport/input/evdevtouch/evdevtouch.pri b/src/platformsupport/input/evdevtouch/evdevtouch.pri index c2edc13143..44eb9da113 100644 --- a/src/platformsupport/input/evdevtouch/evdevtouch.pri +++ b/src/platformsupport/input/evdevtouch/evdevtouch.pri @@ -6,6 +6,8 @@ SOURCES += \ $$PWD/qevdevtouchhandler.cpp \ $$PWD/qevdevtouchmanager.cpp +INCLUDEPATH += $$PWD/../shared + contains(QT_CONFIG, libudev) { LIBS_PRIVATE += $$QMAKE_LIBS_LIBUDEV } @@ -14,4 +16,3 @@ contains(QT_CONFIG, mtdev) { CONFIG += link_pkgconfig PKGCONFIG_PRIVATE += mtdev } - diff --git a/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp b/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp index 6b98ed30a9..5f9455559d 100644 --- a/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp +++ b/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qevdevtouchhandler_p.h" +#include "qtouchoutputmapping_p.h" #include #include #include @@ -116,6 +117,7 @@ public: int findClosestContact(const QHash &contacts, int x, int y, int *dist); void addTouchPoint(const Contact &contact, Qt::TouchPointStates *combinedStates); void reportPoints(); + void loadMultiScreenMappings(); int hw_range_x_min; int hw_range_x_max; @@ -124,10 +126,12 @@ public: int hw_pressure_min; int hw_pressure_max; QString hw_name; + QString deviceNode; bool m_forceToActiveWindow; bool m_typeB; QTransform m_rotate; bool m_singleTouch; + int m_screenIndex; }; QEvdevTouchScreenData::QEvdevTouchScreenData(QEvdevTouchScreenHandler *q_ptr, const QStringList &args) @@ -137,7 +141,8 @@ QEvdevTouchScreenData::QEvdevTouchScreenData(QEvdevTouchScreenHandler *q_ptr, co hw_range_x_min(0), hw_range_x_max(0), hw_range_y_min(0), hw_range_y_max(0), hw_pressure_min(0), hw_pressure_max(0), - m_typeB(false), m_singleTouch(false) + m_typeB(false), m_singleTouch(false), + m_screenIndex(-1) { m_forceToActiveWindow = args.contains(QLatin1String("force_window")); } @@ -222,7 +227,9 @@ QEvdevTouchScreenHandler::QEvdevTouchScreenHandler(const QString &device, const } #endif - qCDebug(qLcEvdevTouch, "evdevtouch: %s: Protocol type %c %s (%s)", qPrintable(device), + d->deviceNode = device; + + qCDebug(qLcEvdevTouch, "evdevtouch: %s: Protocol type %c %s (%s)", qPrintable(d->deviceNode), d->m_typeB ? 'B' : 'A', mtdevStr, d->m_singleTouch ? "single" : "multi"); input_absinfo absInfo; @@ -292,6 +299,14 @@ QEvdevTouchScreenHandler::QEvdevTouchScreenHandler(const QString &device, const if (inverty) d->m_rotate *= QTransform::fromTranslate(0.5, 0.5).scale(1.0, -1.0).translate(-0.5, -0.5); + QTouchOutputMapping mapping; + if (mapping.load()) { + d->m_screenIndex = mapping.screenIndexForDeviceNode(d->deviceNode); + if (d->m_screenIndex >= 0) + qCDebug(qLcEvdevTouch, "evdevtouch: Mapping device %s to screen index %d", + qPrintable(d->deviceNode), d->m_screenIndex); + } + registerTouchDevice(); } @@ -643,8 +658,23 @@ void QEvdevTouchScreenData::reportPoints() return; winRect = QHighDpi::toNativePixels(win->geometry(), win); } else { - QScreen *primary = QGuiApplication::primaryScreen(); - winRect = QHighDpi::toNativePixels(primary->geometry(), primary); + // Now it becomes tricky. Traditionally we picked the primaryScreen() + // and were done with it. But then, enter multiple screens, and + // suddenly it was all broken. + // + // For now we only support the display configuration of the KMS/DRM + // backends of eglfs. See QTouchOutputMapping. + // + // The good news it that once winRect refers to the correct screen + // geometry in the full virtual desktop space, there is nothing else + // left to do since qguiapp will handle the rest. + QScreen *screen = QGuiApplication::primaryScreen(); + if (m_screenIndex >= 0) { + const QList screens = QGuiApplication::screens(); + if (m_screenIndex < screens.count()) + screen = screens.at(m_screenIndex); + } + winRect = QHighDpi::toNativePixels(screen->geometry(), screen); } const int hw_w = hw_range_x_max - hw_range_x_min; @@ -675,10 +705,12 @@ void QEvdevTouchScreenData::reportPoints() tp.pressure = (tp.pressure - hw_pressure_min) / qreal(hw_pressure_max - hw_pressure_min); } + // Let qguiapp pick the target window. QWindowSystemInterface::handleTouchEvent(Q_NULLPTR, q->touchDevice(), m_touchPoints); } + QEvdevTouchScreenHandlerThread::QEvdevTouchScreenHandlerThread(const QString &device, const QString &spec, QObject *parent) : QDaemonThread(parent), m_device(device), m_spec(spec), m_handler(Q_NULLPTR), m_touchDeviceRegistered(false) { diff --git a/src/platformsupport/input/input.pri b/src/platformsupport/input/input.pri index 3b9593eb31..5ce9e6844f 100644 --- a/src/platformsupport/input/input.pri +++ b/src/platformsupport/input/input.pri @@ -12,3 +12,7 @@ contains(QT_CONFIG, tslib) { contains(QT_CONFIG, libinput) { include($$PWD/libinput/libinput.pri) } + +contains(QT_CONFIG, evdev)|contains(QT_CONFIG, libinput) { + include($$PWD/shared/shared.pri) +} diff --git a/src/platformsupport/input/libinput/libinput.pri b/src/platformsupport/input/libinput/libinput.pri index 35d962ff3c..aeba6c725a 100644 --- a/src/platformsupport/input/libinput/libinput.pri +++ b/src/platformsupport/input/libinput/libinput.pri @@ -13,6 +13,8 @@ SOURCES += \ INCLUDEPATH += $$QMAKE_INCDIR_LIBUDEV $$QMAKE_INCDIR_LIBINPUT LIBS_PRIVATE += $$QMAKE_LIBS_LIBUDEV $$QMAKE_LIBS_LIBINPUT +INCLUDEPATH += $$PWD/../shared + contains(QT_CONFIG, xkbcommon-evdev) { INCLUDEPATH += $$QMAKE_INCDIR_XKBCOMMON_EVDEV LIBS_PRIVATE += $$QMAKE_LIBS_XKBCOMMON_EVDEV diff --git a/src/platformsupport/input/libinput/qlibinputtouch.cpp b/src/platformsupport/input/libinput/qlibinputtouch.cpp index 1e82ce5c91..42925a18e1 100644 --- a/src/platformsupport/input/libinput/qlibinputtouch.cpp +++ b/src/platformsupport/input/libinput/qlibinputtouch.cpp @@ -64,11 +64,14 @@ QLibInputTouch::DeviceState *QLibInputTouch::deviceState(libinput_event_touch *e static inline QPointF getPos(libinput_event_touch *e) { + // TODO Map to correct screen using QTouchOutputMapping. + // Perhaps investigate libinput_device_get_output_name as well. + // For now just use the primary screen. QScreen *screen = QGuiApplication::primaryScreen(); - const QSize screenSize = QHighDpi::toNativePixels(screen->geometry().size(), screen); - const double x = libinput_event_touch_get_x_transformed(e, screenSize.width()); - const double y = libinput_event_touch_get_y_transformed(e, screenSize.height()); - return QPointF(x, y); + const QRect geom = QHighDpi::toNativePixels(screen->geometry(), screen); + const double x = libinput_event_touch_get_x_transformed(e, geom.width()); + const double y = libinput_event_touch_get_y_transformed(e, geom.height()); + return geom.topLeft() + QPointF(x, y); } void QLibInputTouch::registerDevice(libinput_device *dev) diff --git a/src/platformsupport/input/shared/qtouchoutputmapping.cpp b/src/platformsupport/input/shared/qtouchoutputmapping.cpp new file mode 100644 index 0000000000..55c1dc34f4 --- /dev/null +++ b/src/platformsupport/input/shared/qtouchoutputmapping.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtouchoutputmapping_p.h" +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +bool QTouchOutputMapping::load() +{ + static QByteArray configFile = qgetenv("QT_QPA_EGLFS_KMS_CONFIG"); + if (configFile.isEmpty()) + return false; + + QFile file(QString::fromUtf8(configFile)); + if (!file.open(QFile::ReadOnly)) { + qWarning("touch input support: Failed to open %s", configFile.constData()); + return false; + } + + const QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + if (!doc.isObject()) { + qWarning("touch input support: Failed to parse %s", configFile.constData()); + return false; + } + + // What we are interested is the virtualIndex and touchDevice properties for + // each element in the outputs array. + const QJsonArray outputs = doc.object().value(QLatin1String("outputs")).toArray(); + for (int i = 0; i < outputs.size(); ++i) { + const QVariantMap output = outputs.at(i).toObject().toVariantMap(); + if (!output.contains(QStringLiteral("touchDevice"))) + continue; + if (!output.contains(QStringLiteral("virtualIndex"))) { + qWarning("evdevtouch: Output %d specifies touchDevice but not virtualIndex, this is wrong", i); + continue; + } + const QString &deviceNode = output.value(QStringLiteral("touchDevice")).toString(); + const int screenIndex = output.value(QStringLiteral("virtualIndex")).toInt(); + m_screenIndexTable.insert(deviceNode, screenIndex); + } + + return true; +} + +int QTouchOutputMapping::screenIndexForDeviceNode(const QString &deviceNode) +{ + return m_screenIndexTable.value(deviceNode, -1); +} + +QT_END_NAMESPACE diff --git a/src/platformsupport/input/shared/qtouchoutputmapping_p.h b/src/platformsupport/input/shared/qtouchoutputmapping_p.h new file mode 100644 index 0000000000..74999d93ce --- /dev/null +++ b/src/platformsupport/input/shared/qtouchoutputmapping_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTOUCHOUTPUTMAPPING_P_H +#define QTOUCHOUTPUTMAPPING_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QTouchOutputMapping +{ +public: + bool load(); + int screenIndexForDeviceNode(const QString &deviceNode); + +private: + QHash m_screenIndexTable; +}; + +QT_END_NAMESPACE + +#endif // QTOUCHOUTPUTMAPPING_P_H diff --git a/src/platformsupport/input/shared/shared.pri b/src/platformsupport/input/shared/shared.pri new file mode 100644 index 0000000000..1443235244 --- /dev/null +++ b/src/platformsupport/input/shared/shared.pri @@ -0,0 +1,5 @@ +HEADERS += \ + $$PWD/qtouchoutputmapping_p.h + +SOURCES += \ + $$PWD/qtouchoutputmapping.cpp diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.cpp index 842896bbad..5944e8d51f 100644 --- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.cpp +++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice.cpp @@ -417,6 +417,9 @@ void QEglFSKmsDevice::createScreens() else pos.rx() += s->geometry().width(); qCDebug(qLcEglfsKmsDebug) << "Adding screen" << s << "to QPA with geometry" << s->geometry(); + // The order in qguiapp's screens list will match the order set by + // virtualIndex. This is not only handy but also required since for instance + // evdevtouch relies on it when performing touch device - screen mapping. qpaIntegration->addScreen(s); siblings << s; } -- cgit v1.2.3