aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel d'Andrada <daniel.dandrada@luxoft.com>2017-10-05 16:20:17 +0200
committerDaniel d'Andrada <daniel.dandrada@luxoft.com>2017-10-06 06:41:16 +0000
commit825a16569525f102fbeec839425d244eab03bff2 (patch)
treeb4fe5459270654c3d014b30f4bf380ed660d52b3
parentf5310e19b99c0e6792dff64aa3e55929d7c73a5c (diff)
Convert MouseEvents into TouchEvents on unix
The UI was meant to be used with touch interaction, not mouse. MouseEvents and TouchEvents follow completely different code paths in Qt. Therefore when interacting with neptune on a linux desktop using a mouse, which is the standard development setup, we should still be exercising the touch code path, which is what happens on target hardware (touchscreen displays). Otherwise you might enconter touch vs. mouse specific issues only when trying the software on a touchscreen, and that's not very productive. Change-Id: Iedcf2052a15539d0c9024b233a20ae6a55e3e06d Reviewed-by: Bramastyo Harimukti Santoso <bramastyo.harimukti.santoso@pelagicore.com>
-rw-r--r--src/MouseTouchAdaptor.cpp278
-rw-r--r--src/MouseTouchAdaptor.h73
-rw-r--r--src/main.cpp16
-rw-r--r--src/src.pro10
4 files changed, 375 insertions, 2 deletions
diff --git a/src/MouseTouchAdaptor.cpp b/src/MouseTouchAdaptor.cpp
new file mode 100644
index 0000000..31d4d59
--- /dev/null
+++ b/src/MouseTouchAdaptor.cpp
@@ -0,0 +1,278 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Neptune IVI UI.
+**
+** $QT_BEGIN_LICENSE:GPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) 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.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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: GPL-3.0
+**
+****************************************************************************/
+
+#include "MouseTouchAdaptor.h"
+
+#include <QGuiApplication>
+#include <QMouseEvent>
+#include <QTest>
+
+#include <qpa/qplatformnativeinterface.h>
+#include <qpa/qwindowsysteminterface.h>
+
+#include <X11/extensions/XInput2.h>
+#include <X11/extensions/XI2proto.h>
+
+Q_LOGGING_CATEGORY(mouseTouchAdaptor, "mousetouchadaptor")
+
+using QTest::QTouchEventSequence;
+
+namespace {
+
+Qt::MouseButton xcbButtonToQtMouseButton(xcb_button_t detail)
+{
+ switch (detail) {
+ case 1: return Qt::LeftButton;
+ case 2: return Qt::MidButton;
+ case 3: return Qt::RightButton;
+ // don't care about the rest
+ default: return Qt::NoButton;
+ }
+}
+
+void xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *event)
+{
+ // xcb event structs contain stuff that wasn't on the wire, the full_sequence field
+ // adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes.
+ // Move this data back to have the same layout in memory as it was on the wire
+ // and allow casting, overwriting the full_sequence field.
+ memmove((char*) event + 32, (char*) event + 36, event->length * 4);
+}
+
+qreal fixed1616ToReal(FP1616 val)
+{
+ return qreal(val) / 0x10000;
+}
+
+} // anonymous namespace
+
+MouseTouchAdaptor *MouseTouchAdaptor::m_instance = nullptr;
+
+MouseTouchAdaptor *MouseTouchAdaptor::instance()
+{
+ if (!m_instance) {
+ new MouseTouchAdaptor;
+ }
+ return m_instance;
+}
+
+MouseTouchAdaptor::MouseTouchAdaptor()
+{
+ Q_ASSERT(!m_instance);
+ m_instance = this;
+
+ qGuiApp->installNativeEventFilter(this);
+
+ // Create a fake touch device to deliver our synthesized events
+ m_touchDevice = new QTouchDevice;
+ m_touchDevice->setType(QTouchDevice::TouchScreen);
+ QWindowSystemInterface::registerTouchDevice(m_touchDevice);
+
+ queryForXInput2();
+}
+
+MouseTouchAdaptor::~MouseTouchAdaptor()
+{
+ Q_ASSERT(m_instance);
+ m_instance = nullptr;
+}
+
+void MouseTouchAdaptor::queryForXInput2()
+{
+ QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface();
+ Display *xDisplay = static_cast<Display*>(nativeInterface->nativeResourceForIntegration("Display"));
+
+ int xiOpCode, xiEventBase, xiErrorBase;
+ if (xDisplay && XQueryExtension(xDisplay, "XInputExtension", &xiOpCode, &xiEventBase, &xiErrorBase)) {
+ // 2.0 is enough for our needs
+ int xiMajor = 2;
+ int xi2Minor = 0;
+ m_haveXInput2 = XIQueryVersion(xDisplay, &xiMajor, &xi2Minor) != BadRequest;
+ }
+}
+
+bool MouseTouchAdaptor::nativeEventFilter(const QByteArray &eventType, void *message, long * /*result*/)
+{
+ if (eventType != "xcb_generic_event_t") {
+ qCWarning(mouseTouchAdaptor) << "Non XCB native event received. Ignoring.";
+ return false;
+ }
+
+ xcb_generic_event_t *xcbEvent = static_cast<xcb_generic_event_t *>(message);
+
+ switch (xcbEvent->response_type & ~0x80) {
+ case XCB_BUTTON_PRESS: {
+ auto xcbPress = reinterpret_cast<xcb_button_press_event_t *>(xcbEvent);
+ return handleButtonPress(static_cast<WId>(xcbPress->event), xcbPress->detail, 0,
+ xcbPress->event_x, xcbPress->event_y);
+ }
+ case XCB_BUTTON_RELEASE: {
+ auto xcbRelease = reinterpret_cast<xcb_button_release_event_t *>(xcbEvent);
+ return handleButtonRelease(static_cast<WId>(xcbRelease->event), xcbRelease->detail, 0,
+ xcbRelease->event_x, xcbRelease->event_y);
+ }
+ case XCB_MOTION_NOTIFY: {
+ auto xcbMotion = reinterpret_cast<xcb_motion_notify_event_t *>(xcbEvent);
+ return handleMotionNotify(static_cast<WId>(xcbMotion->event), 0,
+ xcbMotion->event_x, xcbMotion->event_y);
+ }
+ case XCB_GE_GENERIC:
+ if (m_haveXInput2) {
+ return handleXI2Event(reinterpret_cast<xcb_ge_event_t *>(xcbEvent));
+ } else {
+ return false;
+ }
+ default:
+ return false;
+ };
+}
+
+bool MouseTouchAdaptor::handleButtonPress(WId windowId, uint32_t detail, uint32_t /*modifiers*/, int x, int y)
+{
+ Qt::MouseButton button = xcbButtonToQtMouseButton(detail);
+ qCDebug(mouseTouchAdaptor) << "***** press";
+
+ // Filter out the other mouse buttons
+ if (button != Qt::LeftButton)
+ return true;
+
+ QWindow *targetWindow = findQWindowWithXWindowID(windowId);
+
+ QPoint windowPos(x / targetWindow->devicePixelRatio(), y / targetWindow->devicePixelRatio());
+
+ QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice, false /* autoCommit */);
+ touchEvent.press(0 /* touchId */, windowPos);
+ touchEvent.commit(false /* processEvents */);
+
+ m_leftButtonIsPressed = true;
+ return true;
+}
+
+bool MouseTouchAdaptor::handleButtonRelease(WId windowId, uint32_t detail, uint32_t, int x, int y)
+{
+ Qt::MouseButton button = xcbButtonToQtMouseButton(detail);
+ qCDebug(mouseTouchAdaptor) << "***** release";
+
+ // Don't eat the event if it wasn't a left mouse press
+ if (button != Qt::LeftButton)
+ return false;
+
+ QWindow *targetWindow = findQWindowWithXWindowID(windowId);
+
+ QPoint windowPos(x / targetWindow->devicePixelRatio(), y / targetWindow->devicePixelRatio());
+
+ QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice, false /* autoCommit */);
+ touchEvent.release(0 /* touchId */, windowPos);
+ touchEvent.commit(false /* processEvents */);
+
+ m_leftButtonIsPressed = false;
+ return true;
+}
+
+bool MouseTouchAdaptor::handleMotionNotify(WId windowId, uint32_t /*modifiers*/, int x, int y)
+{
+ if (!m_leftButtonIsPressed)
+ return true;
+
+ QWindow *targetWindow = findQWindowWithXWindowID(windowId);
+
+ QPoint windowPos(x / targetWindow->devicePixelRatio(), y / targetWindow->devicePixelRatio());
+
+ QTouchEventSequence touchEvent = QTest::touchEvent(targetWindow, m_touchDevice, false /* autoCommit */);
+ touchEvent.move(0 /* touchId */, windowPos);
+ touchEvent.commit(false /* processEvents */);
+
+ return true;
+}
+
+QWindow *MouseTouchAdaptor::findQWindowWithXWindowID(WId windowId)
+{
+ QWindowList windowList = QGuiApplication::topLevelWindows();
+ QWindow *foundWindow = nullptr;
+
+ int i = 0;
+ while (!foundWindow && i < windowList.count()) {
+ QWindow *window = windowList[i];
+ if (window->winId() == windowId)
+ foundWindow = window;
+ else
+ ++i;
+ }
+
+ Q_ASSERT(foundWindow);
+ return foundWindow;
+}
+
+bool MouseTouchAdaptor::handleXI2Event(xcb_ge_event_t *event)
+{
+ xi2PrepareXIGenericDeviceEvent(event);
+ xXIGenericDeviceEvent *xiEvent = reinterpret_cast<xXIGenericDeviceEvent *>(event);
+ xXIDeviceEvent *xiDeviceEvent = 0;
+
+ switch (xiEvent->evtype) {
+ case XI_ButtonPress:
+ case XI_ButtonRelease:
+ case XI_Motion:
+ xiDeviceEvent = reinterpret_cast<xXIDeviceEvent *>(event);
+ break;
+ default:
+ break;
+ }
+
+ if (!xiDeviceEvent)
+ return false;
+
+ switch (xiDeviceEvent->evtype) {
+ case XI_ButtonPress:
+ return handleButtonPress(
+ static_cast<WId>(xiDeviceEvent->event),
+ xiDeviceEvent->detail,
+ xiDeviceEvent->mods.base_mods,
+ fixed1616ToReal(xiDeviceEvent->event_x),
+ fixed1616ToReal(xiDeviceEvent->event_y));
+ case XI_ButtonRelease:
+ return handleButtonRelease(
+ static_cast<WId>(xiDeviceEvent->event),
+ xiDeviceEvent->detail,
+ xiDeviceEvent->mods.base_mods,
+ fixed1616ToReal(xiDeviceEvent->event_x),
+ fixed1616ToReal(xiDeviceEvent->event_y));
+ case XI_Motion:
+ return handleMotionNotify(
+ static_cast<WId>(xiDeviceEvent->event),
+ xiDeviceEvent->mods.base_mods,
+ fixed1616ToReal(xiDeviceEvent->event_x),
+ fixed1616ToReal(xiDeviceEvent->event_y));
+ return true;
+ default:
+ return false;
+ }
+}
diff --git a/src/MouseTouchAdaptor.h b/src/MouseTouchAdaptor.h
new file mode 100644
index 0000000..aae8a9f
--- /dev/null
+++ b/src/MouseTouchAdaptor.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Neptune IVI UI.
+**
+** $QT_BEGIN_LICENSE:GPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) 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.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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: GPL-3.0
+**
+****************************************************************************/
+
+#ifndef MOUSETOUCHADAPTOR_H
+#define MOUSETOUCHADAPTOR_H
+
+#include <QAbstractNativeEventFilter>
+#include <QLoggingCategory>
+#include <QObject>
+#include <QTouchDevice>
+#include <QWindow>
+
+#include <xcb/xcb.h>
+
+Q_DECLARE_LOGGING_CATEGORY(mouseTouchAdaptor)
+
+/*
+ Converts native mouse events into touch events
+
+ Useful for emulating touch interaction using a mouse device since touch input
+ follows a completely different code path from mouse events in Qt.
+ */
+class MouseTouchAdaptor : public QObject, public QAbstractNativeEventFilter {
+public:
+ static MouseTouchAdaptor *instance();
+ virtual ~MouseTouchAdaptor();
+
+ bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override;
+private:
+ MouseTouchAdaptor();
+ void queryForXInput2();
+ bool handleButtonPress(WId windowId, uint32_t detail, uint32_t modifiers, int x, int y);
+ bool handleButtonRelease(WId windowId, uint32_t detail, uint32_t, int x, int y);
+ bool handleMotionNotify(WId windowId, uint32_t modifiers, int x, int y);
+ QWindow *findQWindowWithXWindowID(WId windowId);
+ bool handleXI2Event(xcb_ge_event_t *event);
+
+ static MouseTouchAdaptor *m_instance;
+
+ QTouchDevice *m_touchDevice;
+ bool m_haveXInput2{false};
+ bool m_leftButtonIsPressed{false};
+};
+
+#endif // MOUSETOUCHADAPTOR_H
diff --git a/src/main.cpp b/src/main.cpp
index 562f07f..e1be1b2 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -37,6 +37,10 @@
#include <QtAppManInstaller/sudo.h>
#include <QGuiApplication>
+#ifdef NEPTUNE_ENABLE_TOUCH_EMULATION
+#include "MouseTouchAdaptor.h"
+#endif
+
QT_USE_NAMESPACE_AM
Q_DECL_EXPORT int main(int argc, char *argv[])
@@ -66,13 +70,23 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
QStringList fileList;
fileList.append(QStringLiteral("am-config.yaml"));
+#ifdef NEPTUNE_ENABLE_TOUCH_EMULATION
+ auto *mouseTouchAdaptor = MouseTouchAdaptor::instance();
+#endif
+
DefaultConfiguration cfg(fileList, "");
cfg.parse();
a.setup(&cfg);
a.loadQml(cfg.loadDummyData());
a.showWindow(cfg.fullscreen() && !cfg.noFullscreen());
- return MainBase::exec();
+ int result = MainBase::exec();
+
+#ifdef NEPTUNE_ENABLE_TOUCH_EMULATION
+ delete mouseTouchAdaptor;
+#endif
+
+ return result;
} catch (const std::exception &e) {
qCCritical(LogSystem) << "ERROR:" << e.what();
return 2;
diff --git a/src/src.pro b/src/src.pro
index 041f21d..ebbf35c 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -7,10 +7,18 @@ macos: {
CONFIG -= app_bundle
}
-QT = appman_main-private
+QT = appman_main-private testlib
SOURCES = main.cpp
+unix: {
+ CONFIG += link_pkgconfig
+ PKGCONFIG += xcb x11 xi
+ SOURCES += MouseTouchAdaptor.cpp
+ HEADERS += MouseTouchAdaptor.h
+ DEFINES += NEPTUNE_ENABLE_TOUCH_EMULATION
+}
+
DESTDIR = $$OUT_PWD/../
target.path = $$INSTALL_PREFIX/neptune